일을 하다보면 다른 소유자(계정)간에 협업을 할 때 데이터 제공 등의 목적으로 View를 생성, 제공해 주는 경우가 종종있습니다. 그리고 애써 View를 구성해서 제공해 주었는데 부득이하게 등의 이유로 제공받은 쪽에서 데이터를 업데이트를 해야하는 경우가 있습니다. 이때 원본 테이블을 제공해 줄 수 없는 경우 또는 원본 테이블이 여러개인 경우(대부분 View를 생성하는 경우가 원본이 여러개인 경우일 듯 합니다...) 등의 이유로 난감한 경우가 발생하기도 합니다. (제 경우에는 늘어나는 업무량에 '그냥 확 원본 테이블을 제공하자!'가 항상 머릿속을 맴돌지만, 원본 테이블들을 제공하고 업데이트 시 데이터/업무 흐름이나 컬럼 플래그 케이스 등을 설명하고 문서를 작성하는게 업무량은 훨씬 더 많이 늘어나기에... 포기했습니다.)


이런 난감한 경우에 View에 Trigger를 연결해서 원본 테이블의 데이터를 업데이트 하도록 할 수 있습니다. 즉, 데이터/업무 흐름등에 맞춰 Trigger를 생성하고 View에 연결하면, View를 제공받는 쪽에 원본 테이블을 알려 줄 필요없이 View에 Update를 실행하는 것만으로도 원본 테이블에 대한 데이터를 수정할 수 있게 됩니다. 실은 저도 View에 업데이트 구문이 실행된다는 사실은 이번에 알게 됬습니다. Trigger 없이 실행한 경우 실행만 되고 실제 원본 데이터가 업데이트 되진 않더군요.


'업데이트 요청 및 백업 용'같은 성격의 테이블을 만들어 이력을 남기도록 Trigger에 함께 추가 해 놓으니 협업할 때 검증 용으로 괜찮았습니다.


아래는 간단하게 테스트 해 볼 수 있도록 한 예제입니다.
아참, 제공받는 소유자(계정)에 대해서 View를 Update할 수 있는 권한을 부여해 주어야 한다는 것을 잊지 마세요.

1. 원본 테이블(Origin Table) 및 View의 생성

View를 업데이트 할 때 수정 될 원본 테이블(Origin Table)과 View를 생성하고, 원본 테이블(Origin Table)의 데이터를 넣어 줍니다.

-- Origin Table을 생성합니다.
CREATE TABLE ORIGIN_TABLE (
  SEQ NUMBER
, FLAG CHAR(1)
);

-- Origin Table을 기준으로 하는 View Table을 생성합니다.
CREATE VIEW ORIGIN_DATA_VIEW (
  SEQ
, FLAG
) AS
    SELECT SEQ, FLAG FROM ORIGIN_TABLE;

-- 이 테이블은 원본에 대한 백업과 요청 이력을 남겨 보려 생성하는 테이블 입니다.
CREATE TABLE REQ_HISTORY (
  SEQ NUMBER
, FLAG CHAR(1)
, REQ_DATE DATE
);

-- 원본 테이블(Origin Table)에 데이터를 넣어 줍니다. 
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 1, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 2, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 3, 'S' );
INSERT INTO ORIGIN_TABLE (SEQ, FLAG) VALUES ( 4, 'S' );

2. View에 연결되어 작동하는 Trigger 생성

이제 View에 연결되어 작동하는 Trigger를 생성합니다. 일반 Trigger 생성과 같이 Insert, Update, Delete에 대한 처리를 해 줄 수 있습니다만 여기서는 Update만 하도록 하겠습니다.

-- Update시 작동하는 트리거를 작성합니다.
CREATE OR REPLACE TRIGGER TRI_UPDATE_VIEW
    INSTEAD OF UPDATE --(OR INSERT OR DELETE) 여기 INSTEAD가 중요합니다.
    ON ORIGIN_DATA_VIEW
    REFERENCING NEW AS NEW OLD AS OLD
    FOR EACH ROW

BEGIN
    -- 원본 테이블을 업데이트 합니다.
    UPDATE ORIGIN_TABLE SET FLAG = :NEW.flag WHERE SEQ = :NEW.seq;

    -- 원본 백업 및 이력을 남겨 봅니다.
    INSERT INTO REQ_HISTORY(SEQ, FLAG, REQ_DATE) VALUES (:NEW.seq, :OLD.flag, SYSDATE);

EXCEPTION
    WHEN OTHERS THEN RAISE;

END TRI_UPDATE_VIEW;

3. 작동에 대한 테스트

이제 실제로 View에 업데이트를 실행 해서 원본 테이블(Origin Table)의 데이터가 변경 되었는지 확인해 봅니다.

-- VIEW를 UPDATE 해 봅니다.
UPDATE ORIGIN_DATA_VIEW SET FLAG = 'R' WHERE SEQ = 1;
UPDATE ORIGIN_DATA_VIEW SET FLAG = 'X' WHERE SEQ = 1;


-- 갱신이 되었는지 원본 테이블(Origin Table)과 VIEW를 조회 해 봅니다.
SELECT * FROM ORIGIN_TABLE;
SELECT * FROM ORIGIN_DATA_VIEW;


.Net framework v4.0, Visual Studio 2010, asp.net MVC3 프로젝트 기준으로 진행된 내역입니다.

Asp.Net MVC 중에 _Layout.cshtml에서 각 페이지 렌더링 될 때 사용될 기본적인 룰을 정리하던 도중 RenderSection처리하는 부분이 너무 번거로운 것 같아 '좀 내 입맛대로 해보자!'하는 취지에서 찾아보고 진행했던 내용입니다.

WebViewPage클래스를 상속받는 클래스 생성

RenderSection은 MSDN을 찾아보면 System.Web.WebPages.WebPageBase클래스에 구현되어 있으니 WebPageBase클래스를 상속받은 클래스를 생성해도 되지만, WebViewPage클래스에 구현되어 View 페이지(*.cshtml)에서 사용되는 유용한 속성, 메서드들은 사용할 수 없거나, 다시 구현해야 하기 때문에 WebViewPage클래스를 상속받는 클래스를 생성합니다.

클래스를 생성하는 위치는 적절하게 선택하면 되며, 여기서는 WebViewPageHandler 클래스로 진행하도록 하겠습니다.
제가 원하는 RenderSection의 기능이 <script ...></script>를 필요에 따라서 삭제하고, 추가로 jquery에서 사용되는 $(document).ready...로 시작되는 부분을 필요에 따라서 삭제할 수 있도록 하는 부분이어서 아래와 같이 구현하였습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Web.Mvc;
using System.Web.WebPages;
using System.Text.RegularExpressions;

namespace 네임스페이스 명
{
    public abstract class WebViewPageHandler<T> : WebViewPage<T>
    {
        public override void InitHelpers()
        {
            base.InitHelpers();
        }

        /// <summary>
        /// 레이아웃 페이지에서 명명된 섹션의 콘텐츠를 렌더링하고 섹션이 필수인지를 지정합니다. 
        /// <para>또한 스크립트 블럭 테그와  Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제할지 지정합니다.</para>
        /// </summary>
        /// <param name="name">렌더링할 섹션입니다.</param>
        /// <param name="required">섹션을 필수로지정하려면 true이고, 그렇지 않으면 false입니다.</param>
        /// <param name="isRemoveScriptBlock">상단에 스크립트 블럭 시작Tag 와 끝Tag를 삭제하려면 true, 그렇지 않으면 false입니다.</param>
        /// <param name="isRemoveDocReady">Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제하려면 true, 그렇지 않으면 false입니다.</param>
        /// <returns>렌더링할 HTML 콘텐츠입니다.</returns>
        public HelperResult RenderSection(string name, bool required, bool isRemoveScriptBlock, bool isRemoveDocReady)
        {
            String renderString = NoNull.ToString(base.RenderSection(name, required), string.Empty);

            #region 객체에 대한 유효성 검사를 실시합니다. 
            if (string.IsNullOrEmpty(renderString) && !required)
            {
                // 필수지정이 되어 있지않고, 구현 된 Section 내용이 없다면 null을 리턴합니다.
                // cshtml에서는 아무런 내용도 작성되지 않습니다.
                return null;
            }
            else if (string.IsNullOrEmpty(renderString) && !required)
            {
                // 필수로 지정되어있으나 구현 된 Section 내용이 없다면 오류로 생각해야 합니다.
                // 때문에 MSDN 오류 처리에 맞춰 오류를 발생시킵니다.
                throw new HttpException("렌더링할 섹션을 찾을 수 없습니다.");
            }
            #endregion

            // 상단의 <script ...> 와</script> 항목을 삭제합니다.
            // 내용물은 삭제하지 않습니다.
            if (isRemoveScriptBlock)
            {
                renderString = Regex.Replace(renderString, @"<script[^>]*>", "");
                renderString = Regex.Replace(renderString, @"<\/script>", "");
            }

            // Jquery의 '$(document).ready(function () {' 항목과 끝 '});' 항목을 삭제합니다.
            if (isRemoveDocReady)
            {
                renderString = renderString.Replace(@"$(document).ready(function () {", "");
                int index = renderString.LastIndexOf(@"});");
                renderString = renderString.Substring(0, index);
            }

            HelperResult result = new HelperResult(__razor_helper_writer =>
            {
                // 아래 WriteLiteralTo 메서드를 써야 "나 '문자가 변환되지 않습니다.
                WriteLiteralTo(__razor_helper_writer, renderString);
            });

            return result;
        }
    }
}

Web.config 수정

Web.config 파일은 프로젝트 내에 2개가 존재하는데 여기서 수정하는 파일은 View폴더 밑에 있는 파일입니다. 내용 중에 <system.web.webPages.razor>노드 하위에 <pages>노드를 찾아 다음과 같이 수정해 줍니다.

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <configSections>
    <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
      <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
      <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    </sectionGroup>
  </configSections>
  
  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.1, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <!--<pages pageBaseType="System.Web.Mvc.WebViewPage">-->
    <pages pageBaseType="네임스페이스 명.WebViewPageHandler">  <!-- 이 부분을 상황에 맞게 수정합니다. -->
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

_layout.cshtml 파일의 수정

_layout.cshtml에서 위에 overload된 RenderSection을 사용하는 부분입니다.

<!DOCTYPE html>
<html>
<head>
    <title>홈페이지에 오신걸 환영합니다.</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" lang="ko" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />

    <link href="@Url.Content("~/Content/layout.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/jquery-ui/themes/flick/jquery-ui-1.10.4.flick.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/menu_top.css")" rel="stylesheet" type="text/css" />
    @if (IsSectionDefined("PageDefineCssBlock")) { @RenderSection("PageDefineCssBlock") }
                                                                                        
    <script src="@Url.Content("~/Scripts/jquery-1.10.2.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery-ui-1.10.4.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/menu_top.js")" type="text/javascript"></script>
    @if (IsSectionDefined("PageDefineScriptBlock")) { @RenderSection("PageDefineScriptBlock") }
       
    <script type="text/javascript">
        $(document).ready(function () {
            $('#logOut').button().css('font-size', '0.8em');
            @RenderSection("PageDefineDocReadyBlock", false, true, true)   // overload된 RenderSection
        });

        @RenderSection("PageDefineFunctionBlock", false, true, false)    // overload된 RenderSection
    </script>
