2011년 8월 1일 월요일

Tomcat 5.0 ~ 7.0 한글 인코딩 설정

한동안 Tomcat을 사용하지 않다가 오랜만에 사용했더니 인코딩 관련 착각을 해서 한참을 삽질 했습니다. 한글 인코딩에 대하여 간략하게 정리합니다.


톰켓의 기본 인코딩은 ISO-8859-1 입니다. 따라서 한글을 정상적으로 처리하기 위해서는 추가적인 인코딩 설정이 필요합니다. 인코딩 설정은 http get 메서드와 http post 메서드를 개별적으로 설정합니다.

HTTP Get 메서드 인코등 설정

  • [TOMCAT_HOME]/conf/server.xml 수정
  • Connector port="8080" 엘레먼트에 URIEncoding="UTF-8" 속성 추가

HTTP Post 메서드 인코등 설정

  • 인코딩 필터 클래스 추가 - tomcat example 애플리케이션의 encoding 클래스 재 사용
  • 애플리케이션의 web.xml에 인코딩 필터 설정 추가
인코딩 필터를 적용함으로서 request.setCharacterEncoding() 코드를 제거할 수 있다.
package filters;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class SetCharacterEncodingFilter implements Filter {

    protected String encoding = null;
    protected FilterConfig filterConfig = null;
    protected boolean ignore = true;

    public void destroy() {

        this.encoding = null;
        this.filterConfig = null;

    }

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
 throws IOException, ServletException {

        // Conditionally select and set the character encoding to be used
        if (ignore || (request.getCharacterEncoding() == null)) {
            String encoding = selectEncoding(request);
            if (encoding != null)
                request.setCharacterEncoding(encoding);
        }

        chain.doFilter(request, response);

    }

    public void init(FilterConfig filterConfig) throws ServletException {

 this.filterConfig = filterConfig;
        this.encoding = filterConfig.getInitParameter("encoding");
        String value = filterConfig.getInitParameter("ignore");
        if (value == null)
            this.ignore = true;
        else if (value.equalsIgnoreCase("true"))
            this.ignore = true;
        else if (value.equalsIgnoreCase("yes"))
            this.ignore = true;
        else
            this.ignore = false;

    }

    protected String selectEncoding(ServletRequest request) {
        return (this.encoding);
    }
}
위 클래스를 web.xml에 다음과 같이 인코딩 필터로 등록해야 합니다.

  Set Character Encoding
  filters.SetCharacterEncodingFilter
  
    encodingUTF-8



  Set Character Encoding
  /*

2011년 4월 17일 일요일

매스컴에 비친 행인 1, 2, 3, 4

어찌어찌 하다 보니 흘러가는 뉴스 영상에 제가 나오게 되었습니다.

특별한 내용은 아니지만 첫번째 경험이다 보니 기념으로 남겨 볼랍니다. ㅋㅋㅋ 지대로 나온 사진은 전무합니다. 옆모습, 손가락, 매력적인 뒷태, 고개돌린 모습 등 행인 1, 2, 3, 4 포스 입니다.



동훈이, 선유, 주영이와 함께한 설정 샷 (참 뻘쭘한 설정 샷이었습니다.)



제 사진 중 가장 잘나온 사진 입니다. 역시 얼굴보다는 손이더... 영상에 적합한 듯.... 

역시나 제 멋진 뒷태 입니다. 멋지죠? 앞태보다는 뒷태가 더 끌리는 듯


과도하게 카메라를 의식한 나머지 고개를 돌리고야 말았습니다. 아뜨...ㅋㅋ



위 동영상의 원본 URL 입니다.


위 동영상의 원본 위치 입치는 <여기> 입니다.
http://sbscnbc.sbs.co.kr/read.jsp?pmArticleId=10000138530

2011년 4월 6일 수요일

OS별 bit 확인 방법

맥, 리눅스, AIX 운영체제의 bit를 확인하는 방법을 정리해 보았습니다.

Linux

리눅스의 경우 uname 명령어로 간단히 확인 가능 합니다. "uname -a" 명령어로 현재 운영체제의 비트를 확인할 수 있습니다.


AIX

AIX에서는 getconf명령어와 /usr/lib/boot/unix*에 대한 file 명령를 통해서 bit를 확인 할 수 있습니다. 다음은 64비트 운영환경에서 테스트한 결과 입니다. 운영체제가 32비트이면 결과는 32로 표시될 것입니다.

[ ONLINE Mode ]>getconf -a | grep KERN
KERNEL_BITMODE:                         64

[ ONLINE Mode ]>file /usr/lib/boot/unix*
/usr/lib/boot/unix: 64-bit XCOFF executable or object module not stripped
/usr/lib/boot/unix_64: 64-bit XCOFF executable or object module not stripped

Mac OS X

sysctl명령어로 운영체제 비트 정보를 확인할 수 있습니다. 다음은 64비트 OSX에서 테스트한 결과 입니다.

taewan:bin taewankim$ sysctl hw |grep 64bit
hw.cpu64bit_capable: 1
64비트가 아닐 경우에는 다음과 같은 결과가 출력됩니다.

dummy:bin taewankim$ sysctl hw |grep 64bit
hw.cpu64bit_capable: 0

2011년 4월 5일 화요일

Glassfish의 asadmin 명령어 한글 깨짐 현상

GlassFish를 설치하고 "asadmin"를 실행하면 한글이 깨지는 현상이 발생합니다. 다음은 GlassFish 3.1을 설치하고 asadmin을 수행한 결과 입니다.

taewan:bin taewankim$ ./asadmin
?????Ϸ??? "exit"??, ?¶??? ?????? ?????? "help"?? ????մϴ?.
asadmin> help

??ƿ??Ƽ ????                                         asadmin(1M)

NAME
     asadmin  -  Oracle  GlassFish  Server??  ????  ????   ?۾???
     ?????ϱ? ???? ??ƿ??Ƽ?Դϴ?.

SYNOPSIS
     asadmin [--host host]
     [--port port]
     [--user admin-user]
     [--passwordfile filename]
     [--terse={true|false}]
     [--secure={false|true}]
     [--echo={true|false}]
     [--interactive={true|false}]
     [--help]
     [subcommand [options] [operands]]

DESCRIPTION
     Oracle GlassFish Server?? ???? ???? ?۾??? ?????Ϸ???  asad-
     min  ??ƿ??Ƽ??  ????մϴ?.  ????  ?ܼ?  ???????̽? ??? ??
     ??ƿ??Ƽ?? ????? ?? ?ֽ??ϴ?.

  asadmin ??ƿ??Ƽ?? ???? ????
     ???? ?????? ???? ????  ????  ?Ǵ?  ?۾???  ?ĺ??մϴ?.  ????
     ??????  ??ҹ??ڸ? ?????մϴ?. ?? ???? ?????? ???? ???? ????
     ?Ǵ? ???? ???? ?????Դϴ?.

         o    ???? ???? ?????? DAS(?????? ????  ????)??  ????????
              ?ʰ?   ??????   ??  ?ֽ??ϴ?.  ?׷???  ????  ??????
              ?????ϰ?  ??ġ  ???丮   ??   ??????   ???丮??
              ?׼????Ϸ???  ???????? ȣ?????ϴ? ?ý??ۿ? ????ڰ?
              ?α??εǾ? ?־?? ?մϴ?.

         o    ???? ???? ?????? ?׻? DAS??  ?????ϰ?  ?ű⼭  ????
              ?????? ?????Ͽ? ????˴ϴ?. DAS?? ?????ؾ? ?մϴ?.

  asadmin ??ƿ??Ƽ ?ɼ? ?? ???? ???? ?ɼ?
     ?ɼ???  asadmin  ??ƿ??Ƽ  ??  ?ش?   ????   ??????   ??????
     ?????մϴ?. ???? ?ɼ??? ??ҹ??ڸ? ?????մϴ?.

     asadmin ??ƿ??Ƽ???? ???? ?????? ?ɼ??? ?ֽ??ϴ?.

         o    asa??. ???̷???  ?ɼ???   asadmin
              ??ƿ??Ƽ??  ??????  ??????????  ????  ?????? ??????
              ???????? ?ʽ??ϴ?.  asadmin  ??ƿ??Ƽ  ?ɼ???  ????
              ??????  ?տ?  ??ġ?ϰų? ?ڿ? ??ġ?? ?? ?????? ????
              ???? ?ڿ? ??ġ?? asadmin ??ƿ??Ƽ  ?ɼ???  ??????
              ?ʽ??ϴ?.  ??? asadmin ??ƿ??Ƽ ?ɼ??? ???? ??????
--??Ÿ--

위 테스트는 Mac OS에서 실행한 결과 입니다. 이와 같이 한글 깨짐 현상이 발생한 경우 해결 /glassfihs/bin 디렉터리의 asadmin 혹은 asadmin.bat파일을 열어 마지막 라인을 다음과 같이 수정하하고 재 시작하면 해결 됩니다.

## 수정전
# exec "$JAVA" -jar "$AS_INSTALL_LIB/admin-cli.jar" "$@"

## 수정후
exec "$JAVA" -Dfile.encoding=UTF-8 -jar "$AS_INSTALL_LIB/admin-cli.jar" "$@"


asadmin 쉘을 수정하고 재시작하면 한글깨짐 현상이 해결된 것을 확인 할 수 있습니다.

taewan:bin taewankim$ ./asadmin
종료하려면 "exit"를, 온라인 도움말을 보려면 "help"를 사용합니다.
asadmin> help

유틸리티 명령                                         asadmin(1M)

NAME
     asadmin  -  Oracle  GlassFish  Server에  대한  관리   작업을
     수행하기 위한 유틸리티입니다.

SYNOPSIS
     asadmin [--host host]
     [--port port]
     [--user admin-user]
     [--passwordfile filename]
     [--terse={true|false}]
     [--secure={false|true}]
     [--echo={true|false}]
     [--interactive={true|false}]
     [--help]
     [subcommand [options] [operands]]

DESCRIPTION
     Oracle GlassFish Server에 대한 관리 작업을 수행하려면  asad-
     min  유틸리티를  사용합니다.  관리  콘솔  인터페이스 대신 이
     유틸리티를 사용할 수 있습니다.

  asadmin 유틸리티의 하위 명령
     하위 명령은 수행 중인  연산  또는  작업을  식별합니다.  하위
     명령은  대소문자를 구분합니다. 각 하위 명령은 로컬 하위 명령
     또는 원격 하위 명령입니다.

         o    로컬 하위 명령은 DAS(도메인 관리  서버)를  실행하지
              않고   실행할   수  있습니다.  그러나  하위  명령을
              실행하고  설치  디렉토리   및   도메인   디렉토리에
              액세스하려면  도메인을 호스팅하는 시스템에 사용자가
              로그인되어 있어야 합니다.

         o    원격 하위 명령은 항상 DAS에  연결하고  거기서  하위
              명령을 실행하여 실행됩니다. DAS를 실행해야 합니다.

  asadmin 유틸리티 옵션 및 하위 명령 옵션
     옵션은  asadmin  유틸리티  및  해당   하위   명령의   동작을
     제어합니다. 또한 옵션은 대소문자를 구분합니다.

     asadmin 유틸리티에는 다음 유형의 옵션이 있습니다.

         o    asa션. 션이러한  옵션은   asadmin
              유틸리티의  동작을  제어하지만  하위  명령의 동작은
              제어하지 않습니다.  asadmin  유틸리티  옵션은  하위
              명령의  앞에  위치하거나 뒤에 위치할 수 있지만 하위
              명령 뒤에 위치한 asadmin 유틸리티  옵션은  사용되지
              않습니다.  모든 asadmin 유틸리티 옵션은 하위 명령의
--기타--

2011년 3월 30일 수요일

Seam 프레임웍 Logo에 숨겨진 비밀?

이 포스팅은 2년전에 한참 Seam에 미쳐 있을때 작성했던 포스팅 입니다. 왠지 아까워서 예전 블러그에서 퍼왔습니다. Seam 믿거나 말거나 편입니다.


Seam Framework은 기존과 차별되는 독특한 컴포넌트 관리 모델을 제안하고 있습니다. 기존에 Java EE 애플리케이션은 Web 티어와 EJB 티어로 구분되어 있기 때문어 상호간에 참조하기가 어려웠습니다. 웹 에서 EJB 컴포넌트를 참조하기 위해서는 JNDI Lookup과 Casting 작업이 선행되어야 했습니다.

이렇다 보니 Java EE의 핵심 컴포넌트인 JSF 컴포넌트와 EJB 컴포넌트는 Java EE의 핵심 컴포넌트이기는 하지만 상호간을 참조하는 것은 여간 번거로운 작업이 아닐수 없었습니다.

왜 이런일이 발생할까? 라는 고민에서 Seam Framework이 시작되었다고 보셔도 좋을것 같습니다.

처음 Seam을 접할때 Seam이란 의미는 Seamless의 약자가 아닐까? 라는 생각을 했습니다. Seam의 컴포넌트 모델을 보다 보면 다음과 같은 이미지를 연상하게 됩니다.

Seam 로고의 숨겨진 이야지....

개인적인 추측에 불과하지만 Seam Framework의 빗살 무늬 로고는 이런 의미를 담고 있는 것이 아닌가라는 생각을 해 보았습니다.

동일한 JVM에서 구동하는 WAS의 컴포넌트를 통합하여 사용할 수 있도록 지원하는 것이 Seam 프레임웍의 시작점 입니다. 이렇게 Java EE 컴포넌트를 통합하는 모델위에 컴포넌트가 서로를 상호 참조하는 의존성 주입 모델이 결합되면 이것이 최종 Seam 컴포넌트 관리 모델이 됩니다.

Seam 컴포넌트 모델은 지난 번에 말씀 드렸던 것 처럼 JSR - 299 스펙 CDI (Context and Dependency Injection)의 토대가 되고 있고, 현재 Seam의 모습으로 JSR - 299가 정의 되고 있습니다.

웹컴포넌트와 EJB 컴포넌트가 통합되어 관리 된다는 것은 어떤 의미일까요?

이것은 다음과 같은 코드를 보시면 좋을 것 같습니다.

* JSP Sample 코드

    



위와 같은 JSP 코드가 존재한다고 생각해 보시기바랍니다.이코드를 보면 다음과 같은 것을 추측할 수 있습니다.

  • 폼에 하나의 입력 텍스트와 하나의 버튼으로 구성
  • 입력 텍스트에 입력된 값은 person 객체의 name 필드에 저장됨
  • 버튼을 클릭하면 manager 객체의 sayHello 메서드가 실행됨

person과 manager 객체의 실체는 무엇일까요? person 객체는 다음과 같을 수 있습니다.

* person 객체
@Entity
@Name("person")
public class Person implements Serializable{
  private long id;
  private String name;
  //이하 코드 생략
  //getter-setter
  //Annotation
}

person 객체의 실체는 위와 같을 수 있습니다. 실제 Person 객체는 Seam Person 컴포넌트라고 하는 것이 정확한 표현입니다. Person 객체를 person 이라는 이름으로 참조 할 수 있는 것은 바로 Seam이 제공하는 컴포넌트 관리 모델때문입니다.

그런데 Person 객체는 실제로 Entity Bean입니다. JPA에서 ORM으로 사용하는 POJO 객체입니다. JPA는 Java SE에서도 구동되는 기술임을 감안하면 아직까지는 크게 이상할 것은 없는 코드 입니다.

그렇다면 manager 객체, 더 정확히 말해서 manager 컴포넌트의 실체는 무엇일까요?

* manager 컴포넌트

@Stateless
@Name("manager")
public class ManagerAction implements Manager{
    @In @Out Person person;
    @Out List personList;
    @PersistenceContext EntityManager em;

    public String sayHello(){
      em.persist(person);
      person = null;
      return null;
    }
   //이하 코드 생략
}

여러가지 manager 컴포넌트 스타일을 생각해 볼 수 있지만 위와 같은 코드가 일반적입니다. 놀라운 것은 manager 컴포넌트는 실제 무상태 세션 빈이라는 것입니다. JSP 페이지의 테그라이브러리의 Expression Language 형태로 설정한 형태는 실제 세션빈의 메서드를 호출하는 것이었습니다.

JSP를 통하여 별다른 작업 없이 ejb를 직접 호출하는 형태 입니다. JSF에 익숙하신 분들에게는 이렇게 표현하실 수 있습니다. (위 JSP는 실제 JSF 페이지 코드의 일부 입니다. )

Seam은 세션빈을 액션 리스너로 직접 사용할 수 있다.

JSF로 이런한 기능을 구현하기 위해서는 다음과 같은 작업을 거치게 됩니다.

  • JSF페이지에서 managed bean으로 등록된 컴포넌트의 메소드를 호출
  • 그 메서드가 JDNI 룩업을 하고 전처리를 수행
  • jejb 호출

Seam은 이런 절차를 모두 생략하고 직접 세션빈을 액션 리스너로 사용할 수 있는 방법을 제공합니다.

이것이 Seam에서 이야기하는 컴포넌트 통합 모델의 근간 입니다.

Seam은 기존에 경험하지 못했던 새로운 개념의 통합 컴포넌트 모델 관리 개념을 제공합니다. 컴포넌트를 직접 참조하는 것이 아니라 컴포넌트 이름으로 참조하고 이것을 관리하는 것은 Seam 프레임웍이 전담하는 형태입니다.

사실 EJB 컴포넌트와 Web 컴포넌트를 통합하는 것은 Seam의 핵심 기능이기는 하지만 가장 밑바탕이 되는 기능중의 일부 입니다. Seam은 기존의 Dependency Injection을 확장하여 Bijection이라는 개념과 Context 개념을 강화하여 진보된 컴포넌트 관리 모델을 제공합니다.


이런 의미에서 앞에서 살펴 보았던 Seam Logo 이미지를 다시 한번 살펴 보도록 하겠습니다.

eam 프레임웍 로고로 사용하는 빗살 무늬 로고는 아마도 두 개의 Tier로 나누어져 있는 컴포넌트를 서로 연결하는 상호작용 할 수 있도록 지원한다는 의미라고 저는 생각하고 있습니다.

여러분들은 어떻게 보이시나요?

간혹 이런 의미를 생각해 보는 것도 재미있는 것 같습니다.^^

Oracle XE Database 관리 콘솔 포트 변경

오라클 데이터베이스 중 XE 버전을 설치하여 개발환경을 구성하거나 작은 사이즈의 데이터베이스로 활용할 수 있습니다. 오라클 XE는 기본적으로 8080 포트를 이용하여 웹 콘솔을 구동하기 때문에 Tomcat이나 JBoss 같은 WAS를 사용할 경우 포트 충돌이 발생할 수 있습니다.

 

이런 문제는 Oracle XE의 http 포트를 변경하여 관련 이슈를 해결할 수 있습니다.

 

포트를 변경하는 방법은 다음과 같습니다.

 

  1. SQl Console 실행
    • 시작--> Oracle 10g Database Express Edition -->SQL 명령줄 실행
  2. System user로 로그인
    • connect system/{password}
  3. 현재 http port 조회
    • SQL> select dbms_xdb.gethttpport as "HTTP-Port" from dual;
    •  HTTP-Port
    • ----------
    •       8080
  4. port 변경 plsql 실행
    • SQL> begin
    •   2  dbms_xdb.sethttpport('22222');
    •   3  end;
    •   4  /
    • PL/SQL 처리가 정상적으로 완료되었습니다.
  5. 변경 http port 조회
    • SQL> select dbms_xdb.gethttpport as "HTTP-Port" from dual;
    •  HTTP-Port
    • ----------
    •       22222
  6. Database restart
  7. http://localhost:22222/apex 페이지 접속 확인

이와 같은 절차에 따라 oracle xe의 웹 포트를 변경할 수 있습니다.

 

 

 

 

Seam - 사용자 IPAddress 확인 방법

Seam은 애플리케이션 UI 프레임웍으로 JSF를 사용합니다. JSF는 컴포넌트 기반 프레임웍이고 Seam과 결합하여 완전한 컴포넌트 기반 프레임웍의 모습을 갖추고 있습니다.

 

Seam 애플리케이션을 개발하다 보면 HttpServletRequest와 HttpSession 객체를 전혀 접근하지 않고 등록된 컴포넌트를 참조하여 사용하게 됩니다. 사용자 등록 폼 정보는 컴포넌트 맵핑 정보를 이용하여 사용하면 되지만 간혹 사용자가 입력하지 않는 별도의 정보를 사용해야 할 수도 있습니다.

 

가령 어떤 어떤 IP/Host에서 접근하는지 로깅을 남겨야 하거나 게시판에서 게시글의 IP Adress를 저장해야 한다면 어떻게 해야 할까요?

 

막상 이런 문제가 발생하면 약간은 막막해 지는 것이 사실입니다. 이 문제를 해결하는 방법은 크게 두 가지가 있습니다.

 

Seam Framework은 사용할 컴포넌트를 클래스 상단에 @Name을 추가하여 등록하던지 @Factory 어노테이션을 메서드에 설정하여 컴포넌트로 등록하고 사용합니다.

 

클라이언트 정보 출력

클라이언트의 IP Address, Host 명, 사용 Port를 출력하는 예제

 

지금은 클라이언트 정보를 HttpServeletRequest로 부터 가져와야 합니다. 이 정보를 가져오기 위해서는 Seam 컴포넌트를 등록하는 방법과 API로 가져오는 방법 두가지가 있습니다. Seam 프레임웍은 기본적으로 자바 클래스에 어노테이션을 추가하거나 /WEB-INF/components.xml에 컴포넌트를 설정하는 두 가지 방식으로 컴포넌트를 등록하여 사용합니다.

 

기본적으로는 어노테이션 사용법을 권장하며 어노테이션을 사용할 수 없는 부득이한 상황에서는 components.xml에 설정하는 방식을 사용합니다.  

 


1. Client 정보 추출 방법

현재 요청의 클라이언트 정보는 HttpServletRequest에서 추출할 수 있습니다. 관련 메서는 다음과 같습니다.

  • 클라이언트 IP Address: public java.lang.String getRemoteAddr()
  • 클라이언트 호스트 명: public java.lang.String getRemoteHost()
  • 클라이언트 포트: public int getRemotePort()

위 와 같은 3개의 메서드를 이용하여 정보를 추출할 수 있습니다. 이 정보를 Seam 컴포넌트로 등록하는 방법은 다음과 두 가지 방법이 존재 합니다.

 

2. components.xml 파일에 컴포넌트 등록하기

위 3개의 메서드를 이용하여 추출한 정보를 컴포넌트로 사용하기 위해서는components.xml에 등록하는 방법이 있습니다. 이 방법을 사용하는 이유는 이 API를 사용하는 어노테이션을 마땅히 설정할 클래스가 없기 때문입니다. components.xml에 다음과 같이 등록하여 사용할 수 있습니다.

 

설정 대상 파일: {SEAM_GEN_PROJECT}/resoueces/WEB-INF/components.xml

 

<factory name="clientAddr" value="#{request.remoteAddr}" scope="EVENT"/>

<factory name="clientHost" value="#{request.remoteHost}" scope="EVENT"/>

<factory name="clientPort" value="#{request.remotePort}" scope="EVENT"/>

 

Seam 페이지 컴포넌트 사용 코드: {SEAM_GEN_PROJECT}/view/layout/menu.xhtml

 

    <rich:toolBarGroup>
        <h:outputText value="#{projectName}:"/>
        <s:link id="menuHomeId" view="/home.xhtml" value="Home" propagation="none"/>
        Client Info: IP: #{clientAddr}, Host: #{clientHost}, Port: #{clientPort}
    </rich:toolBarGroup>

 

 

3. facesContext Injection 하는 컴포넌트 만들기

JSF는 UI 디바이스 독립적인 프레임웍입니다. 따라서 프로토콜을 추상화하는 형태로 디자인 되어 있습니다. 웹 상에서 모든 요청은 HttpServletRequest로 유입이 되고 JSF는 이것은 FacesContext라는 객체 형태로 변형하여 프레임웍에서 사용합니다. 또한 FacesContext로 부터 HttpServletRequest를 접근할 수 있습니다.

 

Seam 프레임웍은 FacesContext 객체를 Seam 컴포넌트에 Injection하는 것을 지원합니다.

따라서 Seam 프레임웍이 Injection 해준 FacesContext를 이용하여 클라이언트 정보를 추출하는 @Factory 메서드를 작성할 수 있습니다.

 

이렇게 만들어진 컴포넌트는 다음과 같습니다.

 

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;

@Name("clientInfo")
public class ClientInfo {
  @In private FacesContext facesContext;  
 
  @Factory(value="clientAddr", scope=ScopeType.EVENT)
  public String getClientIpAddress(){
    HttpServletRequest request =
      (HttpServletRequest)facesContext.getExternalContext().getRequest();
    return request.getRemoteAddr();
  }
  @Factory(value="clientHost", scope=ScopeType.EVENT)
  public String getClientHost(){
    HttpServletRequest request =
      (HttpServletRequest)facesContext.getExternalContext().getRequest();
    return request.getRemoteHost();
  }
  @Factory(value="clientPort", scope=ScopeType.EVENT)
    public int getClientPort(){
      HttpServletRequest request =
        (HttpServletRequest)facesContext.getExternalContext().getRequest();
      return request.getRemotePort();
  }
}

이 컴포넌트는 앞에서 설정한 components.xml의 설정과 동일한 기능을 제공합니다.

 

지금까지 Seam 프레임웍에서 HttpServletRequest 자원을 컴포넌트화 하는 두 가지 방법을 살펴 보았습니다. 이렇게 Seam 프레임웍은 XML 설정과 Seam 컴포넌트 Bijection 기능을 이용하여 컴포넌트를 만들고 사용하는 방법을 제공합니다.

2011년 3월 28일 월요일

JDO의 클래스 Enhance

Java EE에는 2 개의 ORM 기술이 있습니다. JPA와 JDO입니다. JPA와 JDO는 ORM기술이라는 것이 공통점이지만 목표로하는 대상 저장소에 대해서 차이를 갖습니다. JPA는 RDBMS를 데이터 저장소로 전제합니다. 그러나 JDO는 RDBMS, 파일, 엑셀, Bigtable, HBase, LDAP등을 대상 저장소로 합니다.

Google App Engine은 데이터 Google DataStore에 저장하는 방식으로 Native API, JDO 그리고 JPA를 지원합니다. 구글은 JDO 구현체로 Data NUcleus Platform을 사용합니다. 이 3가지 데이터 저장 인터페이스은 다음과 같은 연관성을 갖습니다.
  • Native API : 기존 C라이브러리 포팅
  • JDO: Native API를 프록시하여 JDO 프로바이더 개발 by Data Nucleus
  • JPA: 별도의 JPA 인터페이스가 개발된 것이라기 보다 JDO가 지원되면서 자연스럽게 지원
    • JPA의 프로바이더로 JDO가 사용됨

JDO Enhance

ORM 엔진 입장에서 가장 어려운 것은 영속성 대상 객체의 상태를 추적하는 것 입니다. 객체의 상태 추적은 가장 많은 자원을 소비할 뿐만 아니라 가장 복잡한 부분입니다.

JDO는 객체의 상태를 추적하는 것을 가볍고 쉽게 처리하기 위하여 영속성 객체 Enhancement를 사용합니다. 영속서 객체에 특정 멤버 변수와 함수를 추가하여 객체의 상태를 추적할 수 있는 방법을 제공하는 것 입니다. Hibernate 와 TopLink, JPA등이 런타임 프록시를 만들어 사용하는 방식을 사용한다면 JDO는 컴파일 시점에 코드를 삽입하는 방식을 사용하는 차이가 있습니다.

이렇게 JDO를 사용하기 위해서는 클래스 컴파일 이후에 코드를 변형하는 Enhance 작업을 수행해야 합니다.  결과적으로 JDO를 개발하기 위해서는 개발 툴 및 빌드 툴 (Ant & Maven)에 enhance를 설정할 수 있어야 합니다.

이클립스 enhance설정

Google은 Google App Engine을 지원하는 플러그인으로 Google Plugin을 제공합니다. 이 플러그인을 설정하고 구글 프로젝트를 생성할 경우에 Enhance는 기본적으로 빌드 프로세스에 설정 됩니다.

다음은 이클립스 구글 프로젝트의 빌드 속성입니다. 빌드 절차중에 Enhancer가 선택된것을 확인할 수 있습니다.


다음은 프로젝트 속성중 Google 프로젝트의 Enhance대상을 패턴으로 적용하는 설정입니다. 모든 클래스를 대상으로 할경우 컴파일 속도가 느려지는 문제가 발생할 수 있습니다. 이러한 불편을 최소화하기 위하여 Enhance대상이 되는 패키지 패턴을 등록하는 설정 창 입니다.


위와 같은 설정결과로 클래스 컴파일 후에는 다음과 같은 Enhance가 돌아가는 것을 확인 할 수 있습니다.


Maven Plugin의 Enhance 설정

프로젝트 관리 툴로 Maven을 사용할 경우 DataNucleus에서 제공하는 datanucleus plugin을 사용하게 됩니다. 이 플러그인을 사용할 경우 enhance를 구동하는 시점과 enhance 대상이 되는 클래스의 패키지를 패턴으로 지정할 수 있습니다. 다음은 컴파일 다음 단계에 model 패키지에 포함된 모든 클래스를 enhance하는 설정 입니다.


Ant에서 enhance 지정

Google App Engine SDK에서는 ant를 위한 설정을 지원합니다. 아래는 SDK를 이용하여 프로젝트 빌드 Ant을 적용한 예제 입니다. 아래 예제에서 "datanucleusenhance" 타스크의 빌드 설정은 다음과 같습니다.



 
 
        

 
  
  
   
    
   
  
  
 

 
  
 
        


JDO Enhance의 결과 산출물

위와 같은 방법으로 빌드설정을 마친 상태라면 (클래스 컴파일 후 Enhance 적용) JDO 엔티티 클래스는 컴파일 후 변경이 발생합니다. JDO 엔티티 클래스에 적용된 변화를 클래스 디컴파일을 통해서 확인 할 수 있습니다.

JDO Simple Entity Code

다음은 테스트로 작성한 JDO Entity 클래스 코드 입니다.

package artszen.gae.model;
import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

@PersistenceCapable
public class Employee {
        @PrimaryKey
        @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
        private Key key;
        
        @Persistent
        private String title;
        
        @Persistent
        private String firstName;
        
        @Persistent
        private String lastName;
        
        @Persistent
        private Date hireDate;

        public Employee() {
                super();
                // TODO Auto-generated constructor stub
        }

        public Employee(String title, String firstName, String lastName, Date hireDate) {
                super();
                this.title = title;
                this.firstName = firstName;
                this.lastName = lastName;
                this.hireDate = hireDate;
        }

        public Key getKey() {
                return key;
        }

        public void setKey(Key key) {
                this.key = key;
        }

        public String getFirstName() {
                return firstName;
        }

        public void setFirstName(String firstName) {
                this.firstName = firstName;
        }

        public String getLastName() {
                return lastName;
        }

        public void setLastName(String lastName) {
                this.lastName = lastName;
        }

        public Date getHireDate() {
                return hireDate;
        }

        public void setHireDate(Date hireDate) {
                this.hireDate = hireDate;
        }

        public String getTitle() {
                return title;
        }

        public void setTitle(String title) {
                this.title = title;
        }

        @Override
        public String toString() {
                return "Employee [key=" + key + ", title=" + title + ", firstName="
                                + firstName + ", lastName=" + lastName + ", hireDate="
                                + hireDate + "]";
        }
        
        
}

Enhance 후 클래스 코드 상태: 디컴파일 결과

다음은 위 클래스 코드의 컴파일 및 enhance 후 클래스를 디컴파일한 결과 입니다. 아래 코드를 보면 많은 멤버변수와 클래스 메소드가 추

package artszen.gae.model;

import com.google.appengine.api.datastore.Key;
import java.util.Date;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.PersistenceManager;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import javax.jdo.identity.ObjectIdentity;
import javax.jdo.spi.JDOImplHelper;
import javax.jdo.spi.PersistenceCapable.ObjectIdFieldConsumer;
import javax.jdo.spi.PersistenceCapable.ObjectIdFieldSupplier;
import javax.jdo.spi.StateManager;

@javax.jdo.annotations.PersistenceCapable
public class Employee implements javax.jdo.spi.PersistenceCapable{

  @PrimaryKey
  @Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
  private Key key;

  @Persistent
  private String title;

  @Persistent
  private String firstName;

  @Persistent
  private String lastName;

  @Persistent
  private Date hireDate;
  protected transient StateManager jdoStateManager;
  protected transient byte jdoFlags;
  private static final byte[] jdoFieldFlags;
  private static final Class jdoPersistenceCapableSuperclass;
  private static final Class[] jdoFieldTypes;
  private static final String[] jdoFieldNames = __jdoFieldNamesInit();
  private static final int jdoInheritedFieldCount;

  public Employee(){}

  public Employee(String title, String firstName, String lastName, Date hireDate){
    this.title = title;
    this.firstName = firstName;
    this.lastName = lastName;
    this.hireDate = hireDate;
  }

  public Key getKey() {
    return jdoGetkey(this);
  }

  public void setKey(Key key) {
    jdoSetkey(this, key);
  }

  public String getFirstName() {
    return jdoGetfirstName(this);
  }

  public void setFirstName(String firstName) {
    jdoSetfirstName(this, firstName);
  }

  public String getLastName() {
    return jdoGetlastName(this);
  }

  public void setLastName(String lastName) {
    jdoSetlastName(this, lastName);
  }

  public Date getHireDate() {
    return jdoGethireDate(this);
  }

  public void setHireDate(Date hireDate) {
    jdoSethireDate(this, hireDate);
  }

  public String getTitle() {
    return jdoGettitle(this);
  }

  public void setTitle(String title) {
    jdoSettitle(this, title);
  }

  public String toString(){
    return "Employee [key=" + jdoGetkey(this) + ", title=" + jdoGettitle(this) + ", firstName=" + 
      jdoGetfirstName(this) + ", lastName=" + jdoGetlastName(this) + ", hireDate=" + 
      jdoGethireDate(this) + "]";
  }

  static{
    jdoFieldTypes = __jdoFieldTypesInit();
    jdoFieldFlags = __jdoFieldFlagsInit();
    jdoInheritedFieldCount = __jdoGetInheritedFieldCount();
    jdoPersistenceCapableSuperclass = __jdoPersistenceCapableSuperclassInit();
    JDOImplHelper.registerClass(___jdo$loadClass("swm.enterprise.rest.jdodemo.model.Employee"), jdoFieldNames, jdoFieldTypes, jdoFieldFlags, jdoPersistenceCapableSuperclass, new Employee());
  }

  public void jdoCopyKeyFieldsFromObjectId(PersistenceCapable.ObjectIdFieldConsumer fc, Object oid){
    if (fc == null)
      throw new IllegalArgumentException("ObjectIdFieldConsumer is null");
    if (!(oid instanceof ObjectIdentity))
      throw new ClassCastException("oid is not instanceof javax.jdo.identity.ObjectIdentity");
    ObjectIdentity o = (ObjectIdentity)oid;
    fc.storeObjectField(2, o.getKey());
  }

  protected void jdoCopyKeyFieldsFromObjectId(Object oid){
    if (!(oid instanceof ObjectIdentity))
      throw new ClassCastException("key class is not javax.jdo.identity.ObjectIdentity or null");
    ObjectIdentity o = (ObjectIdentity)oid;
    this.key = ((Key)o.getKey());
  }

  public final void jdoCopyKeyFieldsToObjectId(Object oid){
    throw new JDOFatalInternalException("It's illegal to call jdoCopyKeyFieldsToObjectId for a class with SingleFieldIdentity.");
  }

  public final void jdoCopyKeyFieldsToObjectId(PersistenceCapable.ObjectIdFieldSupplier fs, Object paramObject)
  {
    throw new JDOFatalInternalException("It's illegal to call jdoCopyKeyFieldsToObjectId for a class with SingleFieldIdentity.");
  }

  public final Object jdoGetObjectId(){
    if (this.jdoStateManager != null)
      return this.jdoStateManager.getObjectId(this);
    return null;
  }

  public final Object jdoGetVersion(){
    if (this.jdoStateManager != null)
      return this.jdoStateManager.getVersion(this);
    return null;
  }

  protected final void jdoPreSerialize(){
    if (this.jdoStateManager != null)
      this.jdoStateManager.preSerialize(this);
  }

  public final PersistenceManager jdoGetPersistenceManager(){
    return this.jdoStateManager != null ? this.jdoStateManager.getPersistenceManager(this) : null;
  }

  public final Object jdoGetTransactionalObjectId(){
    return this.jdoStateManager != null ? this.jdoStateManager.getTransactionalObjectId(this) : null;
  }

  public final boolean jdoIsDeleted(){
    return this.jdoStateManager != null ? this.jdoStateManager.isDeleted(this) : false;
  }

  public final boolean jdoIsDirty(){
    if (this.jdoStateManager != null)
      return this.jdoStateManager.isDirty(this);
    return false;
  }

  public final boolean jdoIsNew(){
    return this.jdoStateManager != null ? this.jdoStateManager.isNew(this) : false;
  }

  public final boolean jdoIsPersistent(){
    return this.jdoStateManager != null ? this.jdoStateManager.isPersistent(this) : false;
  }

  public final boolean jdoIsTransactional(){
    return this.jdoStateManager != null ? this.jdoStateManager.isTransactional(this) : false;
  }

  public void jdoMakeDirty(String fieldName){
    if (this.jdoStateManager != null)
      this.jdoStateManager.makeDirty(this, fieldName);
  }

  public final Object jdoNewObjectIdInstance(){
    return new ObjectIdentity(getClass(), this.key);
  }

  public final Object jdoNewObjectIdInstance(Object key){
    if (key == null)
      throw new IllegalArgumentException("key is null");
    if (!(key instanceof String))
      return new ObjectIdentity(getClass(), key);
    return new ObjectIdentity(getClass(), (String)key);
  }

  public final void jdoProvideFields(int[] indices){
    if (indices == null)
      throw new IllegalArgumentException("argment is null");
    int i = indices.length - 1;
    if (i >= 0)
      do
      {
        jdoProvideField(indices[i]);
        i--;
      }
      while (i >= 0);
  }

  public final void jdoReplaceFields(int[] indices){
    if (indices == null)
      throw new IllegalArgumentException("argument is null");
    int i = indices.length;
    if (i > 0){
      int j = 0;
      do{
        jdoReplaceField(indices[j]);
        j++;
      }
      while (j < i);
    }
  }

  public final void jdoReplaceFlags(){
    if (this.jdoStateManager != null)
      this.jdoFlags = this.jdoStateManager.replacingFlags(this);
  }

  public final synchronized void jdoReplaceStateManager(StateManager sm){
    if (this.jdoStateManager != null){
      this.jdoStateManager = this.jdoStateManager.replacingStateManager(this, sm);
    }
    else{
      JDOImplHelper.checkAuthorizedStateManager(sm);
      this.jdoStateManager = sm;
      this.jdoFlags = 1;
    }
  }

  public boolean jdoIsDetached(){
    return false;
  }

  public javax.jdo.spi.PersistenceCapable jdoNewInstance(StateManager sm){
    Employee result = new Employee();
    result.jdoFlags = 1;
    result.jdoStateManager = sm;
    return result;
  }

  public javax.jdo.spi.PersistenceCapable jdoNewInstance(StateManager sm, Object obj){
    Employee result = new Employee();
    result.jdoFlags = 1;
    result.jdoStateManager = sm;
    result.jdoCopyKeyFieldsFromObjectId(obj);
    return result;
  }

  public void jdoReplaceField(int index){
    if (this.jdoStateManager == null)
      throw new IllegalStateException("state manager is null");
    switch (index){
    case 0:
      this.firstName = this.jdoStateManager.replacingStringField(this, index);
      break;
    case 1:
      this.hireDate = ((Date)this.jdoStateManager.replacingObjectField(this, index));
      break;
    case 2:
      this.key = ((Key)this.jdoStateManager.replacingObjectField(this, index));
      break;
    case 3:
      this.lastName = this.jdoStateManager.replacingStringField(this, index);
      break;
    case 4:
      this.title = this.jdoStateManager.replacingStringField(this, index);
      break;
    default:
      throw new IllegalArgumentException("out of field index :" + index);
    }
  }

  public void jdoProvideField(int index){
    if (this.jdoStateManager == null)
      throw new IllegalStateException("state manager is null");
    switch (index){
    case 0:
      this.jdoStateManager.providedStringField(this, index, this.firstName);
      break;
    case 1:
      this.jdoStateManager.providedObjectField(this, index, this.hireDate);
      break;
    case 2:
      this.jdoStateManager.providedObjectField(this, index, this.key);
      break;
    case 3:
      this.jdoStateManager.providedStringField(this, index, this.lastName);
      break;
    case 4:
      this.jdoStateManager.providedStringField(this, index, this.title);
      break;
    default:
      throw new IllegalArgumentException("out of field index :" + index);
    }
  }

  protected final void jdoCopyField(Employee obj, int index){
    switch (index)
    {
    case 0:
      this.firstName = obj.firstName;
      break;
    case 1:
      this.hireDate = obj.hireDate;
      break;
    case 2:
      this.key = obj.key;
      break;
    case 3:
      this.lastName = obj.lastName;
      break;
    case 4:
      this.title = obj.title;
      break;
    default:
      throw new IllegalArgumentException("out of field index :" + index);
    }
  }

  public void jdoCopyFields(Object obj, int[] indices){
    if (this.jdoStateManager == null)
      throw new IllegalStateException("state manager is null");
    if (indices == null)
      throw new IllegalStateException("fieldNumbers is null");
    if (!(obj instanceof Employee))
      throw new IllegalArgumentException("object is not an object of type swm.enterprise.rest.jdodemo.model.Employee");
    Employee other = (Employee)obj;
    if (this.jdoStateManager != other.jdoStateManager)
      throw new IllegalArgumentException("state managers do not match");
    int i = indices.length - 1;
    if (i >= 0)
      do{
        jdoCopyField(other, indices[i]);
        i--;
      }
      while (i >= 0);
  }

  private static final String[] __jdoFieldNamesInit(){
    return new String[] { "firstName", "hireDate", "key", "lastName", "title" };
  }

  private static final Class[] __jdoFieldTypesInit(){
    return new Class[] { ___jdo$loadClass("java.lang.String"), ___jdo$loadClass("java.util.Date"),             
           ___jdo$loadClass("com.google.appengine.api.datastore.Key"), 
           ___jdo$loadClass("java.lang.String"), ___jdo$loadClass("java.lang.String") };
  }

  private static final byte[] __jdoFieldFlagsInit(){
    return new byte[] { 21, 21, 24, 21, 21 };
  }

  protected static int __jdoGetInheritedFieldCount(){
    return 0;
  }

  protected static int jdoGetManagedFieldCount(){
    return 5;
  }

  private static Class __jdoPersistenceCapableSuperclassInit(){
    return null;
  }

  public static Class ___jdo$loadClass(String className){
    try {
      return Class.forName(className); } catch (ClassNotFoundException e) {  }
    throw new NoClassDefFoundError(e.getMessage());
  }

  private Object jdoSuperClone() throws CloneNotSupportedException {
    Employee o = (Employee)super.clone();
    o.jdoFlags = 0;
    o.jdoStateManager = null;
    return o;
  }

  private static String jdoGetfirstName(Employee objPC) {
    if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) && (!objPC.jdoStateManager.isLoaded(objPC, 0)))
      return objPC.jdoStateManager.getStringField(objPC, 0, objPC.firstName);
    return objPC.firstName;
  }

  private static void jdoSetfirstName(Employee objPC, String val) {
    if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
      objPC.jdoStateManager.setStringField(objPC, 0, objPC.firstName, val);
    else
      objPC.firstName = val;
  }

  private static Date jdoGethireDate(Employee objPC) {
    if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) && 
        (!objPC.jdoStateManager.isLoaded(objPC, 1)))
      return (Date)objPC.jdoStateManager.getObjectField(objPC, 1, objPC.hireDate);
    return objPC.hireDate;
  }

  private static void jdoSethireDate(Employee objPC, Date val) {
    if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
      objPC.jdoStateManager.setObjectField(objPC, 1, objPC.hireDate, val);
    else
      objPC.hireDate = val;
  }

  private static Key jdoGetkey(Employee objPC) {
    return objPC.key;
  }

  private static void jdoSetkey(Employee objPC, Key val) {
    if (objPC.jdoStateManager == null)
      objPC.key = val;
    else
      objPC.jdoStateManager.setObjectField(objPC, 2, objPC.key, val);
  }

  private static String jdoGetlastName(Employee objPC) {
    if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) && 
        (!objPC.jdoStateManager.isLoaded(objPC, 3)))
      return objPC.jdoStateManager.getStringField(objPC, 3, objPC.lastName);
    return objPC.lastName;
  }

  private static void jdoSetlastName(Employee objPC, String val) {
    if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
      objPC.jdoStateManager.setStringField(objPC, 3, objPC.lastName, val);
    else
      objPC.lastName = val;
  }

  private static String jdoGettitle(Employee objPC) {
    if ((objPC.jdoFlags > 0) && (objPC.jdoStateManager != null) && 
        (!objPC.jdoStateManager.isLoaded(objPC, 4)))
      return objPC.jdoStateManager.getStringField(objPC, 4, objPC.title);
    return objPC.title;
  }

  private static void jdoSettitle(Employee objPC, String val) {
    if ((objPC.jdoFlags != 0) && (objPC.jdoStateManager != null))
      objPC.jdoStateManager.setStringField(objPC, 4, objPC.title, val);
    else
      objPC.title = val;
  }
}

2011년 3월 16일 수요일

sudo 사용자 등록 (in CentOS)

일반 사용자가 루트 권한을 획득하기 위해서는 su 명령을 사용해야 합니다. 사실 매번
 루트 권한을 얻기 위해서 su를 수행하는 것은 불편하죠. Ubunto를 사용할 때 가장 편
리한 명령은 아마도 sudo일 것입니다. sudo는 일반사용자가 루트 권한을 임시적으로 획득하여 특정 명령을 할 수 있도록 합니다.

Ubuntu에서는 처음부터 일반 사용자가 sudo를 사용할 수 있도록 되어 있지만 CentOS >등 다른 리눅스 배포판에서는 sudo를 기본적으로 사용할 수 없도록 되어 있습니다.

[was@centos ~]$ sudo more /etc/sysctl.conf
[sudo] password for was:
was is not in the sudoers file.  This incident will be reported.
[was@centos ~]$

위 예를 보면 weblogic 사용자는 sudoers 파일에 등록되어 있지 않기 때문에 sudo를 사용할 수 없다는 메세지를 확인할 수 있습니다.

일반사용자가 sudo 명령어를 사용하기 위해서는 /etc/sudoers에 등록되어 있어야 합니다.

/etc/sudoers에 일반 사용자를 등록하는 방법은 다음과 같습니다.

  1. root로 사용자 전환 (su -)
  2. /etc/sudoers의 파일 permission 변경
    • chmod u+w /etc/sudoers
  3. /etc/sudoers에 일반 사용자 등록
  4. /etc/sudoers 퍼미션 원복
    • /etc/sudoers는 440 퍼미션이어야 함
    • chmod u-w /etc/sudoers
  5. sudo 테스트


/etc/sudoers의 초기 퍼미션은 다음과 같습니다.

[devtainer@centos ~]$ ls -al /etc/sudoers
-r--r----- 1 root root 3217  3월 16 14:09 /etc/sudoers

파일 수정을 위하여 /etc/sudoers의 퍼미션을 수정해야 합니다. (root 전환 후 퍼미션 변경)

[devtainer@centos ~]$ su -
암호:
[root@centos ~]# chmod u+w /etc/sudoers
[root@centos ~]# ls -al /etc/sudoers
-rw-r----- 1 root root 3217  3월 16 14:09 /etc/sudoers

/etc/sudoers에 사용자 등록 방법은 다음과 같습니다. 다음과 같은 설정을 /etc/sudoers의 하단에 추가하면 설정은 완료됩니다.

case 1. 특정 사용자가 sudo를 사용할 수 있하는 설정
devtainer       ALL=(ALL)       ALL

case 2. 그룹에 포함된 모든 사용자가 sudo를 사용할 수 있하는 설정
%wheel        ALL=(ALL)       ALL

case 3. 패스워드 생략 설정
%wheel        ALL=(ALL)       NOPASSWD: ALL
devtainer        ALL=(ALL)       NOPASSWD: ALL

case 4. sudo를 사용하여 cd 마운트, 언마운트 가능하도록 설정
%users  ALL=/sbin/mount /mnt/cdrom, /sbin/umount /mnt/cdrom
#users 그룹의 멤버는 sudo를 사용하여 cd롬 마운트와 언마운트만 허용

이제 사용자 등록이 완료된 상태 입니다. /etc/sudoers 의 퍼미션은 440이어야 합니다. 다음과 같이 퍼미션이 440이 아닐 경우에 에러가 발생됩니다. /etc/sudoers파일의 퍼미션을 440으로 변경하면 sudo명령이 정상적으로 동작하는 것을 확인할 수 있습니다.

[devtainer@centos ~]$ sudo tail /etc/sudoers
sudo: /etc/sudoers is mode 0640, should be 0440
sudo: no valid sudoers sources found, quitting
[devtainer@centos ~]$ su -
암호:
[root@centos ~]# chmod u-w /etc/sudoers
[root@centos ~]# su - devtainer
[devtainer@centos ~]$ sudo tail /etc/sudoers
[sudo] password for devtainer:
# %wheel        ALL=(ALL)       NOPASSWD: ALL

## Allows members of the users group to mount and unmount the
## cdrom as root
# %users  ALL=/sbin/mount /mnt/cdrom, /sbin/umount /mnt/cdrom

## Allows members of the users group to shutdown this system
# %users  localhost=/sbin/shutdown -h now
devtainer       ALL=(ALL)       ALL

2011년 3월 14일 월요일

CentOS DVD ISO 파일 다운로드 미러 사이트

다음은 CentOS의 DVD ISO를 다운로드 받을 수 있는 미러사이트 URL 입니다.