</head>

<body>

....

아래는 View 페이지 내역으로 PageDefineDocReadyBlock블럭에서는 위에서 구현한 부분과 같이 <script ...>부터 $(document).ready(function () {, </script> 부분이 제거된 상태로 _layout.cshtml파일에 삽입됩니다. 또한 PageDefineFunctionBlock블럭에서는 <script ...>, </script> 부분이 제거되어 삽입 됩니다.

....
@{ 
    Layout = "~/Views/Shared/_Layout.cshtml"; 
}

@*@section PageDefineScriptBlock { }*@

@section PageDefineCssBlock {
    <!-- 페이지 선언 CSS Block -->
    <link href="@Url.Content("~/Content/login.css")" rel="stylesheet" type="text/css" /> 
}

@section PageDefineDocReadyBlock {
    <script type="text/javascript">
        $(document).ready(function () {
            $("#Id_user").focus();
            $("#login_form").submit(function () {
                var inputId = $("#Id_user").val();
                var inputPw = $("#Pw_user").val();

                if (inputId.length === 0) {
                    alert("ID를 입력해주세요.");
                    $("#Id_user").focus();
                    return false;
                }

                if (inputPw.length === 0) {
                    alert("비밀번호를 입력해주세요.");
                    $("#Pw_user").focus();
                    return false;
                }
            });
        });
    </script>
}

@section PageDefineFunctionBlock {
    <script type="text/javascript">
        function test() {
            alert('test function입니다.');
        }
    </script>
}
....

마지막으로 실제 실행 된 HTML의 소스 부분입니다.

<!DOCTYPE html>
<html>
<head>
    <title>홈페이지에 오신걸 환영합니다.</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" lang="ko" />
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />

    <link href="/Content/jquery-ui/themes/flick/jquery-ui-1.10.4.flick.css" rel="stylesheet" type="text/css" />

    <!-- 페이지 선언 CSS Block -->
    <link href="/Content/login.css" rel="stylesheet" type="text/css" /> 
                                                                                        
    <script src="/Scripts/jquery-1.10.2.js" type="text/javascript"></script>
    <script src="/Scripts/jquery-ui-1.10.4.js" type="text/javascript"></script>
                                                                                              
    <script type="text/javascript">
        $(document).ready(function () {
            
    
        
            $("#Id_user").focus();
            $("#login_form").submit(function () {
                var inputId = $("#Id_user").val();
                var inputPw = $("#Pw_user").val();

                if (inputId.length === 0) {
                    alert("ID를 입력해주세요.");
                    $("#Id_user").focus();
                    return false;
                }

                if (inputPw.length === 0) {
                    alert("비밀번호를 입력해주세요.");
                    $("#Pw_user").focus();
                    return false;
                }
            });
        
        });

         
    
        function test() {
            alert('test function입니다.');
        }
    

    </script>
</head>

<body>
    <!-- Content Start -->

...

참고 페이지

아래 작성한 내용은 Windows에 Tomcat을 서비스로 등록하여 구동하는 것을 전제로 구성된 내용입니다.

1.Apache Tomcat 내려받기

우선 아래 그림 처럼 아파치 톰캣 홈 페이지에서 Binary 형태(.zip)의 Tomcat을 내려받습니다.


Windows 환경에서는 '32-bit Windows zip'(또는 64-bit Windows zip)항목을 클릭해서 내려받기 합니다.
(그 외의 항목에서는 Windows Service에 등록하는 배치파일이 없을 수 있습니다.)

2.Apache Tomcat 설치

거창하게 설치라고 했습니다만 ... Binary 형태(.zip)로 내려 받았기 때문에 설치라는 행위를 할 필요는 없습니다. 내려받은 파일을 적절한 위치(원하는 위치)에 압축을 푼 뒤, 압축을 풀어 나온 파일(폴더)를 복사하고, 이름을 변경하여 붙여넣기 합니다.

폴더 명은 용도에 따라서 적절하게 변경하면 되나 가급적 한글은 사용하지 않는 것이 좋습니다. 글에서는 'tomcat1', 'tomcat2'로 구분해서 사용합니다.

3.설정 변경

3.1.tomcat2항목의 conf디렉터리 파일 중 server.xml파일을 열어 port항목이 존재하는 부분의 내용을 다르게 분리해 줍니다.

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
 
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
 
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"  redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>
 
      <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
 
    </Engine>
  </Service>
</Server>

위 코드에 기본적으로 설정 된 prot, redirectPort 부분은 tomcat1을 구동하면 사용되는 포트이므로 수정 없이 tomcat2를 실행하면 포트 충돌이 발생하거나 tomcat2 종료 시 tomcat1까지 함께 종료되는 등의 문제가 발생할 수 있습니다. 때문에 강조된 부분을 모두 변경해 주어야 합니다. 예를 들어 port="8005"port="8100"와 같이 변경 해 주어야 하며, 'Connector' 항목에 설정 된 포트의 경우 방화벽을 사용하고 있다면 방화벽 예외 항목에 포함되어야 합니다. (필요하다면 tomcat1, tomcat2모두 변경해서 사용해도 상관없습니다.)


추가로 위 내용 중 protocol="AJP/1.3" 항목의 redirectPort="8443" 항목의 포트는 바로 윗 줄의 redirectPort="8443" 항목의 포트 번호와 같아야 합니다.
(통상적으로 tomcat만 사용하여 웹서버를 구성하는 것이라면 주석으로 처리해 두어도 무방합니다.)

3.2.tomcat2폴더의 bin디렉터리의 파일 중 service.bat파일을 찾아 메모장 등 에디터로 열러 수정합니다.

...
rem Set Default Service name
set SERVICE_NAME=TOMCAT ...
set PR_DISPLAYNAME=Apache Tomcat ...
...

파일의 내용 중에 SERVICE_NAME, PR_DISPLAYNAME항목을 적절하게 변경합니다. 변경 시 주의할 부분은 SERVICE_NAME항목에 띄어쓰기나 '_'(언더바)가 들어가면 서비스 등록 시 오류가 발생할 수도 있고, 서비스 명이 정상적으로 표시되지 않을 수도 있습니다.

4.서비스 등록 및 확인

4.1.서비스 등록

3번 항목까지 모두 완료되면 커멘드(CMD)창을 열어 tomcat2bin디렉터리로 이동합니다. 해당 디렉터리에서 아래와 같이 명령을 실행하여 서비스에 등록합니다.

$prompt tomcat2\bin>service.bat install


4.2.서비스 등록 확인

Windows의 관리도구 > 서비스창을 띄워서 3.2에서 설정한 SERVICE_NAME에 맞는 항목이 등록되었는지 확인하고, 등록되었다면 상황에 맞게 설정 및 시작을 진행합니다.


5.실행 확인

브라우저를 띄워 3.1항목에서 설정한 포트에 맞게 호출하여 정상적으로 실행되고 있는지 확인합니다. 정상적으로 실행되고 있다면 아래와 같은 화면을 확인할 수 있습니다.




IIS 7.0 이상에서 aspx를 구동 시 다음과 같은 에러가 나는 경우가 있습니다.

HTTP 오류 500.23 - Internal Server Error
관리되는 통합 파이프라인 모드에 적용되지 않는 ASP.NET 설정이 있습니다.
이 경우에는 해당 페이지에 해결방법이 자세히 나와 있는데
간단히 정리해서
 
1. [ 시작 >  제어판 > 프로그램 및 기능 > Windows 기능 사용/사용 안 함 ]
   항목을 선택한 후 웹관리 도구 중 IIS 6 관리 호환성을 모두 선택합니다. ( 또는 서버관리자에서 웹서버 역할부분에서 확인, 추가 할 수 있습니다. )

2. 그래도 계속 아래 메시지가 난다면

An ASP.NET setting has been detected that does not apply in the Integrated managed pipeline mode

cmd 창에서 아래 명령을 실행하면 해결됩니다.
%SystemRoot%\system32\inetsrv\appcmd migrate config "Default Web Site/"


보통 Windows Server 2008의 경우엔 Oracle에서 제공하는 설치파일을 다운받아 설치하면 문제가 되지 않습니다. 다만 Windows Server 2008 R2 및 Windows 7의 경우에는 설치가 되지 않습니다. 물론 호환성 적용 등을 해도 설치되지 않습니다.
10g Oracle Installer가 인식하는 OS의 버전은 4.0, 5.0, 6.0까지인데, Windows Server 2008 R2 및 Windows 7은 6.1로 인식하기 때문입니다. 이런 경우엔 11g Client를 설치하면 문제가 없습니다만, 부득이하게 10g Client를 설치해야하는 경우를 위한 설치 방법을 적어둡니다.

이 글에서 설명하는 Oracle Client의 버전은 10.2.0.4기준입니다.
10.2.0.4 다운로드 경로
http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html

1. 위에 존재하는 링크를 따라 Oracle Client를 다운받아 압축을 적절히 풀어놓습니다.
2. 다음과 같은 경로에 3개의 파일을 수정해주면 설치가 가능합니다.

..\client\install\oraparam.ini
..\client\stage\prereq\client\refhost.xml
..\client\stage\prereq\client_prereqs\client\refhost.xml

3. oraparam.ini의 경우 중간에
[Certified Versions]
#You can customise error message shown for failure, provide value for CERTIFIED_VERSION_FAILURE_MESSAGE
Windows=5.0,5.1,5.2,6.0

와 같은 구문을 찾을 수 있는데 Windows=5.0,5.1,5.2,6.0,6.1와 같이 6.1을 추가해 줍니다.

4. 2개의 refhost.xml파일의 경우
<OPERATING_SYSTEM>
<VERSION VALUE="6.0"/>
</OPERATING_SYSTEM>

이란 구문 아래 동일한 구성으로 <VERSION VALUE="6.1"/>을 추가해 줍니다. 위와 같이 처리한 후 setup.exe를 실행하면 유효성 검사항목을 통과하며 설치가 됩니다.

원격 제어 환경이나 MS 서버 환경에서 메일 체크할 땐 필요한 방법인데... 자주 사용하지 않는 내용이다보니 금방 잊어버리네요...

아래 내용은 MS SMTP를 사용/기준으로 작성한 내용입니다.

커멘드 창에 아래처럼 입력하면

$\> telnet Mail\_Server\_IP\_Address 25(PORT)

화면이 전화면 되면서 SMTP 환영 메시지를 확인할 수 있습니다. 그리고 아래와 같이 메일을 발송합니다.

220 xxxxx Microsoft ESMTP MAIL Service, Version: x.x.x ready as 요일, 날짜, 월 년도, 시간 

helo me                      -> 정상적으로 접속이 되었는지 확인
mail from:                   -> 발송자 메일주소
rcpt to:                        -> 수신자 메일주소
data
subject:                       -> 제목

-> 내용적기

.                                  -> 마지막 종료 문자

1. 묶고 풀기 ( tar )

☞ tar 명령은 파일을 묶는 명령이다. 

원래는 백업을 위해 테이프백업 장치인 DAT  Device 에 백업을 할 때 쓰는 명령이었지만, 요즘은 파일의 압축이나 묶기에 더 많이 쓰이는 편이다. 

압축명령에는 compress 나 gzip 이 따로 있지만 tar 명령으로 통합해서 쓰면 편리하고 좋다. 우선 파일을 묶는 방법에 대해 알아 본다.


- tar 명령으로 파일을 묶을 때는 주의해야 할 것이 있는데 바로 디렉토리 단위로 묶는 것이다. 공개 자료실등에서 tar 파일을 받아서 풀 경우 디렉토리 단위로 묶지 않은 파일이면 수십개의 파일이 풀려 나와 디렉토리가 엉망이 되니 신경을 써야 된다. 

- tar는 아카이브 안의 파일에 대한 소유권과 허가권을 그대로 가지고 있으며, 심볼릭 링크나 하드링크, 디렉토리 구조를 유지한다. 뒤에 나오지만 이런점 때문에 tar를 이용해서 시스템 내에서의 데이터의 이동이나 복사에 자주 사용된다. 


  • 묶기
    $ tar cvf 생성_파일명.tar 대상 파일명(또는 대상 디렉토리)
  • 풀기
    $ tar xvf 생성_파일명.tar 


ex) 

$ tar cvf doc.tar doc/

$ tar xvf doc.tar


☞ tar 파일의 종류는 다음과 같다.

  • *.tar       <-- 일반적으로 tar로만 묶었을 때
  • *.tar.Z     <-- compress 로 압축했을 때
  • *.tar.z     <-- gzip 으로 압축(gunzip으로 푼다)
  • *.tar.gz    <-- gzip 으로 압축(gunzip으로 푼다)
  • *.tgz       <-- gzip 으로 압축(gunzip으로 푼다) 


2. 압축, 압축풀기

☞ 압축은 compress 나 gzip 등의 명령을 쓰면 된다. 

  • compress를 이용한 압축(*.tar.Z)
    $ compress 대상파일명.tar
  • uncompress를 이용한 압출풀기
    $ uncompress 대상파일명.tar.Z 


  • gzip을 이용한 압출(*.tar.gz)
    $ gzip 대상파일명.tar 
  • gunzip을 이용한 압축풀기
    $ gunzip 대상파일명.tar.gz


- 각각의 경우에서 뒤의 확장자 .Z .gz가 붙고 떨어지는 것을 주의 깊게 봐야 한다. 

- 파일을 풀기 전에 tar 파일의 내용을 알고 싶을 때 직접 풀지 않고 내용을 보려면 -t 옵션을 주어 풀기를 test 할 수 있다.

ex)  $ tar tvf 대상 파일명.tar


3. 동시수행

☞ tar 명령으로 묶기와 압축을 동시에 수행할 수 있다. 

아래와 같이 tar 명령에서 z 옵션을 주면 된다. 하지만 사용되는 OS에 따라서 z옵션이 지원되지 않는 경우도 있다.


  • 동시 수행
    $ tar cvfz 생성파일명.tgz  대상파일/
    $ tar xvfz 대상파일명.tgz
    $ tar tvfz 대상파일명.tgz


  • 파이프를 이용한 압축 (*.tgz)
    $ tar cvf - 대상파일/ | gzip > 생성파일명.tgz 
  • 파이프를 이용한 압축풀기
    $ gunzip -c 대상파일명.tgz | tar xvf - 


  • 묶어서 디렉토리 옮긴 후 풀기 
    $ tar cvf - 대상파일명 | ( cd 디렉토리명 ; tar xvf - ) 

1. library 추가

- version 1.1 이상 필요

- commons-fileupload.jar

- commons-io.jar


2. Bean 주입 ( applicationContext.xml )

-[bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" /]

- 이부분에 대한 추가적인 설정이나 설명은 각자 찾아보길 바란다.


3. Source part

// 파일 첨부를 위해 추가 import 구문

import org.springframework.util.FileCopyUtils;

import org.springframework.web.multipart.MultipartFile;

import org.springframework.web.multipart.MultipartHttpServletRequest;

import org.springframework.web.multipart.commons.CommonsMultipartResolver;


public class ActionClassName extends MappingDispatchActionSupport {


       private CommonsMultipartResolver cResolver;

       // Multipartrequest를 컨트롤하는 객체를 주입받는다.

       cResolver = (CommonsMultipartResolver) getWebApplicationContext().getBean("multipartResolver");    

       

       public ActionForward adminGoodStoreSave(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {


        MultipartHttpServletRequest mrequest = cResolver.resolveMultipart(request);

        String realPath      = request.getSession().getServletContext().getRealPath("/");    // 실제 서버의 주소는 이런식으로 가져올 수도 있다.

        String uploadPath    = realPath + customPath;


          // Multipartrequest 에서 파일 객체부분을 가저온다.

        Map fileMap = mrequest.getFileMap();


        for (Iterator it=fileMap.entrySet().iterator(); it.hasNext();) {

            Entry entry      = (Entry)it.next();

            MultipartFile mf = (MultipartFile)entry.getValue();


            if (!mf.isEmpty()) {

                if(mf.getSize() > 0){

       

                    // 원본 파일명

                    String originalFilename = mf.getOriginalFilename();

                    // 새로운 파일명은 시간(밀리세컨드까지)으로 변경하고 

                    // 확장자만을 갖는다.

                    String newFilename =  System.currentTimeMillis() + originalFilename.substring(originalFilename.length() -4, originalFilename.length());;


                    File fOri = new File(uploadPath, newFilename);


                    // 실제 파일을 작성한다.

                    InputStream is = mf.getInputStream();

                    OutputStream os = new FileOutputStream(fOri);

                    FileCopyUtils.copy(is, os);


                    /* 

                     * 여기에서 파일 필드를 구분하여 처리를 한다.

                     * mf.getName() 또는 entry.getKey().toString() 으로 

                     * input Tag의 name을 확인할 수 있다.

                     */

                    if"file1".equals(mf.getName()) ) {

                            /* 파일 필드1에 대한 처리 구역 */

                    } else if"file2".equals(mf.getName()) ) {

                            /* 파일 필드2에 대한 처리 구역 */

                    } else if"file3".equals(mf.getName()) ) {

                            /* 파일 필드3에 대한 처리 구역 */

                    }


                    os.close();

                    is.close();


                }

            }

        }

        return mapping.findForward(Constants.SUCCESS_KEY);

    }

       

}

       

       

4. JSP Tag part

<html>

<head>

</head>

<body>

....

<tr>

<td align="center" width="100">첨부1</td>

<td colspan="3"><input type="file" name="file1" id="file1"></td>

</tr>

<tr>

<td align="center" width="100">첨부2</td>

<td colspan="3"><input type="file" name="file2" id="file2"></td>

</tr>

<tr>

<td class="rowtitle" align="center" width="100">첨부3</td>

<td colspan="3"><input type="file" name="file3" id="file3"></td>

</tr>

...

</body>

</html>


Character-set이 UTF-8인 환경에서 EUC-KR환경으로 submit을 했을 때 한글이 깨져서 전송이 되는 문제로
JAVA 환경인 경우에는 통상적으로 전송 받는 쪽에서

request.setCharacterEncoding("euc-kr");   
또는 
String Variable_Name = new String( Input_Variable_Name.getBytes("UTF-8"),"EUC-KR") ); 

위와 같은 형식으로 한글에 대한 처리를 해주고는 합니다. 허나 이 방식은 JAVA에서 브라우저에 Data를 바인딩 하는 경우에는 원할하게 처리가 되었습니다만, 브라우저를 이용하는 사용자가 직접 입력한 값이나 Hidden 필드에 Script로 값을 넣어 전송 등, 위와 같은 처리를 해주어도 올바른 값을 전송받지 못하는 경우가 발생 할 수 있습니다. 특히 한글은 항상 깨지더군요.

확인한 방법으로는 Javascript로 Submit을 제어하는 경우 전송이 이루어지는 submit();구문 직전에 아래와 같은 구문을 추가해 주면 문제가 해결되었습니다. submit 직전에 브라우저 document의 설정을 변경하는 방법입니다.

// EUC-KR로 변경했던 Character-set을 폼 전송 후 원래의 Character-set으로 설정하기 위해
// 원래의 Character-set 정보를 변수에 담아 둡니다.
var defCharset = document.charset;    

if (/MSIE/.test(navigator.userAgent)) {
    document.charset = 'EUC-KR';
} else {
    form_name.acceptCharset = 'EUC-KR';
}

form_name.submit();
document.charset = defCharset;
요즘은 cvs보다 svn쪽을 더 선호하는 추세이기는 하지만...
아직 회사에서도 cvs를 사용하는 부분이 있다....

새로운 계정을 추가하고 비밀 번호를 생성해서 passwd파일에 넣어주는 일이 어렵지는 않지만...
어느정도 번거로운 것도 있고... 특히 자주 사용하지 않아서 쉽게 잊어 버린다...
웹을 검색하면서 apache나 perl을 이용해서 암호화된 비밀 번호를 생성하는 것을 공부하다 보니
Windows에서 perl을 이용해서 쉽게 계정을 생성하는 것을 해보려한다.

우선 http://strawberryperl.com/에 가서 원하는 버전을 받는다.
5.10.x.x버전은 아직 버그가 많이 존재한다고 해서 strawberry-perl-5.8.9.2버전의 바이너리 타입을 받았다.
(설치하는 걸 그다지 좋아하지 않기도 하지만... 대부분 바이너리로 실행이 가능하다면 포터블처럼 들고다닐 수 있기에 선호하는 편이다. )

적당한(원하는) 위치에 압축을 풀고,
[strawberry-perl-5.8.9.2\perl\bin\]을 살펴보면 perl.exe 파일을 확인할 수 있다.
perl.exe는 cmd 창에서 실행이 가능하다.
(전역으로 perl.exe를 사용하고 싶다면 환경변수[Path]에 해당 경로를 넣어주면 된다. 아래는 버전확인 부분)

perl의 설치는 이것으로 완료되었다. 이제 cvs에서 사용할 비밀번호 생성 스크립트를 작성해 보자.
이부분은 잘 모르는 부분이라서 아래 출처에서 참고하였으며, 자세한 설명이 되어있는 곳이니 한번쯤은 읽어보기 바란다.

출처 : http://wiki.kldp.org/wiki.php/DocbookSgml/CVS_Tutorial-KLDP  (+ 항목 3.2.2. )

파일명 : cvspasswdgen.pl

#!D:\04.Programs\strawberry-perl-5.8.9.2\perl\bin                           <- perl이 설치된 bin 디렉터리

($u, $p)=@ARGV;
@d=(A..Z,a..z);
$s=$d[rand(52)].$d[rand52];
print $u.":".crypt($p, $s).":cvs\n";


위의 내용을 작성하여 [\strawberry-perl-5.8.9.2\perl] 디렉터리에 저장한다.
( 다른 디렉터리에 저장을 해도 무관하지만 굳이 perl 폴더에 저장하는 이유는 아래의 .bat파일에서 확인할 수 있다. )

위의 cvspasswdgen.pl 만으로도 실행을 할 수는 있지만... perl 의 실행경로를 환경변수에 넣어주지 않았다면 아래 화면과 같이 매번 perl.exe파일의 실행경로를 적어주어야 하는 번거로움이 있다...

필자는 환경변수에 추가해서 사용하는 걸 별로 좋아하지 않다보니 너무 번거로웠다. 때문에 번거로움을 해결하기 위해서 cvsgen.bat라는 파일을 만들어봤다.
(많은 삽질을 했지만.. 덕분에 번거로움이 많이 줄어든 것 만으로도 뿌듯하다.^^;;;;)

파일명 : cvsgen.bat

@echo off
rem ========================================================
rem 아이디 값 입력
rem ========================================================
:select_id
@echo Enter your id
@echo off
set id=
set /p id=

rem 빈 값에 대해 다시 입력처리
if "%id%" == "" goto select_id

rem 한줄 띄우기
@echo.

rem ========================================================
rem 암호 값 입력
rem ========================================================
:select_pw
@echo Enter your passward : 
@echo off
set pw=
set /p pw=

rem 빈 값에 대해 다시 입력처리
if "%pw%" == "" goto select_pw

rem 한줄 띄우기
@echo.

rem ========================================================
rem 이부분의 %cd%값은 현재 실행되고 있는 bat파일의
rem 위치가 된다. 만약 실행 위치를 변경하려면 이곳을 수정한다.
rem ========================================================
%cd%/bin/perl.exe cvspasswdgen.pl %id% %pw%

rem 아래 명령어를 사용하면 실행이 완료되어도 창이 닫히지 않고 그대로 유지된다.
pause > nul

:end


cvsgen.bat 파일은 [\strawberry-perl-5.8.9.2\perl\]위치에 저장해두고 파일을 실행해 보면 아래와 같은 화면을 볼 수 있다.

해당부분을 복사해서 passwd파일에 넣으면 끝~!!
개발을 하다보면 프로그램이 종료되지 않아서.. 작업관리자에서 강제로 프로세스를 종료해야하는 일이 종종 생기는 것 같다... 거기다 노트북인지라... 프로세스 갯수는 거의 80여개에 가까운 숫자가 된다....

프로세스를 보다보니 대체 무엇에 쓰이는 항목들인지..
이래저래 찾아보고 몇가지만 정리해 본다.

-----------------------------------------------------------------------------------------------------
프로세스명 : System 
 -> 쓰레드의 시작점 프로세스

프로세스명 : system idle process 
 -> 작업관리자에서 system idle process은 cpu의 여유로운 상태를 말해준다. 
    현재 사용할 수 있는 남은 자원을 %로 말한 것으로 아무작업을 안하고 있을때는 99%가 됩니다.

프로세스명 : taskmgr.exe 
 -> Task Manager 즉, 작업 관리자를 띄우면 생성된다.
 -> 실행파일경로 : C:\WINDOWS\system32\taskmgr.exe

프로세스명 :  wuauclt.exe
 -> Windows Update AutoUpdate Client는 마이크로소프트 윈도우 자동업데이트를 도와주는 백그라운드 프로그램
 -> 실행파일경로 : C:\WINDOWS\system32\wuauclt.exe or \system32\dllcache\wuauclt.exe 

프로세스명 : conime.exe
 -> Consol IME 명령 프롬프트에서 다국어를 지원하도록 하기 위해 사용하는 프로그램.
    영어만 사용하지 않는 이상 반드시 필요한 프로그램. 단, conimekr.exe일경우 conimekr.exe는 광고 프로그램.
 -> 실행파일경로 : C:\WINDOWS\system32\conime.exe

프로세스명 : mmc.exe
 -> Microsoft Management Console application으로 컴퓨터 관리창을 실행했을 경우 나타난다.
 -> 실행파일경로 : C:\WINDOWS\system32\mmc.exe

프로세스명 : wmiprvse.exe
 -> 윈도우 미디어 플레이어 10을 설치하면 생성되는 파일이며, 단 wmiprvsw.exe는 Sasser worm이다. wmiprvsw.exe이라면 제거해야한다. 
 -> 실행파일경로 : C:\WINDOWS\system32\dllcache\wmiprvse.exe or \wbem\wmiprvse.exe

작업이름 : Winlogon.exe
 -> 사용자 로그인/로그오프를 담당하는 프로세스, 윈도우의 시작/종료시, Ctrl+Alt+Del
 -> 실행파일경로 : C:\WINDOWS\system32\Winlogon.exe

프로세스명 : smss.exe
 -> Session Manager SubSystem, 사용자 세션을 시작하는 기능을 담당. Winlogon, Win32(Csrss.exe)을 구동시키고, 시스템 변수를 설정. 
    Winlogon이나 Csrss가 끝날때, 정상적인 Winlogon/Csrss 종료시 시스템을 종료시키며, 비정상적인 Winlogon/Csrss 종료시, 시스템이 멎는 상태가 된다
 -> 실행파일경로 : C:\WINDOWS\system32\smss.exe

프로세스명 : csrss.exe
 -> Client/Server Runtime SubSystem, 윈도우 콘솔을 관장, 쓰레드를 생성/삭제하며, 16bit 가상 MS-DOS 모드를 지원. 
    이 프로세서는 작업관리자로 제거 할 수 없다.
 -> 실행파일경로 : C:\WINDOWS\system32\csrss.exe

프로세스명 : Lsass.exe
 -> Local Security Authentication Server, Winlogon 서비스에 필요한 인증 프로세스를 담당
 -> 실행파일경로 : C:\WINDOWS\system32\Lsass.exe

프로세스명 : winmgmt.exe
 -> 클라이언트 관리 요소, 윈도우를 정상적으로 운영하는데 있어 필수적인 프로세스, 종료시 시스템 다운및 다른 부작용유발됨
 -> 실행파일경로 : C:\WINDOWS\system32\wbem\winmgmt.exe

프로세스명 : MDM.EXE
 -> MDM.EXE(Machine Debug Manager) Office 2000과 Windows NT Option Pack / Developer Studio등에 의해서 설치됨 - 일반적으로 컴퓨터성능에 영향을 미침
 -> 실행파일경로 : C:\WINDOWS\system32\MDM.EXE

라디오 버튼을 동적으로 생성하기 위해 만들었던 jQuery인데...
DWR과 같이 사용을 한터라.... 범용으로 사용하기에는 무리가 있을 수 있다.
음...데이터 형태만 맞추면 가능할지도....^^;;

/************************************************************************************
' function명: addRadioMap
' 내용      : 라디오버튼 동적으로 생성.
'             jQuery.js(core)에 삽입
'             strGrpName = 라디오버튼 그룹명.
'             data = [{key:'string', value:'string'},{key:'string', value:'string'},{key:'string', value:'string'}] 형식
'             strKey = Map데이터의 key
'             strValueKey = Map데이터의 value key
*************************************************************************************/
addRadioMap: function ( strGrpName, data, strKey, strValueKey )  {
    var arrText = [];
    for( var iCnt in data ){
      arrText[iCnt] = "&nbsp;<input type='radio' name='"+strGrpName
                    +"' id='"+data[iCnt][strKey]+"'value='"+data[iCnt][strValueKey]+"'>"+data[iCnt][strValueKey];
    }

    // 해당 테그의 압쪽에 넣는다.
    this.prepend(arrText.join(' '));    //or this.html(arrText.join(''));
프로젝트를 진행하면서 새로운 사실을 발견했다~

java에서 String 타입의 null 값 더하기 String 타입의 null은 String 타입 nullnull이 된다....
찾아본 결과 아래의 글을 찾아볼 수 있었는데...
String 타입의 편의성을 위해 그렇게 된다고 하는데....

자바를 오랜 기간 다루었던 사람들은 알지도 모르는 것이지만 
다룬 기간이 짧은 나로서는 새로우면서도 조금은 쏘킹했다.

오오 널널~

===========================================================================================

[ null String value in Java ] null String problem


자바에서 String 을 다룰때 조심해야할 사항들이 있다.

junit으로 테스트 하면 다음의 테스트 클래스는 성공적으로 실행된다.

public class Test_NullString extends TestCase {
    
    public void testNullString () {
        String nullString = null;
        String nullString2 = null;
        assertNull(nullString); // null 이다.
        nullString += "?";
        assertEquals("null?", nullString);
        assertEquals("nullnull", nullString + nullString );   
}

    
    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }
    public void testSameObject() {
        assertFalse(new TestObj() == new TestObj());
    }
    private String getJane(){ return "Hi";}
    private String getTom() { return "Hi";}
    
    private class TestObj{
        public boolean equals(Object obj) {
            if ( obj instanceof TestObj ) return true;
            else return false;
        }
    }
}

testNullString() 에서 볼 수 있듯이 null 로 초기화된 string의 경우 이어지는 string 값을 더할 경우 + 연산에 의해서 "null" 이 붙게 된다. null 로 초기화된 두개의 string 을 더할때에도 마찬가지로 그 결과는 "nullnull" 이 된다.

이런 결과는 null 에 대한 보통의 상식과 전혀 다른 결과이다. null 인 스트링과 null 이 아닌 스트링을 더했을때 당연히 null 이 아닌 스트링만 나오기를 기대했었는데 의외의 결과...

왜 일반의 통념과 다르게 
null에 대한 + 연산을 이렇게 구현해야 했을까?

우선 + 연산이 스트링에 대해서 아주 빈번하게 사용된다는 점을 들 수 있는데, + 연산이 구체적으로 어떻게 실행되는지 api 의 코드에는 나와있지 않아서 알 수 없지만 내부적으로 StringBuffer 와 동일한 규칙이 적용되고 있다고 짐작할 따름이다.(StringBuffer의 경우도 append( null ); 이 들이오면 "null" 을 붙이도록 구현되어 있다. 뭐냐 이거.. -_-;;)

만일 일반의 생각대로 ((String)null) + "a" 의 결과가 "a" 라고 하자.

그러면 결과는 "a"가 나올 수 없는데, null 이기 때문에 NullPointerException 이 던져져야 한다. null 로 초기화된 객체는 아무런 연산 행위도 갖지 못한다. 무의식 중에 스트링에 대해서 + 연산을 실행하지만 이런 편의를 위해서 위와같이 null 스트링을 처리하는 것이 아닌가...짐작할 뿐이다.

예를 들어 ((Object)null) + ((Object)null) 을 하면 컴파일 에러가 던져진다. Object 형에 대해서는 + 연산이 정의되어있지 않기 때문... 결국 String 객체에 + 연산을 적용하기 위해서 null에 대해 특별한 처리를 한 것 같다.

두번째 예제도 나름 흥미롭다.

    public void testSameString() {
        assertTrue("home" == "home");
        assertFalse("home" == new String("home"));
        assertFalse( new String("home")== new String("home"));
        assertTrue(getJane() == getTom());
    }

보통 == 와 equals(Object o) 에 대한 닳고 닳은 설명들과 달리 위 테스트도 모두 성공한다. 첫번째와 달리 두번째, 세번째 테스트가 false로 나오는 것에 주목할 필요가 있다.

자바소스 코드 내에서 "...." 로 둘러싸인 부분들은 모두 ConstantPool에 저장된다. ConstantPool이란 자바 클래스 파일에 상수값들만 따로 뽑아놓은 값들인데 동일한 문자열들은 모두 하나의 스트링 값으로 참조되므로 첫번째 "home" == "home"은 true가 된다.(동일한 물리 주소를 참조)

두번째 세번째 assert 에서 new 로 새로운 스트링객체를 만드는데 이것들은 ConstantPool에 있는 스트링을 통해서 새로 만들어진 객체라는게 중요하다.(서로 다른 물리주소를 참조하므로 false이다.)

상식과 어긋나 보이는 이런 규칙들은 대부분의 경우 편리하고 좋지만 안좋을 때도 있다. 일례로 이 글을 쓰게 된 것도 Servlet 에서 getQueryString() 이 반환하는 null 값이 예상치 못한 행동을 하고(위에 설명한...) 그 이유를 알아보다 위와같은 테스트까지 하게 된 것이다.

어찌보면 null의 문제라기보다는 null을 반환하도록 코딩된 서블릿의 구현이 문제가 있는게 아닌가 싶다. 사실 모든 메소드가 인스턴스를 반환할때 null을 피하는 것이 좋다.특히 + 연산이 허용된 java.lang.String 의 경우는 더욱 그러하다. 하지만 스펙에는 아주 용감하게 "쿼리 스트링이 없을 경우 null 을 반환한다" 라고 쓰여져 있다.

덕분에 두시간 넘게 헤매고 다녔다. -_-;;

웬만하면 스트링을 초기화할때는 null 이 아니라 "" 로 초기화해야한다.

           String query = "":

물론 String을 반환하는 메소드의 경우 ""과 null 이 서로 다른 의미를 갖는게 아니라면 무조건!!! "" 을 반환하도록 하는게 옳다고 생각한다. 
예전에 테이블 컬럼 정보 조회 쿼리를 올린적이 있었는데....
이리저리 도움을 받아서 다시 정리했다.

음.. 이번거는 목적이 '테이블 정의서'라서.... 
조금 애매하기는 하다.

아래의 쿼리문을 그대로 돌리면 해당 유저의 스키마 테이블을 모두 조회한다.
아참.. 오라클 실행계획을 보면 LOOP가 굉장히 많고 시간도 조금 걸리는 문장이란걸 유념하자.
그리고 좀 길다...쩝...

SELECT (CASE WHEN MAIN.COLUMN_ID = 1 THEN MAIN.TABLE_COMMENTS 
                       ELSE '' END)                                                                             table_kor_name
           , (CASE WHEN MAIN.COLUMN_ID = 1 THEN MAIN.TABLE_NAME 
                        ELSE '' END )                                                                           table_eng_name
           , MAIN.COLUMN_ID                                                                                column_id
           , MAIN.COLUMN_NAME                                                                          column_name
           , MAIN.COLUMN_COMMENTS                                                                 column_comments
           , (CASE SUB.CONSTRAINT_TYPE WHEN 'P' 
                                                             THEN 'PK' 
                                                             WHEN 'R' 
                                                             THEN 'FK ('||SUB.FK_COLUNM||')' END) key_flag
           , (CASE NVL(MAIN.NULL_FLAG, 'N') WHEN 'N' 
                                                                 THEN 'NOT NULL' 
                                                                 ELSE '' END)                                    null_flag
           , MAIN.DATA_TYPE                                                                                 data_type
           , (CASE MAIN.DATA_TYPE WHEN 'NUMBER' 
                                                   THEN (CASE WHEN MAIN.DATA_PRECISION IS NOT NULL 
                                                                       THEN TO_CHAR(MAIN.DATA_LENGTH)||' ('
                                                                                ||TO_CHAR(MAIN.DATA_PRECISION)||')'                                                                                     ELSE TO_CHAR(MAIN.DATA_LENGTH) END)
                                                   WHEN 'DATE'   
                                                   THEN '' 
                                                   ELSE TO_CHAR(MAIN.DATA_LENGTH) END)      data_type_length
   FROM (SELECT T_COMT.COMMENTS                                              table_comments
                         , T_COLS.TABLE_NAME                                             table_name
                         , C_COMT.COMMENTS                                             column_comments
                         , T_COLS.COLUMN_NAME                                         column_name
                         , (CASE T_COLS.NULLABLE WHEN 'Y' THEN 'Y' END) null_flag
                         , T_COLS.DATA_TYPE                                               data_type
                         , T_COLS.DATA_LENGTH                                           data_length
                         , T_COLS.COLUMN_ID                                               column_id
                         , T_COLS.DATA_PRECISION                                       data_precision
                 FROM USER_TAB_COLUMNS T_COLS
                         , USER_TAB_COMMENTS T_COMT
                         , USER_COL_COMMENTS C_COMT
                WHERE T_COLS.TABLE_NAME  = T_COMT.TABLE_NAME
                     AND T_COLS.TABLE_NAME  = C_COMT.TABLE_NAME
                     AND T_COLS.COLUMN_NAME = C_COMT.COLUMN_NAME
                     AND T_COMT.TABLE_TYPE  = 'TABLE'
             ) MAIN
           , (SELECT CONST.CONSTRAINT_TYPE    constraint_type
                         , CONST.TABLE_NAME             table_name
                         , COLS.COLUMN_NAME            column_name
                         , R_COLS.COLUMN_NAME         fk_colunm
                 FROM USER_CONSTRAINTS CONST
                         , USER_CONS_COLUMNS COLS
                         , USER_CONS_COLUMNS R_COLS
                WHERE CONST.CONSTRAINT_NAME   = COLS.CONSTRAINT_NAME
                    AND CONST.CONSTRAINT_TYPE IN ('P', 'R')
                    AND CONST.R_CONSTRAINT_NAME = R_COLS.CONSTRAINT_NAME(+)
             ) SUB
 WHERE MAIN.TABLE_NAME    = SUB.TABLE_NAME(+)
     AND MAIN.COLUMN_NAME   = SUB.COLUMN_NAME(+)
     --AND MAIN.TABLE_NAME  = UPPER('TABLE_NAMES')  /*특정 테이블 조회일 경우 사용.*/
 ORDER BY MAIN.TABLE_NAME, MAIN.COLUMN_ID
;


헉...이거 신경써서 오와 열을 맞췄더니만.... 
미리보기 하니 다 깨지네...쩝.. 몰라....
요즘은 웹 에디터를 사용하여 DB에 저장을 할 때 
그냥 통째로 HTML 태그와 함께 저장을 하는 경우가 많아지는 것 같다...

그 내용을 대충 요약해서 보여줘야 하는데...
아무생각없이 조회된 내용을 몇 글자 내로 잘라서 보여 줬더니
그 속에 같이 저장된 태그들 덕에 욕먹을 뻔했던 기억이....

지금 적어놓는 건 JAVA 프로퍼티에서 걸러 줬을 때 사용하던 정규식이다..
(내가 이렇게 사용했다는 것뿐...)

참고로,
정규식은 javascript에서도 사용할 수 있다는거.....^^;;

==============================================================================================

// HTML 태그제거 정규식 적용
var_name = var_name.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>","").replaceAll("\r|\n|&nbsp;","");

// 위에 구문이 넘 길어서 쪼개 놓으면 요렇게...
//var_name = var_name.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>","");
//var_name = var_name.replaceAll("("\r|\n|&nbsp;","");

return var_name;
예전에 올렸던건데....
글 삭제되면서.... 같이 삭제했던 글....
크흑....

====================================================================================================
확인할 때는 당연히! DBA 계정으로 확인


SELECT U.TABLESPACE_NAME                                                  "테이블 스페이스"
           , U.BYTES / 1048576                                                         "크기(mb)"
          , (U.BYTES - SUM(NVL(F.BYTES,0))) / 1048576                   "사용됨(mb)"
          , (SUM(NVL(F.BYTES,0))) / 1048576                                    "남음(mb)"
          , TRUNC((SUM(NVL(F.BYTES,0)) / U.BYTES) * 100,2)          "남은 %"
          , U.FILE_NAME                                                                  "저장위치"
 
FROM DBA_FREE_SPACE F
          , DBA_DATA_FILES U
WHERE F.FILE_ID(+) = U.FILE_ID
GROUP BY U.TABLESPACE_NAME
               , U.FILE_NAME
               , U.BYTES
 
ORDER BY U.TABLESPACE_NAME

;


1. *.jsp 파일에서 엑셀로 열거나 저장하기 위해서 <head>부분에 삽입 필요.
2. 열기를 하면 브라우저 창에서 열림.
3. 각 컬럼 색상이나 모양등은 엑셀에서 제공하는 색상만이 지원되며 (웹 색상은 지원되지 않는듯....) 컬럼모양 역시 엑셀 서식을 기준으로 적용된다.
4. *.css파일을 참조할 수 있으며, 엑셀로 내려받는 것에는 문제가 없으나 엑셀파일을 열었을 경우 해당 파일이 없다며 오류가 발생.

=== 추가 부분 ===
<head>
<!-- 엑셀 파일이라는 걸 브라우저에 알려주는 부분 -->
<%@ page contentType="application/vnd.ms-excel; name='My_excel'; text/html; charset=euc-kr"%> 
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr" />
<title>Untitled Document</title>
<%
//해당 파일에 추가 설정
//저장을 눌렀을 때 저장 파일명이 myexcel 이 된다.
//지정해주지 않으면 기본적으로 *.jsp파일 명이 되는 듯.
response.setHeader("Content-Disposition", "attachment; filename=myexcel");
response.setHeader("Content-Description", "JSP Generated Data");   
%> 
</head>
전부터 테이블 정의서가 없어 고생한 적이 많았는데....
능통한 분의 도움을 받아 작성해놓은 쿼리문.
요즘은 이 쿼리를 사용하지 않길 바랄뿐이다.
하나하나 조회해서 정리하는 귀찬음이란....

=== v1.0 ===
select column_id
        , table_name
        , column_name
        , data_type ||'( '||data_precision||', '||data_scale||' )'
        , data_length
        , data_precision
        , data_scale
        , decode( nullable, 'Y', '', 'N', 'NOT NULL',  '')
   from USER_TAB_COLUMNS
where table_name = upper( 'TABLE_NAME' )
  order by column_id

=== v1.1 ===
select decode( a.column_id, '1' , a.table_name, '' ) as TableName
       , a.column_id as ID
       , a.column_name as ColumnName
       , b.comments as Comments
       , a.data_type ||'( '||a.data_precision||', '||a.data_scale||' )' as Types
       , a.data_length as Length
       , a.data_precision as Precision
       , a.data_scale as Scale
       , decode( a.nullable, 'Y', 'NULL', 'N', 'NOT NULL',  '') as NullYN
  from USER_TAB_COLUMNS a,
         ALL_COL_COMMENTS b
where a.table_name = upper( 'TABLE_NAME' )
 --and a.data_type = upper( 'Data_Type' )
    and a.table_name = b.table_name(+)
    and a.column_name = b.column_name(+)
  order by column_id

예전에 소수점 처리 때문에 꽤나 애먹었던 기억이 있다.
이리저리 검색도 해보고, 도움말도 보고....

<script language= "JavaScript">
// strValue : 처리하려는 값
// strType :  '* 소수점 계산. 0:절사, 1:반올림, 2:올림
function fnFixNum( strValue, strType)
{
    var iType =  parseInt( strType );
    switch ( type )
    {
        case 0 :  // 절사
                return( parseInt( strValue ) );
                break;

        case 1 :  // 반올림
                if( strValue< 0 ){  //음수일 경우
               
                    if( ( Math.abs( strValue ) + 0.5 )
                         - ( parseInt( Math.abs( strValue ) + 0.5 ) ) == 0 ){
                          return ( ( parseInt( Math.abs( strValue ) + 0.5 ) * -1 ) +1 );
                    }
                    else
                    {
                          return ( parseInt( Math.abs( strValue ) + 0.5 ) * -1 );
                    }

                } else { //양수 일 경우
                     return ( parseInt( strValue + 0.5 ) )
                }
                break;

        case 2 : //올림
                if( ( value - parseInt( strValue ) ) >= 0.1 ) {
                     return ( parseInt( strValue ) +1 );
                }
                if( ( value - parseInt( strValue ) ) <= -0.1 ){
                     return ( parseInt( strValue ) -1 ) ;
                }

        }//End switch
       
}//End function
</script>

javascript Trim() 함수

String.prototype.trim() = function()
{
      return this.replace(/(^ *)|( *$)/g, "");
}

String.prototype.ltrim() = function()
{
      return this.replace(/(^ *)/g, "");
}

String.prototype.rtrim() = function()
{
      return this.replace(/( *$)/g, "");
}

출처 : http://tong.nate.com/reizes/22327742

LOG4J

I. 들어가면서.. 그리고 log4j
log4j는 자바 어플리케이션에서 빠르고 효과적으로 로깅 할 수 있도록 도와주는 오픈 소스 프로젝트입니다. 로깅(logging)은 코드의 가독성을 떨어뜨리는 단점이 있지만 애플리케이션에 문제가 있을 때 개발자가 자세한 상황을 파악할 수 있도록 해 주며 테스팅시 빠질 수 없는 요소입니다. 아마도 여러분들은 여러 어플리케이션이 추가되면서 각 개발자들만의 독특한 로깅방식이 서로 썩이고 얽혀서 화면에 나타나는것을 많이 봤을겁니다 -_-; 즉 로깅방법을 통일할 필요가 있는것이죠. 모든 개발자가 특정 포맷에 맞추어서 로깅 한다면 한결 로깅하기도 편하겠지요. 오픈 소스 프로젝트인 Log4j는 개발자들이 매우 손쉽고 다양한 형태로 로깅을 할 수 있도록 도와줍니다. 성능또한 우수해 더이상 System.out.println을 사용할 필요가 없습니다.

II. 다운로드
다운로드 http://logging.apache.org/log4j/docs/download.html
매뉴얼
http://logging.apache.org/log4j/docs/documentation.html
API spec
http://logging.apache.org/log4j/docs/api/index.html

III. LOG4J 구조
① Logger(Category) : 로깅 메세지를 Appender에 전달합니다.
② Appender : 전달된 로깅 메세지를 파일에다 기록할 것인지, 콘솔에 출력할 것인지 아니면 DB에 저장할 것인지 매개체 역활을 합니다.
③ Layout : Appender가 어디에 출력할 것인지 결정했다면 어떤 형식으로 출력할 것이지 출력 layout을 결졍합니다.

IV. LOG4J 로깅 레벨
① FATAL : 가장 크리티컬한 에러가 일어 났을 때 사용합니다.
② ERROR : 일반 에러가 일어 났을 때 사용합니다.
③ WARN : 에러는 아니지만 주의할 필요가 있을 때 사용합니다.
④ INFO : 일반 정보를 나타낼 때 사용합니다.
⑤ DEBUG : 일반 정보를 상세히 나타낼 때 사용합니다.

만약 로깅 레벨을 WARN 으로 설정하였다면 그 이상 레벨만 로깅하게 됩니다.
즉 WARN, ERROR, FATAL 의 로깅이 됩니다.

V. 샘플코드 1

test.jsp</P><BR><P><%@pagecontentType="text/html;charset=MS949"
 import="org.apache.log4j.Logger" %>

<%!
 static Logger logger = Logger.getLogger("test.jsp");
%>

<%
logger.fatal("fatal!!");

logger.fatal("fatal2!!", new NullPointerException("널"));

logger.error("error!", new NumberFormatException());

logger.error("error!2");

logger.warn("warn");

logger.info("info");

logger.debug("debug");
%>


결과 콘솔화면








static Logger logger = Logger.getLogger("test.jsp");
static 메소드 getLogger를 통해 logger 인스턴스를 가져옵니다. getLogger에는 파라미터로 스트링 혹은 클래스를 사용하는데 jsp에서는 클래스를 파라미터로 주기에는 좀 애매합니다. 그냥 스트링으로 주도록 하지요

logger.fatal("fatal!!");
[ logger.fatal("fatal2!!", new NullPointerException("널입니다요")); ]
logger에 fatal 레벨의 메세지를 전달합니다. 다음 두가지 메소드를 지원하는군요

fatal(Object message) , fatal(Object message, Throwable t)

각 레벨마다 위처럼 두가지 메소드를 지원합니다.

지원 메쏘드
logger.fatal(Object message) logger.fatal(Object message, Throwable t)
logger.error(Object message) logger.error(Object message, Throwable t)
logger.warn(Object message) logger.warn(Object message, Throwable t)
logger.info(Object message) logger.info(Object message, Throwable t)
logger.debug(Object message) logger.debug(Object message, Throwable t)

VI. 샘플코드 2

TestServlet.java

import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class TestServlet extends HttpServlet {
    static Logger logger = Logger.getLogger(TestServlet.class);

    public void init(ServletConfig config) throws ServletException {
         super.init(config);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         try {
              ...
              logger.info("Hellow World~");
              ...
          } catch (Exception e) {
              logger.error("Error at TestServlet", e);
          }
     }
}



VII. LOG4J 설정
프로그램에서 설정

<TD><%@pagecontentType="text/html;charset=MS949"  import="org.apache.log4j.*,java.io.* " %>
<%!
 static Logger logger = Logger.getLogger("log4j.jsp");
%>

<%
String layout = "%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n";
String logfilename = "DailyLog.log";
String datePattern = ".yyyy-MM-dd ";

PatternLayout patternlayout = new PatternLayout(layout);
DailyRollingFileAppender appender = new DailyRollingFileAppender(patternlayout, logfilename, datePattern);
logger.addAppender(appender);
logger.setLevel(Level.INFO);
logger.fatal("fatal!!");
%>


property 파일에 설정
log4j.properties를 만들어 /WEB-INF/classes 밑에 놓으세요

log4j.rootLogger=INFO, stdout, rolling
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender
log4j.appender.rolling.File=output.log
log4j.appender.rolling.Append= true
log4j.appender.rolling.MaxFileSize=500KB
log4j.appender.rolling.DatePattern='.'yyyy-MM-dd
log4j.appender.rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#최상위 카테고리에 INFO로 레벨 설정 및 appender로 stdout, rolling을 정의
log4j.rootLogger= INFO, stdout, rolling

#stdout 어펜더는 콘솔에 뿌리겠다는 정의
log4j.appender.stdout=org.apache.log4j.ConsoleAppender

#stdout 어펜더는 patternlayout을 사용하겠다는 정의
log4j.appender.stdout.layout= org.apache.log4j.PatternLayout

#페턴은 다음과 같이 포맷팅 하겠다는 것을 정의
log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

#역시나 rolling 어펜더는 파일로 처리한다라고 정의
log4j.appender.rolling=org.apache.log4j.DailyRollingFileAppender

#로그 파일 이름은 output.log
log4j.appender.rolling.File=output.log

#true면 톰캣을 내렸다 올려도 파일이 리셋되지 않습니다.
log4j.appender.rolling.Append= true

#파일 최대 사이즈는 500KB로 설정
log4j.appender.rolling.MaxFileSize= 500KB

#파일 포맷은 output.log.2005-03-10 으로 관리하겠다고 정의
log4j.appender.rolling.DatePattern= '.'yyyy-MM-dd

#역시나 rolling 어펜더는 패턴 레이아웃을 사용하겠다고 정의
log4j.appender.rolling.layout= org.apache.log4j.PatternLayout

#rolling 어펜더는 패턴 레이아웃 포맷
log4j.appender.rolling.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

VIII. 설정 포맷
로그파일명 포맷 (DatePattern)
로그파일명 포맷입니다. 날짜, 시간 및 분단위로까지 로그 파일을 분리할 수 있습니다.

형식 설명
'.'yyyy-MM 매달 첫번째날에 로그파일을 변경합니다
'.'yyyy-ww 매주의 시작시 로그파일을 변경합니다.
'.'yyyy-MM-dd 매일 자정에 로그파일을 변경합니다.
'.'yyyy-MM-dd-a 자정과 정오에 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH 매 시간의 시작마다 로그파일을 변경합니다.
'.'yyyy-MM-dd-HH-mm 매분마다 로그파일을 변경합니다.

PatternLayout 포맷
로그자체를 어떤 포맷으로 남길지 결정합니다.
layout에는 HTMLLayout, PatternLayout, SimpleLayout, XMLLayout등이 있으며 PatternLayout이 일반적으로 가장 많이 쓰입니다.

형식 설명
%p debug, info, warn, error, fatal 등의 priority 가 출력된다.
%m 로그내용이 출력됩니다
%d 로깅 이벤트가 발생한 시간을 기록합니다.포맷은 %d{HH:mm:ss, SSS}, %d{yyyy MMM dd HH:mm:ss, SSS}같은 형태로 사용하며 SimpleDateFormat에 따른 포맷팅을 하면 된다
%t 로그이벤트가 발생된 쓰레드의 이름을 출력합니다.
%% % 표시를 출력하기 위해 사용한다.
%n 플랫폼 종속적인 개행문자가 출력된다. \r\n 또는 \n 일것이다.
%c 카테고리를 표시합니다
예) 카테고리가 a.b.c 처럼 되어있다면 %c{2}는 b.c가 출력됩니다.
%C 클래스명을 포시합니다.
예) 클래스구조가 org.apache.xyz.SomeClass 처럼 되어있다면 %C{2}는 xyz.SomeClass 가 출력됩니다
%F 로깅이 발생한 프로그램 파일명을 나타냅니다.
%l 로깅이 발생한 caller의 정보를 나타냅니다
%L 로깅이 발생한 caller의 라인수를 나타냅니다
%M 로깅이 발생한 method 이름을 나타냅니다.
%r 어플리케이션 시작 이후 부터 로깅이 발생한 시점의 시간(milliseconds)
%x 로깅이 발생한 thread와 관련된 NDC(nested diagnostic context)를 출력합니다.
%X 로깅이 발생한 thread와 관련된 MDC(mapped diagnostic context)를 출력합니다.

예시) (같은 색끼리 보시면 됩니다) 위의 test.jsp를 다음 포맷으로 출력해본다면

[%c] [%C] [%d] [%F] [%l] [%L] [%m] [%M] [%n] [%p] [%r] [%t] [%x] [%X]는 다음과 같다

[test.jsp] [org.apache.jsp.test_jsp] [2005-03-10 12:37:23,561] [test_jsp.java] [org.apache.jsp.test_jsp._jspService(test_jsp.java:64)] [64] [fatal!!] [_jspService] [개행] [FATAL] [765567] [http-8080-Processor25] [] []

=============================================
본문서는 자유롭게 배포/복사 할수 있지만
이문서의 저자에 대한 언급을 삭제하시면 안됩니다
저자 : GoodBug (unicorn@jakartaproject.com)
최초 : http://www.jakartaproject.com 
=============================================

읏... struts.xml파일을 classes폴더에 두어야 하는데......
정말 바보처럼 Web-Inf 폴더에 두고 삽질만 2시간...쩝....


튜토리얼을 보니
struts2.x 는 브라우저 주소창에 *.action이라고 적어줘야 action이 일어난다고..
action이라고 다적을라니 struts1.x 의 *.do가 너무 그리웠다...ㅡ.ㅡ;;;;
(라기보다는 action이라는 단어가 귀찮았다...쩝....)

네이버 언냐~나 구글 어빠~ 등을 전전 긍긍하면서 찾아봤더니
역시나 struts.action.extension 이라는 설정항목이 있었다.

struts.properties파일에서 설정해주는 내용들이 대부분이었는데...
설정파일이 너무 많아지는 것같아서,
'한개의 파일에서 처리는 안돼는겨??' 란 오기아닌 오기가...
(설정파일이 너무 많다는건 언제나 불만이었다.)

딱! 맞는 내용은 찾을 수 없었지만... encoding설정 하는 내용에서 힌트를 얻어서
로컬에서 테스트를 해봤다....
하지만 위에있는 실수와 삽질덕에.....

잊어버리기 전에(그렇진 않겠지만...) 적어둔다.
3개중에 1개만 해주면 된다.

=== Web.xml 에서의 설정 ===
....
<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
   <init-param>
      <param-name>struts.i18n.encoding</param-name>
      <param-value>UTF-8</param-value>
   </init-param>
   <init-param>
      <param-name>struts.devMode</param-name>
      <param-value>true</param-value>
   </init-param>
   <init-param>
      <param-name>struts.action.extension</param-name>
      <param-value>action,do</param-value>
   </init-param>
</filter>
...

=== struts.xml 에서의 설정 ===
...
<struts>
    <constant name="struts.i18n.encoding" value="UTF-8" />
    <constant name="struts.devMode" value="true" />
    <constant name="struts.action.extension" value="action,do" />
...
</struts>
...


=== struts.properties 에서의 설정 ===
...
struts.action.extension=action,do
...

출처 : http://blog.theple.com/rdk75/folder/2.html?uid=29

▣ 제목

JEUS 로깅 체계

▣ 내용

JEUS의 로그는 standard output으로 출력되거나 file로 기록됩니다.

JEUS에서 로그를 남기는 주체인 로거(Logger)들은 상-하 관계를 가집니다.

만일 자신의 출력방식(target)이 file이면 자신의 file에만 기록하고

stdout(standard output)이면 상위의 로거에 자신의 로그를 위임합니다.

Jeus Manager(jeus) 프로세스를 제외하고는 콘솔(console)을 점유하지 않으므로

stdout으로 target이 설정된 경우에는 상위 로거에 자신의 로그가 출력됩니다.

즉, stdout으로 설정하면 상위 로거의 출력방식(target)을 따라 상위 로거에 자신의 로그가 출력됩니다.


* 공통사항

- 출력방법 : stdout(or console) / file / stdout:file(stdout과 file 출력을 동시에)

- buffer size : 로깅은 I/O를 일으키므로 로깅 효율성(시스템 부하)을 위해 buffer를 사용하고

버퍼가 가득 찼을 때에만 I/O를 수행한다. 설정단위는 byte 이며 보통 1024 / 4096 / 8192

사용하고 대부분 8192 이상의 설정을 하여도 성능 차이는 미미하다.

- 일자별로 출력되는 경우 파일명은 <로그명>_월일년.log 로 생성된다.

- JEUS 3.x와 4.x의 로그 레벨 설정이 다르므로 주의한다(반드시 메뉴얼 참조)

- 운영중이고 문제가 거의 발생하지 않는 사이트인 경우에는 성능의 향상을 위해서

target은 file로, buffer size를 설정(4k or 8k), 로그 레벨을 중/상 정도로 설정하도록 한다.

1. JeusServer Log

- 출력방법 : stdout / file / stdout:file(stdout과 file 출력을 동시에)

- 생성위치 : $JEUS_HOME/logs/JeusServer/JeusServer_날짜.log

- 생성형태 : 일자별

- webadmin(웹으로 접근)을 사용하도록 설정된 JEUS인 경우 JeusServer Log는 <로그명>_월일년_0.log로

- 기록내용 : Jeus Manager에서 남기는 로그, thread dump 등.

- 지정 : JeusMain.xml의 Node -> Logging에서 설정

JEUSMain.xml의 jeus-system -> node -> system-log 에서 설정

2. Container Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/JeusServer/<ContainerName>/ContainerName_날짜.log

- 생성형태 : 일자별

- 기록내용 : Container에서 남기는 로그

- 지정 : JeusMain.xml의 Node -> EngineContainer -> Logging에서 설정

JEUSMain.xml의 jeus-system -> node -> engine-container -> system-log 에서 설정

3. Servlet Engine Log

- Servlet Engine은 ContextGroup에서 남기는 로그(error log, access log, user log, context user log)와

stdout, stderr를 하위 항목으로 가진다.

4. StdOut Log

- 출력방법 : 기본적으로는 Container Log에 포함되나 redirection을 true로 지정할 경우 file로 기록된다.

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/stdout_날짜.log

- 생성형태 : 일자별

- 기록내용 : AP에서 System.out.println() 메소드를 사용하여 남기는 로그

- 지정 : Container.xml의 Container -> RedirectStdOut 에서 설정

WEBMain.xml의 web-container -> redirect-stdout 에서 설정

5. StdErr Log

- 출력방법 : 기본적으로는 Container Log에 포함되나 redirection을 true로 지정할 경우 file로 기록된다.

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/stderr_날짜.log

- 생성형태 : 일자별

- 기록내용 : AP에서 System.err.println() 메소드를 사용하여 남기는 로그

- 지정 : Container.xml(WEBMain.xml)의 Container -> RedirectStdErr 에서 설정

WEBMain.xml의 web-container -> redirect-stderr 에서 설정

6. Context Group

- Context Group은 error log, access log, user log, context user log를 하위 항목으로 가진다.

7. Access Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/accesslog/access_날짜

$JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/access..log

- 생성형태 : 일자별 or 단일파일

(valid days 설정을 하지 않거나 -1로 지정한 경우 단일파일에 쌓이게 되므로 반드시 valid da

- 기록내용 : ContextGroup에서 호출된 AP의 access 로그(호출시간, IP, Method, URL, 응답시간 등)

- 지정 : Container.xml의 Container -> ContextGroup -> AccessLogXXX 에서 설정

WEBMain.xml의 web-container -> context-group -> logging -> access-log 에서 설정

8. Error Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/errorlog/

$JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/error.log

- 생성형태 : 일자별 or 단일파일

(valid days 설정을 하지 않거나 -1로 지정한 경우 단일파일에 쌓이게 되므로 반드시 valid da

- 기록내용 : ContextGroup에서 호출된 AP의 수행과 관계되는 오류, servlet engine의 로그

- 지정 : Container.xml의 Container -> ContextGroup -> ErrorLogXXX 에서 설정

WEBMain.xml의 web-container -> context-group -> logging -> error-log 에서 설정

9. User Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/userlog/

$JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/user.log

- 생성형태 : 일자별 or 단일파일

(valid days 설정을 하지 않거나 -1로 지정한 경우 단일파일에 쌓이게 되므로 반드시 valid da

- 기록내용 : ContextGroup에 속한 worker thread들의 상태를 일정주기(Monitoring interval)로 출력(ti 결과

JSP의 내장 객체인 application.log() / Servlet의 ServletContext.log() 메소드를 AP에서 호출

해당 context에서 별도로 지정하지 않는 경우 ContextGroup의 User Log에 남게된다.

- 지정 : Container.xml의 Container -> ContextGroup -> ErrorLogXXX 에서 설정

WEBMain.xml의 web-container -> context-group -> logging -> user-log 에서 설정

10. Context User Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/userlog/ContextName_

$JEUS_HOME/logs/<ServletEngineName>/<ContextGroupName>/ContextName.log

- 생성형태 : 일자별 or 단일파일

- 기록내용 : JSP의 내장 객체인 application.log() / Servlet의 ServletContext.log() 메소드를 AP에서 호출

별도로 설정하지 않으면 상위 로거인 User Log 에 남게된다.

- 지정 : Container.xml의 Container -> ContextGroup -> Context -> UserLogXXX 에서 설정

jeus-web-dd_<ContextName>.xml의 jeus-web-dd -> context -> user-log 에서 설정

11. EJB Engine Log

- 출력방법 : stdout / file

- 생성위치 : $JEUS_HOME/logs/<EJBEngineName>/

- 생성형태 : 일자별

- 기록내용 : Container에서 남기는 로그

- 지정 : EJBMain.xml의 EJBEngineConfig -> Logging에서 설정

EJBMain.xml의 ejb-engine -> system-log 에서 설정

▣ 참고사항

기타 , JMS 엔진에서 남기는 로그, JEUS System Engine에서 남기는 로그, WebT에서 남기는 로그, TM(Transaction Manager)에서 남기는 로그 등은 빈도가 낮아 언급하지 않았음.

통상적으로 한 컴퓨터안에 Apache 와 IIS를 사용하는 경우 많이 발생

Apache 또는 IIS가 80포트를 사용하기 때문에 서로 출돌이 나서 실행이 되지 않는 경우.

이럴 경우에는 Apache 포트를 변경하거나 잠시 중단하면 해결이 가능하다.
(또는 IIS의 포트 변경도 가능....)

출처 : http://blog.empas.com/trucrys/18210501

taskkill.exe 명령어 사용

/F  => 프로세서를 강제로 종료
/IM => 종료할 프로그램 이름
 
 
예) IE 프로그램을 죽일때
taskkill /F /IM iexplorer.exe

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>롤링 배너</title>

<style type="text/css">
#rollBanner {
    position:relative;
    width:150px;
    height:70px;
    overflow:hidden;
    border:1px solid #000;
    white-space:nowrap;
}
#rollBannerText {
    position:relative;
    width:200px;
    height:50px;
    overflow:hidden;
    border:1px solid #000;
    white-space:nowrap;
}
#rollBanner p    {
    position:absolute;
    margin:0;
}
#rollBannerText p    {
    position:absolute;
    margin:0;
}
</style>

<script type="text/javascript">

function    rollBanner(divId,intervalTime)  {
    var divId = document.getElementById(divId); // Roll Div 선언
    var pTag =  divId.getElementsByTagName("p")[0]; // absolute P 변수 선언
    var imgTag =  divId.getElementsByTagName("img"); //이미지 변수 선언 (실제로 여기선 필요없음. 다 링크가 달렸기때문에..)
    var textTag = divId.getElementsByTagName("a"); // 단순 Text 변수 혹은 이미지에 걸린 링크 선언
    var pWidth = pTag.offsetHeight; //p태그가 가진 Width값 변수
    var speed = 1; //1픽셀씩 이동

    pTag.style.top = 0 + "px"; //수정불가, 위치를 옮기고 싶다면 div 자체에 margin값을 스타일로 줘야함 (p자체가 움직이는 구조)

    var bannerArray = new Array(); // 배너를 롤링하기 위해 배열복사(배열까진 필요없을것 같기도함)
    bannerArray[0] = pTag.innerHTML; // 배열에 P 태그가 가지고 있는 항목 저장

    if (textTag.length != 0) // a 태그가 있으면 (img로 대체할수도 있겠지)
    {
        if (pWidth > divId.offsetHeight) //p width값이 div width 값보다 크다면 움직여라 (작은데도 움직일거면 삭제)
        {
            pTag.style.top = pTag.offsetTop; //absolute P태그의 left를 선언 (offset은 읽기전용이라서..)
            pTag.innerHTML=pTag.innerHTML+bannerArray[0]; //일단 처음에 배너 배열을 하나 더 복사해서 길게 만듬

            var rollInterval = setInterval(
            function()
            {
                /* onmouse Event */
                pTag.onmouseover = function() {
                    speed=0;//이동 안함
                };
                pTag.onmouseout = function() {
                    speed=1;//1픽셀씩 이동
                };

                pTag.style.top =  parseInt(pTag.style.top)-speed + "px";//1픽셀씩 이동
                if (parseInt(pTag.style.top) % pWidth==0)
                /* 배너 넓이 만큼 이동했을시 */
                {

                    pTag.innerHTML=pTag.innerHTML+bannerArray[0]; //뒤로 배너배열 하나더 추가
                }
            }
            ,intervalTime);

        }
    }

}

</script>

</head>


<body>
<div id="rollBanner">
<p>
 <table>
  <tr>
   <td>
    <a href="#"><img src="mainBanner1.jpg" alt="배너1" />1</a> <a href="#"><img src="mainBanner1.jpg" alt="배너1" />1 </a><br>
    <a href="#"><img src="mainBanner1.jpg" alt="배너1" />2</a> <a href="#"><img src="mainBanner1.jpg" alt="배너1" /> 2</a><br>
    </td>
  </tr>
</table>

</p>
</div>

<script type="text/javascript">
    rollBanner("rollBanner",25);
</script>


</body>
</html>


출처 : 다른 분이 찾아준 부분이어서 모름.....

Java 어노테이션(Annotation)에 관한 간략한 소개
Java Fundamental 2007/01/14 03:53

원본 : http://bioportal.weizmann.ac.il/course/prog2/tutorial/java/javaOO/annotations.html

JDK 5.0 릴리즈에는 어노테이션(annotation)이라 불리는 메타데이터 기능이 도입되었다. 어노테이션은 코드 조각의 작성자 명이나 컴파일러가 특정 오류를 억제하도록 지시하는 것과 같이 프로그램의 일부가 아닌 프로그램에 관한 데이터를 제공해 준다. 어노테이션은 코드가 어떻게 수행되는 것에는 아무런 영향을 주지 않는다.

어노테이션은 @어노테이션의 형태를 사용하고 클래스, 필드, 메소드 등과 같은 프로그램의 선언부에 적용할 수 있다. 어노테이션은 가장 처음으로 그리고 종종(관례상) 그 줄에 나타나며 임의의 인수를 포함할 수 있다:

        @Author("MyName")

        class myClass() { }

혹은

        @SuppressWarnings("unchecked")

        void MyMethod() { }


여러분만의 어노테이션을 정의하는 것은 여기에 기술되지 않을 고급기법에 속하지만, 모든 자바 프로그래머들이 알아야 하는 3개의 내장 어노테이션이 있다: @Deprecated, @Override, 그리고 @SuppressWarnings가 그것이다. 다음 예제들은 메소드에 적용된 모든 세 가지 타입의 어노테이션을 보여준다:

import java.util.List;


class Food {}

class Hay extends Food {}

class Animal {

    Food getPreferredFood() {

        return null;

    }

    /**

     * @deprecated document why the method was deprecated

     */

    @Deprecated

    static void deprecatedMethod() { }

}

class Horse extends Animal {

    Horse() {

        return;

    }

    @Override

    Hay getPreferredFood() {

        return new Hay();

    }

    @SuppressWarnings("deprecation")

    void useDeprecatedMethod() {

        Animal.deprecateMethod(); //deprecation warning - suppressed

    }

}


@Deprecated

@Deprecated 어노테이션은 표시된 메소드가 더 이상 사용되지 말아야 함을 가리킨다. 컴파일러는 프로그램이 비추천(deprecated) 메소드나 클래스 혹은 변수를 사용할 때마다 경고를 발생시킨다. 한 요소가 비추천되면 그것은 앞의 예제에 나타나 있는 것처럼 해당되는 @deprecated 태그를 사용하여 문서화 되어야 한다. 태그는 소문자 ‘d’로 시작하며 어노테이션은 대문자 ‘D’로 시작함을 주의하라. 일반적으로 여러분은 비추천 메소드의 사용을 피해야 하며 그것 대신 무엇을 사용해야 하는지를 확인해야 한다.


@Override

@Override 어노테이션은 요소가 상위 클래스에서 선언된 한 요소를 오버라이드 할 것임을 컴파일러에게 알려준다. 앞의 예제에서는 오버라이드 어노테이션이 Horse 클래스의 getPreferredFood 메소드가 Animal 클래스에 있는 동일한 메소드를 오버라이드하고 있음을 가리키는 데 사용되었다. @Override로 표시된 한 메소드가 상위 클래스에 있는 메소드를 오버라이드하는 데 실패할 경우 컴파일러는 에러를 발생시킨다.


메소드를 오버라이드 할 때 이 어노테이션을 사용하도록 요구되는 것은 아니기 때문에 특히 메소드가 오버라이드된 메소드의 리턴 타입의 하위 타입을 리턴할 때 명시적으로 그것을 드러내도록 요구하는 것이 유용할 수 있다. 이러한 관례는 “공변 리턴 타입(covariant return type)”이라 불리는데, 이전의 Food 인스턴스를 리턴하는 Animal.getPreferredFood 예제에서 사용되었다. Horse.getPreferredFood (Horse은 Animal의 하위클래스)는 Hay의 인스턴스를 리턴한다. 좀 더 자세한 정보를 위해서는 Overriding and Hiding Methods를 참조하라.


@SuppressWarnings

@SuppressWarnings 어노테이션은 컴파일러가 다르게 발생될 특정 경고를 억제하도록 해준다. 이전의 예제에서 useDeprecatedMethod는 Animal의 비추천 메소드를 호출한다. 보통 컴파일러는 경고를 발생시킬테지만, 이 경우에는 그것이 억제된다.

모든 컴파일러 경고는 한 종류에 속한다. 자바 언어 명세(Java Language Specification)는 “비추천(deprecation)”과 “비확인(unchecked)”의 두 가지 종류를 나열한다. “비확인” 경고는 제네릭이 나오기 전에 작성된 레거시 코드와 인터페이싱할 경우에 발생할 수 있다. 하나 이상의 경고 종류를 억제하려면 다음의 문법을 사용하라.

@SuppressWarnings({"unchecked", "deprecation"})

지원되는 경고 종류들의 목록을 보려면 컴파일러 문서를 확인하라.


어노테이션을 좀 더 고급스럽게 사용하는 것에는 자바 프로그램을 읽어 그것의 어노테이션을 처리할 수 있는 프로그램을 작성하는 것이 포함되어 있다. 이 작업을 돕기 위해, JDK 5.0 릴리즈에는 apt라 불리는 어노테이션 처리도구(annotation processing tool)가 포함되어 있다. 다음의 JDK 릴리즈(코드네임 머스탱)에는 apt 기능이 자바 컴파일러의 표준 요소가 될 것이다. 좀 더 자세한 정보를 보려면 Getting Started with the Annotation Processing Tool 를 참조하라. 머스탱의 진척사항에 관한 더 자세한 정보를 원한다면 Language Model API JSR 269: Pluggable Annotation Processing API 을 참고하라.

<html>
 <head>

  <script language="javascript">
   function ExemSubmit( str )
   {
    //document.exem.method              = "post";                    //아래 form tag에 적어두었으면 적을 필요 없음.
    document.exem.action                    = "URL or Page";       //아래 form tag와 중복 된다면 전송이 잘 이루어지지 않는다.
    document.exem.postparameter.value = "xxxx";                  //PageMode와 같은 파라메터 값을 지정하여 전송할 수 있다.
   
    //str변수를 받아 사용할 수 있다.
   }
   
   function chkForm()
   {
    //여기에 폼을 submit할 때 체크해야하는 로직을 적으세요//
   }
  </script>
 </head>
 
 
 <body>
  <form id="exem" name="exem" method="post" action="xxx.jsp" onsubmit="return chkForm();">
   <input type='button' name='insert' value='저장' onClick='JavaScript:ExemSubmit("Insert")'>&nbsp;
   <input type='button' name='update' value='수정' onClick='JavaScript:ExemSubmit("Update")'>&nbsp;
   <input type='button' name='delete' value='삭제' onClick='JavaScript:ExemSubmit("Delete")'>
   
   <!-- 위의 form tag에 전송할 것들을 모두 적어 두었다면 아래와 같이 submit할 수 있다.
   <input type='button' name='directSubmit' value='바로 전송' onClick='document.exem.submit()'>
   -->
  </form>
 </body>
</html>

- 우선 Win Form에 'notifyIcon'컨트롤을 추가.
- 속성에서 아이콘을 등록. (등록하지 않으면 트래이상태에서 아이콘이 보이지 않음)


- 이벤트 부분

//Tray 시키기
private void btnHide_Click(object sender, System.EventArgs e)
{
     this.Hide();                    // alt+tab 시 보이지 않는다.
     nIcon.Visible    = true;       // 트레이의 아이콘을 보이게 한다.
     this.Hide();
     this.nIcon.Text  = "설명.....";
}

//원래대로 돌아오기
private void nIcon_DoubleClick(object sender, System.EventArgs e)
{
     this.Visible             = true;
     this.ShowInTaskbar = true;                                  // 현재 프로그램을 테스크 바에 표시
     this.WindowState     = FormWindowState.Normal;   // 폼을 윈도 상태를 normal
     nIcon.Visible           = false;
}

 

[엔터프라이즈 관리자]

원격지에 있는 SQL서버에 접속 할 때 사전에 몇가지 알아두어야 할 사항.


1. OS 방화벽 열어주기

 2003은 기본적으로 방화벽이 설정되어있지 않다. 하지만 보안을 위해서 방화벽을 살리고 개인이 지정해 놓은 포트만을 열어두는 경우가 있다.  이럴때는 방화벽 설정에서 MS-SQL 서버 포트(기본 설정 포트는 1433이다.)를 열어주야 한다.


2. 공유기 사용시

 - 여러대의 컴을 공유기에 물려서 사용하는 경우 공유기의 포트를 포워딩 해주어야 한다. 한 개의 공유기에 연결되어있는 여러 대의 SQL서버를 원격지에서 동시에 관리하고 싶다면, SQL 서버의 포트를 각각 커스텀 포트로 변경 해 주어야 하며, 공유기 설정에서도 각각 커스텀화 된 포트를 모두 포워딩 해주어야 한다.

(2대 이상을 연결하는 사람이 많을지 의문이지만.....ㅡ.ㅡ)

+ Recent posts