Java8에 들어간 stream, parallelStream이 좋다는 이야기만 듣고 대충 개념만 이해한 상태에서...
"그냥 좋겠지"란생각으로 쓰려다...

간단하게 테스트 해보고 정리 ^^ 참고로 로컬 PC에서 돌렸으며, 로컬 PC의 물리 cpu 코어 개수는 4개이다.




10만건, 100만건 테스트를 그냥 간단하게 2, 3회 정도 실시해 본 결과는 아래와 같다.


10만

##  1 소요시간(초.0f) : 0.005초

##  2 소요시간(초.0f) : 0.058초

##  3 소요시간(초.0f) : 0.013초


##  1 소요시간(초.0f) : 0.004초

##  2 소요시간(초.0f) : 0.042초

##  3 소요시간(초.0f) : 0.007초


100만

##  1 소요시간(초.0f) : 0.015초

##  2 소요시간(초.0f) : 0.063초

##  3 소요시간(초.0f) : 0.056초


##  1 소요시간(초.0f) : 0.015초

##  2 소요시간(초.0f) : 0.062초

##  3 소요시간(초.0f) : 0.058초


결과 1이 단순 for loop, 결과 2가 stream, 결과 3이 parallelStream 이다.

10만건이 되었든 100만건이 되었든... 단순 for loop가 빠르다.

이로 인해 내릴 수 있는 결론은 loop문 안에서 처리되는 비즈니스 로직에 block이나 delay 요소가 없다면 단순 for loop로 돌리는게 더 빠를 수 있다. stream이나 parallelStream은 list를 stream으로 바꾸고 내부적으로 라이브러리를 사용하는 비용이 소모 되므로 단순 작업에서는 더 느릴 수 있다고 어디선가 본 것 같다 ^^;;


그럼 loop문 안에 인위적으로 sleep을 넣는다면? 소스 코드 안의 sleep을 주석을 풀고 실행하게 되면 결과는 아래와 같다.


10만

##  1 소요시간(초.0f) : 1.001초

##  2 소요시간(초.0f) : 1.037초

##  3 소요시간(초.0f) : 0.144초


##  1 소요시간(초.0f) : 1.0초

##  2 소요시간(초.0f) : 1.077초

##  3 소요시간(초.0f) : 0.161초


100만

##  1 소요시간(초.0f) : 10.012초

##  2 소요시간(초.0f) : 10.09초

##  3 소요시간(초.0f) : 1.289초


##  1 소요시간(초.0f) : 10.004초

##  2 소요시간(초.0f) : 10.105초

##  3 소요시간(초.0f) : 1.291초


단순 for loop와 stream은 거의 차이가 없으며 parallelStream이 압도적으로 빠르다.

list를 parallelStream 으로 변환하고 라이브러리를 로드하고 사용하는 비용을 쓰더라도 loop문 안에서 지연이 발생해서 loop를 도는 속도가 현저히 떨어지게 된다면 병렬로 나눠 처리하는 것이 좋다는 결론을 얻게 된다.


하지만 loop 안에서 지연이 발생한다고 해서 무조건 parallelStream 을 쓰는 것이 좋을까?

경험 상 병렬로 작업을 처리한다 하더라도 loop 내부에 DB Insert, Update, Delete와 같은 것이 있다면 DB에 크나큰 부담으로 시스템 장애를 일으킬 수 있으니 조심해야 한다.

또한 parallelStream 이 CPU를 점유할 경우 다른 parallelStream 작업에도 영향을 미칠 수 있으므로 조심 ^^


덧. for loop보다 stream이 이론 상으로 더 빨라야 하는 것 같은데... 오히려 stream이 느리네..

stream이 더 빠른 경우가 무엇인지에 대한 고민이 필요 ^^;;



저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

보통 개발 시에 파일 업로드를 하려 하면 html form에서 multipart/form-data로 파일을 선택해서 업로드 하고, 이를 서버 단에서 받아 처리를 하게 된다.

하지만 이런 방법이 아니라 원격지의 이미지 파일을 읽어온 후 필요 시 리사이지, 그리고 나서 다시 다른 곳에 있는 서버로 파일을 업로드 하는 기능이 필요해 개발을 하다 보니, 많이 사용되는 방법이 아니기에 정리해 둔다.


<원격지에서 파일을 읽어 들여 파일 객체로 만든 후 리사이징, 업로드하기>


<이미지 리사이징>



저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

구매후기 이미지 업로드에서 오류가 나고 있어서 해당 내용을 검토 중에 알게 된 내용을 정리합니다.

기본적으로 아래와 같은 방식으로 업로드가 되고 있습니다.


브라우저 -> php -> Java


그런데 문제는 어느 한쪽의 문제가 아니라 php, Java 둘다 문제가 발생...

그리고 문제의 원인은 둘다 Version Up이였습니다.


1. 먼저 Java

Error Message가 "Required MultipartFile parameter 'file' is not present" 라고 발생.

구글링 하니 http://stackoverflow.com/questions/25830503/spring-mvc-http-status-400-required-multipartfile-parameter-file-is-not-pre 가 검색 됨.


아래와 같은 방식으로 수정


AS-IS : <bean id="multipartResolver" class="**org.springframework.web.multipart.commons.CommonsMultipartResolver**" />

TO-BE : <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />


2. 다음으로 PHP


PHP에서 Java 쪽으로 파일을 올릴 때는 CURL을 사용했는데 기존에 /tmp에 올라온 임시 파일을 읽어 들일 때는 @를 사용해서 리소스를 읽어 들였음.

php 5.5.0 기준 Deprecated 된 기능이라서 다른 방법으로 수정



저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

Spring Batch를 그닦 많이 써본 경험이 없지만, 특정 요구 사항이 있어서 개발하게 되었습니다.

작업을 하면서 겪은 오류를 어떻게 해결 했는지 History 및 Backup을 위해 적어 둡니다.


보통 배치는 Reader와 Writer로 구분합니다.

특정 정보를 Reader에서 읽어 들여서 필요한 내용을 가공하고 Writer에서 필요한 행동을 합니다.


그런데 Reader에서 정보를 읽어 들이고 가공하는 시간이 오래 걸리면서 Writer에서 DB Connection을 못 찾게 되고, Writer 작업을 수행하지 못하게 되는 경우가 발생했습니다.


구글링을 한 결과 "autoReconnect 값을 true로 해라", "validationQuery를 날려라" 등등이 나왔지만 유효하지 않은 해결책이였습니다.


결국 찾아낸 해결 방법은 applicationContext.xml에서 database 관련 다음의 옵션을 수정하는 거였습니다.


testOnBorrow : true


이 옵션은 커넥션 풀에서 커넥션을 가져올 경우 유효한지 검사하는 것인데

일반적인 웹서비스에서는 커넥션이 자주 맺고 끊어져서 이 옵션과 상관없이 잘 동작 하는데

배치와 같이 특별한 경우에는 기존 커넥션을 사용하려다가 연결 해제된 커넥션을 검증 안하고 쓰면서 발생한 문제였습니다.

즉 testOnBorrow가 false인 경우에는 유효하지 않은 커넥션을 사용할 수 있으므로, Writer에서 에러가 종종 발생하게 된 것입니다.


저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

List에서 불필요한 item을 제거하기 위해 loop를 돌리게 되면 제거 후 size가 맞지 않는 일로 인해 null point exception을 만나기가 쉽다.

이럴 경우 제대로 된 방법이 무엇인지는 모르지만...

실제로 돌려보고 Exception이 나지 않은 방법을 찾아 정리 차원에서 남겨 둔다.


검색해서 참고한 URL은 https://stackoverflow.com/questions/17279519/removing-items-from-list-in-java/17279565#17279565?newreg=0dee1db24aaa4154a0cebdc173ed5aeb


List<Object> toRemove = new ArrayList<Object>();

for(Object a: list){

if(a.getXXX().equalsIgnoreCase("AAA")){

toRemove.add(a);

}

}

list.removeAll(toRemove);


다른 방법들은 다 해 봤지만 null point exception 발생 함.


저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

MySQL에 DateTime 필드에 값이 없을 경우 기본 값으로 "0000-00-00 00:00:00" 들어가는 경우가 있다.

이럴 경우 쿼리해 온 값이 Java의 Model 객체에 Setter로 값을 넣는 과정에서 에러가 발생한다.

에러 메시지는 다음과 같다.


"Cannot convert value '0000-00-00 00:00:00' from column 4 to TIMESTAMP"


뭐 '0000-00-00 00:00:00' 값은 timestamp 값으로 바꿀 수 없다는 메시지 같은데...

이럴 경우 어떻게 해결해야 하나 고민하다가 구글링을 통해 알게 된 내용을 정리한다.

보통 Model 객체의 Setter를 아래와 같이 작성하게 된다.



setter에서 Date 객체로 받아야 하는데 값을 넘겨줄 수 없기에 이런 경우에는 DB 접속 옵션을 아래처럼 바꾸어 주면 된다.


jdbc:mysql://localhost/myDatabase?zeroDateTimeBehavior=convertToNull


zeroDateTime일 경우 null을 반환하라는 의미같은데

위와 같이 옵션을 설정한 후 디버깅을 해 보면 zeroDateTime일 경우 setter에 실제로 null 값이 들어오게 된다.


참고 : http://stackoverflow.com/questions/1363527/cannot-convert-0000-00-00-000000-to-timestamp

저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

다국어 사용을 위해서 그동안 Struts2 기반 MessageUtil만 사용하다가 Spring 기반으로 변경하기 위해 구글링, 작업한 내용을 정리 차원에서 올립니다.


1. applicationContext.xml에 다음 내용을 추가


<!-- for MessageUtil -->

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

        <property name="basenames">

            <list>

                <value>/WEB-INF/messages/messages</value>

            </list>

        </property>

        <property name="cacheSeconds" value="100000" />

        <property name="defaultEncoding" value="UTF-8" />

    </bean>

    <bean id="messageSourceAccessor" class="org.springframework.context.support.MessageSourceAccessor">

        <constructor-arg ref="messageSource"/>

    </bean>

    <bean id="messageUtil" class="package.path.MessageUtil">

        <property name="messageSourceAccessor" ref="messageSourceAccessor"/>

    </bean>


2. MessageUtil




참고 사이트

http://springsource.tistory.com/113

http://blog.naver.com/pureb612b/10120505318

http://zinlee.tistory.com/204

저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret

구글링을 통해서 GPS의 좌표를 통한 거리 구하는 공식을 어렵게 찾아 적용했다.

내용은 아래와 같다.


if(oldCoordinate != null) {

// TODO 거리 누적...

double theta = oldCoordinate.getLongitude() - Double.parseDouble(lp.getLongitude());

double dist = Math.sin(Utility.deg2rad(oldCoordinate.getLatitude())) * Math.sin(Utility.deg2rad(Double.parseDouble(lp.getLatitude()))) +

Math.cos(Utility.deg2rad(oldCoordinate.getLatitude())) * Math.cos(Utility.deg2rad(Double.parseDouble(lp.getLatitude()))) * Math.cos(Utility.deg2rad(theta));

if(dist < -1 || dist > 1) {

logger.d("dist : %s", dist);

}

dist = Math.acos(dist);

dist = Utility.rad2deg(dist);

dist = dist * 60 * 1.1515; // statute miles. 단위는 기본 마일.

dist = dist * 1.609344;

if(dist != Double.NaN) {

totalDist += dist*1000;

}

} else {

oldCoordinate = new Coordinate();

}

if(!"".equals(lp.getAltitude())) oldCoordinate.setAltitude(Double.parseDouble(lp.getAltitude()));

if(!"".equals(lp.getLatitude())) oldCoordinate.setLatitude(Double.parseDouble(lp.getLatitude()));

if(!"".equals(lp.getLongitude())) oldCoordinate.setLongitude(Double.parseDouble(lp.getLongitude()));


하지만 dist 값이 -1보다 작거나 1보다 큰 값이 반환되는 경우 Math.acos에 잘못된 파라미터로 들어가게되고, acos는 NaN을 반환하게 된다.

팀 동료와 고민하면서 구글링하게 된 결과 나온 것은 아래와 같다.


float[] distance = new float[3];

if(oldCoordinate != null) {

Location.distanceBetween(oldCoordinate.getLatitude(), oldCoordinate.longitude,

Double.parseDouble(lp.getLatitude()), Double.parseDouble(lp.getLongitude()), distance);

totalDist += distance[0];

} else {

oldCoordinate = new Coordinate();

}

if(!"".equals(lp.getAltitude())) oldCoordinate.setAltitude(Double.parseDouble(lp.getAltitude()));

if(!"".equals(lp.getLatitude())) oldCoordinate.setLatitude(Double.parseDouble(lp.getLatitude()));

if(!"".equals(lp.getLongitude())) oldCoordinate.setLongitude(Double.parseDouble(lp.getLongitude()));


그냥 Location 안에 static으로 선언 된 distanceBetween 메소드를 이용하면 된다.

알기까지 무지막지한 삽질...

알고나면 허무한 ㅠㅠ

저작자 표시 비영리 변경 금지
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret
Site-mesh의 decorators.xml 파일에서 excludes를 정의해서 사용하려 했지만
여전희 decorator 파일이 적용 되는 상황이 발생했다.
여기 저기 구글링 해 봤지만 내 검색 능력의 한계인가 잘 찾질 못했다.
결국 사내 아는 분에게 문의해서 원인을 알게 되었다.


sitemesh.xml decorators.xml


위의 decorators.xml에서 uninstall 부분만 decorator를 적용하지 않으려 했지만 처음에는 적용 되지 않았고,
이에 sitemesh.xml의 아래 부분이 추가 되면서 문제가 해결 되었다.

<property name="decorators" value="/WEB-INF/decorators.xml" />
<excludes file="${decorators}" />

삽질은 정말 해도 해도 끝이 없는거 같다.
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret
실 서비스 도중에 대량의 작업을 동기식으로 작업하려면 부담이 되는 경우가 많다.
이럴 경우에는 비동기식으로 작업을 하게 되는데, 보통 Queue에 작업해야 할 목록을 집어 넣어두고, 나중에 Cosumer가 해당 작업을 하도록 만드는 것이 일반적이다.
이런 작업을 하는데 유용한 도구가 ActiveMQ이며 이번 주소록 프로젝트를 하면서 ActiveMQ를 사용하게 되어 정리차원에서 블로그에 올려본다.

설치하기 이전에 우선 환경을 셋팅해야 한다.

JDK는 1.5.x 버젼 이상 설치되어 있어야 하며, " JAVA_HOME" 환경 변수는 JDK가 설치된 경로로 잡혀 있어야 한다. Maven은 1.0.2 이상 설치되어 있어야 한다.(Maven은 소스 설치 또는 개발자 버젼 설치를 위해 필요한 듯...)
또한 관련 JAR 파일들은 미리 classpath에 있어야 한다.
따라서 Maven을 사용한다면 dependencies 아래 아래와 같이 추가하면 된다.



<!-- For Active MQ -->
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-core</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-web</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.xbean</groupId>
    <artifactId>xbean-spring</artifactId>
    <version>3.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>2.5.6.SEC01</version>
</dependency>

 환경 셋팅을 다했다면 ActiveMQ를 다운로드 해서 설치해야 한다. 다운로드는 http://activemq.apache.org/download.html 에서 다운 받을 수 있으며, 현재 기준으로 안정 버젼은 5.3.0이다.

설치는 다운로드 받은 파일을 적당한 곳에 풀어주면 된다.

설치 후 ActiveMQ를 실행 시킨다.

cd [activemq_install_dir]
bin\activemq

위와 같이 하던가 아니면 설치 디렉토리의 activemq.bat 파일을 더블클릭 해도 된다.
Unix(Linux) 환경에서는 아래와 같이 하면 된다.

cd [activemq_install_dir]
bin/activemq

OR

bin/activemq > /tmp/smlog  2>&1 &;
Note: /tmp/smlog may be changed to another file name.

ActiveMQ의 자세한 Start 방법은 http://activemq.apache.org/version-5-getting-started.html#Version5GettingStarted-StartingActiveMQ 를 참고하면 된다.

ActiveMQ를 실행 후 제대로 돌고 있는지 확인하기 위해서는 61616 포트를 확인해 보면 된다.

Windows

netstat -an|find "61616"
  TCP    0.0.0.0:61616          0.0.0.0:0              LISTENING

Unix(Linux)

netstat -an|grep 61616
tcp        0      0 :::61616                    :::*                        LISTEN 

위와 같이 61616 포트를 Listen 하고 있다면 제대로 ActiveMQ가 실행 되고 있는 것이다.
또한 위와 같이 ActiveMQ가 실행되고 있는 것이 확인되면 아래의 url로 접근해서 Queue의 상태를 확인해 볼 수 있다.

http://localhost:8161/admin

물론 해당 서버가 로컬이 아니라면 위의 localhost 도메인을 적절히 수정해야 할 것이다.

기본적으로 ActiveMQ를 위와 같은 방식으로 띄운다면 ActiveMQ가 설치된 디렉토리의 conf 디렉토리 밑에 있는 activemq.xml 파일을 가지고 뜬다. 하지만 원하는 디렉토리 밑에 있는 activemq.xml을 참조하도록 하기 위해서는 아래와 같이 띄우면 된다.

activemq설치디렉토리/bin/activemq xbean:file:원하는소스가 있는 디렉토리

activemq.xml에서는 기본적으로 아래와 같은 셋팅을 하면 된다.


policyEntry 부분에 queue 이름만 정확히 넣어 주고 여기서 셋팅한 이름을 applicationContext.xml 파일에서 사용하면 된다.
jetty 부분은 admin으로 모니터링 할 내용을 적어 둔 것이다.
덧. activemq.base는 상단에서 단순히 properties 파일 선언하고 해당 부분에서 ActiveMQ가 설치된 디렉토리를 지정한 것이다.

* applicationContext.xml 파일의 설정


위와 같이 설정이 모두 마쳐 졌다면 프로그램을 만들 차례이다.
우선 가장 기본이 되는 리스너는 다음과 같은 형식으로 만들어 주면 된다.


위 클래스는 주소를 수정할 경우 registerQueue 메소드를 호출해서 Queue에 한개의 아이디를 쌓고, Listener에서 Queue에 데이터가 들어오면 pullFromQueueAndModifyAddress 메소드를 호출해서 수정한 아이디의 주소를 이용하여 친구들 주소들 모두를 수정해 주는 것이다.

우선 applicationContext.xml에서 선언한 jmsTemplate 관련 setter를 선언해 주고 기본이 되는 2가지 메소드를 만들어 주면 된다.
registerQueue 메소드는 Queue에 등록하는 메소드이며 메소드 등록 시 jmsTemplate를 이용하면 된다. 해당 메소드는 별도의 외부 클래스에서 호출해 주는 것이므로 알아서 테스트 해 보면 될 것이다.
Queue에 데이터가 등록되면 Listener가 알아서 감지하고 pullFromQueueAndModifyAddress 메소드가 실행 된다.(applicationContext.xml의 listener list에 선언한 부분)
안의 비즈니스 로직은 알아서 만들면 되는 것이고 이렇게 되면 http://localhost:8161/admin/queues.jsp 에서 해당 내용이 제대로 적용 되었는지 확인해 볼 수 있다.

보통 Queue에 Data가 쌓이게 되면 바로 바로 Consumer가 작동하게 되므로 서버를 Debug 모드로 돌려 놓고 BreakPoint를 찍어 놓았다면 registerQueue를 실행하는 즉시 디버거가 작동하게 될 것이다.

덧. 백업 용도로 정신 없이 작성하다 보니 내가 써 놓고도 내가 무슨 말이지 모르겠다 ^^ 적당히 알아서 들으시면 될 것 같다. 삽질은 프로그램의 기본 ^^
덧2. ActiveMQ의 설정 및 시작 방법 등 자세한 내용은 http://users.handysoft.co.kr/~wsko/articles/activemq-guide/index.html에 더 잘 나온 것 같다.



신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret
매일 프로그램 삽질은 하고 있지만 이번 건은 유난히 더 삽질이 길었다.
3일 동안 아무런 일도 못하고 이 일에만 매달렸다.
다른 일을 하긴 해야 하지만, 개발자 자존심에 해결하지 못하고 넘어가기에도 뭐 하고...
결국에는 해결하게 되었지만 아무것도 아닌 원인 때문에 해결하고도 짜증이 난다.

증상은 다음과 같다.




소스는 간단하다.
그냥 특정 쿠키를 생성한 다음 response에 실어 보내는 것이다.

그런데 로컬과 서버의 결과물이 달랐다.

* 로컬의 결과물
cookieKey1=bla...bla...;
cookieKey2=bla...bla...;

* 서버의 결과물
cookieKey1="bla...bla...";
cookieKey2="bla...bla...";

둘 간의 차이점은 value 앞 뒤로 쌍따옴표(")가 있느냐 없느냐이다.
큰 차이가 아닐지 모르지만 인증 관련된 작업을 다루는데 있어서 해당 값으로 인해 로그인이 되기도 하고 안되기도 하는 부분이라서 매우 골치 아픈 부분이었다.

소스를 이리도 뜯어보고 저리도 뜯어 봤지만 해결은 되지 않았고,
Googling을 아무리 해 봐도 답은 나오지 않았다.(내 능력 부족 -.-;;)

결국 회사 내 다른 분의 도움을 얻어 로컬과 서버의 tomcat 버젼의 차이때문에 생기는 것임을 알게 되었고 서버의 tomcat 버젼을 5.5.27로 downgrade해서 문제를 해결하게 되었다

관련된 내용은 http://tomcat.apache.org/tomcat-5.5-doc/changelog.html 의 46587 에서 확인해 볼 수 있다.

신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

트랙백이 하나이고 , 댓글이 없습니다.
secret
프로젝트를 진행하면서 BTS로 _, %로 검색시 검색이 제대로 안된다는 내용이 등록되었다.
해당 내용을 해결하기 위해 구글링 하던 도중 아래와 같은 내용을 찾게 되었다.

http://okjsp.pe.kr/seq/2372

'_', '%'와 같은 문자들이 검색을 하기 위한 wild 문자로 사용되지 않고 리터럴 문자로 사용되기 위해서는 뒤에 escape '\' 와 같이 사용해 주어야 한다는 것이다.
즉 다음과 같이 사용하면 되는 것이다.

LIKE '%검색어\_\%% escape '\'

사용하고 있는 DB는 CUBRID 였으며, 위와 같이 할 경우 잘 동작 되었다.

위 내용을 수정하면서 SQL Injection 위험이 있던 '%$keyword$%' 부분도 '%#keyworkd#%'로 바꾸게 되었다.
하지만 이 과정 중에 ibatis가 자체적으로 #keyword#를 'keyword'로 바꾸기 때문에 결과에서는 '%'keyword'%'가 되므로 수정해 주어야 했다.
MySQL이라면 CONCAT과 같은 함수를 써서 다음과 같이 하면 된다.

LIKE CONCAT('%', #keyword# '%')

하지만 CUBRID에는 CONCAT 함수가 없다. 관련해서 또다시 검색해 보니 Java에서 String을 이어 붙이기 하는 것과 같은 방법을 사용하면 되었다.
즉 CUBRID에서는 다음과 같이 하면 된다.

LIKE '%' + #keyword# + '%'

오늘도 삽질을 통해 또 하나의 깨달음을 얻게 되었다.
신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글 하나 달렸습니다.
  1. ㅋㅋㅋ, 지금 저한테 필요한 내용 ㅋㅋㅋ
secret
일반 프로퍼티 또는 List 타입의 프로퍼티를 출력하는 방법은 이미 살펴 보았다.
프로퍼티가 아닌 객체 자체를 출력하는 경우는 다음과 같이 할 수 있다.

struts.xml

<action name="printObject" class="example.chapter2.PrintObjectAction">
    <result>/chapter2/printObject.jsp</result>
</action>

example.model.Product.java

package example.model;

public class Product {
    private String name;
    private String modelNo;
   
    public Product() {}
    public Product(String name, String modelNo) {
        this.name = name;
        this.modelNo = modelNo;
    }
   
    public String getName() {
        return name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getModelNo() {
        return modelNo;
    }
   
    public void setModelNo(String modelNo) {
        this.modelNo = modelNo;
    }
}

example.chapter2.PrintObjectAction.java

package example.chapter2;

import example.model.Product;

public class PrintObjectAction {
    private Product product;
   
    public String execute() throws Exception {
        product = new Product("MP3 플레이어", "MP3-070701");
        return "success";
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }
}

/chapter2/printObject.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<s:label>제품 이름 : </s:label>
<s:property value="product.name"/>
<br>
<s:label>제품 모델번호 : </s:label>
<s:property value="product.modelNo"/>

</body>
</html>

Product라는 모델에 빈 프로퍼티를 만들고, PrintObjectAction.java 파일의 execute 메소드에서 해당 프로퍼티에 값을 셋팅한다.
셋팅 된 값을 printObject.jsp에서 <s:property> 태그에서 product.name, product.modelNo로 불러와 사용한다.
해당 값은 OGNL 형식으로 getter, setter 형식의 빈 메소드를 사용하여 값을 불러 온다.
label 태그는 html 형식의 label 태그를 생성해 주는 역할을 한다.

객체를 List형태로 보여주는 경우라면 아래와 같이 사용할 수 있다.

<action name="printObjectList" class="example.chapter2.PrintObjectListAction">
   <result>/chapter2/printObjectList.jsp</result>
</action>

example.chapter2.PrintObjectListAction.java

package example.chapter2;

import java.util.ArrayList;
import java.util.List;

import example.model.Product;

public class PrintObjectListAction {
    private List<Product> listProduct;
   
    public String execute() throws Exception {
        listProduct = new ArrayList<Product>();
        listProduct.add(new Product("MP3 플레이어", "MP3-070701"));
        listProduct.add(new Product("노트북 PC", "NB-070613"));
        listProduct.add(new Product("PDA", "PDA-070507"));
        listProduct.add(new Product("휴대폰", "MOB-070615"));
       
        return "success";
    }

    public List<Product> getListProduct() {
        return listProduct;
    }

    public void setListProduct(List<Product> listProduct) {
        this.listProduct = listProduct;
    }
   
}

/chapter2/printObjectList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<p>제품목록 : </p>
<table border="1" width="400">
<tr align="center" style="color:white;background-color:black;">
    <td>제품이름</td>
    <td>제품 모델번호</td>
</tr>

<s:iterator value="listProduct" status="status">
<tr bgcolor="<s:if test="#status.odd">lightgrey</s:if>">
    <td><s:property value="name"/></td>
    <td><s:property value="modelNo"/></td>
</tr>
</s:iterator>


</table>
</body>
</html>

action 파일의 execute 메소드에서 List 타입의 listProduct 프로퍼티에 값을 설정한다.
설정된 값을 printObjectList.jsp에서 불러와서 사용한다.
앞에서 product.name, product.modelNo 와 같이 사용했지만, iterator 안에서는 name, modelNo 와 같은 식으로 사용한다.
status는 Iteration 상태 값을 가리키는 값이다.
if 태그를 통해서 해당 값이 홀수인지 체크(#status.odd)한다.
status에서 지원하는 다른 속성은 다음과 같다.

first : 처음 행
last : 마지막 행
index : 행의 인덱스

추가로 날짜 출력과 관련된 예제 하나를 더 살펴보면 아래와 같다.

struts.xml

<action name="printDate" class="example.chapter2.PrintDateAction">
   <result>/chapter2/printDate.jsp</result>
</action>

PrintDateAction.java

package example.chapter2;

import java.util.Date;

public class PrintDateAction {
    private Date currDate;
   
    public String execute() {
        currDate = new Date();
       
        return "success";
    }

    public Date getCurrDate() {
        return currDate;
    }

    public void setCurrDate(Date currDate) {
        this.currDate = currDate;
    }
}

printDate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<ul>
    <li><s:property value="currDate"/></li>
    <li><s:date name="currDate"/></li>
    <li><s:date name="currDate" format="yyyy-MM-dd HH:mm:ss"/></li>
</ul>
</body>
</html>

sdate 태그에서 format 을 설정하지 않으면 "2009. 5. 11 오후 3:06:36"와 같이 출력되며, format을 주게 되면 format 형식으로 출력된다.





신고

'Java > Struts2' 카테고리의 다른 글

스트럿츠를 사용하여 객체 출력하기  (0) 2009.05.11
스트럿츠를 사용하여 List 출력하기  (1) 2009.05.06
스트럿츠2 설정하기  (2) 2009.04.28

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret
이번에는 스트럿츠를 사용하여 List를 출력해 보자. 일단 Action 파일과 result 파일 정의를 struts.xml에 정의하면 다음과 같다.

<action name="printStringList" class="example.chapter2.PrintStringListAction">
   <result>/chapter2/printStringList.jsp</result>
</action>

위에서 정의한 대로 PrintStringListAction.java 파일을 example.chapter2 패키지에 만든다.

package example.chapter2;

import java.util.ArrayList;
import java.util.List;

public class PrintStringListAction {
    private List<String> listString;
   
    public String execute() throws Exception {
        listString = new ArrayList<String>();
        listString.add("MP3 플레이어");
        listString.add("노트북 PC");
        listString.add("PDA");
        listString.add("휴대폰");
       
        return "success";
    }
   
    public List<String> getListString() { return listString; }
    public void setListString(List<String> listString) {
        this.listString = listString;
    }
}


listString이란 List 타입 변수에 값들을 담고, 나중에 result page(printStringList.jsp)에서 사용할 getListString 메소드도 만들어 준다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:iterator value="listString">
    <li><s:property/></li>
</s:iterator>
</body>
</html>

struts에서 사용하는 taglib를 선언 후 iterator를 통해 listString에 들어간 원소들을 하나씩 보여주고 있다.
위 소스의 결과는 아래와 같다.






신고

'Java > Struts2' 카테고리의 다른 글

스트럿츠를 사용하여 객체 출력하기  (0) 2009.05.11
스트럿츠를 사용하여 List 출력하기  (1) 2009.05.06
스트럿츠2 설정하기  (2) 2009.04.28

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글 하나 달렸습니다.
  1. 여기까지 따라해봤는데..
    printString, printStringList 예제 둘다 실행하면 HTML 결과물에 아무것도 나타나지 않습니다..
    이럴때 생각할 수 있는 문제들로 무엇이 있을까요..? 답변 부탁드립니다.
secret
스트럿츠를 사용하기 위해 스트럿츠 환경을 구축할 필요가 있다.
우선은 이클립스에서 다음과 같이 'Dynamic Web Project'를 하나 생성한다.


그런 다음 위에서 볼 수 있는 것처럼 몇가지 내용을 추가해야 한다.
우선은 필요한 라이브러리를 추가해 보자.
http://struts.apache.org/에서 해당 버전을 다운받는다. 현재 시점에서 가장 최신 버젼은 2.1.6이다.(http://struts.apache.org/download.cgi#struts216)
다운 받은 파일의 압축을 풀면 lib 디렉토리에 jar 파일들이 있는데, 그 중에서 필요한 jar 파일들을 추가한다.
추가해야 할 파일 목록은 다음과 같다.

antlr-2.7.2.jar
commons-beanutils-1.7.0.jar
commons-chain-1.2.jar
commons-fileupload-1.2.1.jar
commons-logging-1.0.4.jar
commons-logging-api-1.1.jar
commons-validator-1.3.1.jar
freemarker-2.3.13.jar
jstl.jar
ognl-2.6.11.jar
oro-2.0.8.jar
standard.jar
struts-core-1.3.10.jar
struts2-core-2.1.6.jar
xwork-2.1.2.jar

jstl.jar 파일과 standard.jar 파일은 JSTL을 사용하기 위해 이미 앞에서 추가한 파일이며, 그외 다른 파일들도 추가한다.
에이콘에서 나온 '스트럿츠2 프로그래밍' 책을 기초로 셋팅을 따라 한 것인데 해당 책에는 특히 commons-fileupload-1.2.1.jar 라이브러리에 대한 업급이 없다.
commons-fileupload-1.2.1.jar 파일 없이 server를 구동 시킬 경우 에러가 발생 했었다.

라이브러리 추가 후에는 설정 파일을 수정해야 한다.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>struts2</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
 
  <filter>
      <filter-name>struts</filter-name>
      <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>struts</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
 
</web-app>

struts.xml

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

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <package name="default" extends="struts-default" namespace="">
    </package>
</struts>

struts.properties

struts.i18n.reload=true
struts.devMode=false
struts.configuration.xml.reload=true
struts.continuations.package=org.apache.struts2.showcase
struts.custon.i18n.resources=globalMessages
#struts.action.extension=jspa
struts.url.http.port=8080
#struts.freemarker.manager.classname=customFreemarkerManager
struts.serve.static=true
struts.serve.static.browserCache=false
struts.multipart.maxSize=2097252

web.xml은 WebContent/WEB-INF/ 아래에 만들면 되며, struts.xml, struts.properties 파일은 src 밑에 둔다.(이클립스에서 자동으로 classes 밑으로 배포해 준다.)
위와 같이 설정 한 후 이클립스에서 서버를 추가해 준다.

Tomcat 6.0으로 선택

Tomcat 6.0으로 선택



추가 된 서버를 더블 클릭하면 아래와 같은 창이 뜬다.

Server name을 struts2로 바꾼다.(이름은 아무렇게나 해도 된다.)

Server name을 struts2로 바꾼다.(이름은 아무렇게나 해도 된다.)


아래 Modules 탭으로 이동 후 아래 그림과 같이 나오면 다음 내용을 진행한다.

Add External Web Module... 클릭

Add External Web Module... 클릭



Browser를 눌러 Document Root를 지정해 준다

Browser를 눌러 Document Root를 지정해 준다



Document Root는 프로젝트 생성할 때 자동으로 생성된 Directory 중 WebContent 디렉토리를 지정해 준다.
이와 같이 지정 후 저장한 다음 서버 에디팅 창을 닫는다.
그런 다음 생성된 struts2 서버를 start 한다.
이상 없이 start 가 된다면 다음과 같이 간단한 struts예제를 만들어 시험해 본다.

struts.xml

<struts>
    <package name="default" extends="struts-default" namespace="">
        <action name="printString" class="example.chapter2.PrintStringAction">
            <result>/chapter2/printString.jsp</result>
        </action>
    </package>
</struts>

package example.chapter2;

public class PrintStringAction {
    private String greetings;
   
    public String execute() throws Exception {
        greetings = "반갑다! 스트럿츠2.";
        return "success";
    }
   
    public String getGreetings() { return greetings; }
    public void setGreetings(String greetings) {
        this.greetings = greetings;
    }
}


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <s:property value="greetings" />
</body>
</html>

struts.xml에서 정의한 것처럼, example.chapter2 패키지 않에 PrintStringAction.java 파일을 만들고, jsp 파일은 /WebContent/chapter2/printString.jsp로 만든다.
jsp에서는 "<%@ taglib prefix="s" uri="/struts-tags" %>"와 같이 strus에서 사용할 tag lib를 선언한 다음, PrintStringAction에서 정의한 greetings 변수를 "<s:property value="greetings" />"와 같이 출력해 준다.
<s:property/>에서 사용한 value부분은 Action 파일에서 정의한 getter를 사용해서 값을 가져 온다.(OGNL 사용)
만일 getter가 없다면 오류가 나지는 않으며, 단지 화면에 내용이 출력되지 않는다.

신고

'Java > Struts2' 카테고리의 다른 글

스트럿츠를 사용하여 객체 출력하기  (0) 2009.05.11
스트럿츠를 사용하여 List 출력하기  (1) 2009.05.06
스트럿츠2 설정하기  (2) 2009.04.28

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글  2개가 달렸습니다.
  1. 좋은 정보 감사합니다.. 퍼갈께요 ^_^
  2. 감사합니다 !!! 퍼가겠습니다~
secret
필터를 통해 서블릿이 실행되기 전에 특정 액션을 하게 만들 수 있다고 했다.
그런데 서블릿이 실행되고 나서 특정 액션을 하게 만들려면 어떻게 해야 할까?
간단하게 생각해 보면 필터의 doFilter()메소드 안에 있는 chain.doFilter(request, response) 를 마치고 나서 작업하면 될 것 같다.
하지만, 서블릿에 넘겨주는 response 객체를 서블릿이 사용하게 되면, 필터를 거치지 않고 바로 클라이언트로 response 하게 된다.
따라서 doFilter를 통해 response를 넘길 때 새로운 응답 객체(HttpServletResponse를 구현한 객체)를 만들어 넘기는 방법을 써야 한다.

하지만, HttpServletResponse는 간단한 클래스가 아니므로, 썬에서 구현해 둔 다음 4가지를 이용하면 된다.

ServletRequestWrapper
HttpServletRequestWrapper
ServletResponseWrapper
HttpServletResponseWrapper

위 4가지 클래스를 상속한 후 필요한 메소드만 재정의 해서 사용하면 된다.

response를 할 때 내용을 압축해서 보내주는 예제를 통해서 Response Filter를 어떻게 사용하는지 확인해 볼 수 있다.

package com.example.web;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CompressionFilter implements Filter {
    private ServletContext ctx;
    private FilterConfig cfg;
   
    @Override
    public void init(FilterConfig cfg) throws ServletException {
        // TODO Auto-generated method stub
        this.cfg = cfg;
        ctx = cfg.getServletContext();
        ctx.log(cfg.getFilterName() + " initialized.");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain fc) throws IOException, ServletException {
        // TODO Auto-generated method stub
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
       
        String valid_encodings = request.getHeader("Accept-Encoding");
        if(valid_encodings.indexOf("gzip") > -1) {
            CompressionResponseWrapper wrappedResp
                        = new CompressionResponseWrapper(response);
            wrappedResp.setHeader("Content-Encoding", "gzip");
            ctx.log("before chain...");
            fc.doFilter(request, wrappedResp);
           
            GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
            gzos.finish();
           
            ctx.log(cfg.getFilterName() + ": finished the request.");
        } else {
            ctx.log(cfg.getFilterName() + ": no encoding performed.");
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        cfg = null;
        ctx = null;
    }
}


package com.example.web;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class CompressionResponseWrapper extends HttpServletResponseWrapper {
    private GZIPServletOutputStream servletGzipOS = null;
    private PrintWriter pw = null;

    public CompressionResponseWrapper(HttpServletResponse response) {
        super(response);
    }
   
    public void setContentLength(int len) {}
   
    public GZIPOutputStream getGZIPOutputStream() {
        return this.servletGzipOS.internalGzipOS;
    }
   
    private Object streamUsed = null;
   
    public ServletOutputStream getOutputStream() throws IOException {
        if((streamUsed != null) && (streamUsed != pw)) {
            throw new IllegalStateException();
        }
       
        if(servletGzipOS == null) {
            servletGzipOS = new GZIPServletOutputStream(getResponse().getOutputStream());
            streamUsed = servletGzipOS;
        }
        return servletGzipOS;
    }
   
    public PrintWriter getWriter() throws IOException {
        if((streamUsed != null) && (streamUsed != servletGzipOS)) {
            throw new IllegalStateException();
        }
       
        if(pw == null) {
            servletGzipOS = new GZIPServletOutputStream(getResponse().getOutputStream());
            OutputStreamWriter osw = new OutputStreamWriter(servletGzipOS, getResponse().getCharacterEncoding());
           
            pw = new PrintWriter(osw);
            streamUsed = pw;
        }
        return pw;
    }
}


package com.example.web;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletOutputStream;

public class GZIPServletOutputStream extends ServletOutputStream {
    GZIPOutputStream internalGzipOS;
   
    GZIPServletOutputStream(ServletOutputStream sos) throws IOException {
        // TODO Auto-generated constructor stub
        this.internalGzipOS = new GZIPOutputStream(sos);
    }
   
    @Override
    public void write(int param) throws IOException {
        // TODO Auto-generated method stub
        internalGzipOS.write(param);
    }

}

압축과 관련된 부분은 다루고자 하는 범위를 넘어서는 듯 하며, Response를 어떻게 변형하여 넘겨주는지를 확인해 보면 된다.
CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response); 와 같이 HttpServletResponseWrapper를 확장한 클래스를 통해 만들어진 랩퍼 클래스로부터 변경된 response 객체를 리턴받아 사용한다.
response 객체는 super(response);와 같이 상위 클래스에서 알아서 처리하도록 하며, 나머지 압축 관련 부분만 처리해 준다.


신고

'Java > Servlet & JSP' 카테고리의 다른 글

필터 - RESPONSE  (0) 2009.04.28
필터 - REQUEST  (1) 2009.04.16
웹 애플리케이션 보안  (4) 2009.04.10
웹 애플리케이션 배포하기  (0) 2009.04.07
부모 자식 태그간의 통신  (0) 2009.03.26
클래식 커스텀 태그  (0) 2009.03.26

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret
모든 서블릿 앞단에서 공통적으로 처리해야 할 내용이 있을 경우 필터를 사용해서 해당 내용을 처리할 수 있다.
필터는 자신만의 고유 API가 있으며, 컨테이너가 이 API를 알고 호출해 준다.
해당 API는 init(), destroy(), doFilter()이다.
그리고 호출해야 할 필터는 DD(web.xml)에서 설정하게 된다.

하나의 예제로 필터를 만들어 보면 다음과 같이 할 수 있다.(Head & First 예제)

BeerRequestFilter.java

package com.example.web;

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;
import javax.servlet.http.HttpServletRequest;

public class BeerRequestFilter implements Filter {
    private FilterConfig fc;
   
    public void init(FilterConfig config) throws ServletException {
        this.fc = config;
    }
   
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest httpReq = (HttpServletRequest) req;
       
        String name = httpReq.getRemoteUser();
       
        if(name != null) {
            fc.getServletContext().log("User " + name + "is updating");
        }
       
        chain.doFilter(req, resp);
    }
   
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
       
    }
}

모든 필터는 Filter 인터페이스를 구현해야 한다.
또한 doFilter()의 인자로 HttpServletRequest, HttpServletResponse가 아니라 ServletRequest, ServletResponse 가 온다는 점도 주의해야 한다.
그리고 필터는 순차적으로 진행될 수 있는데 chain.doFilter를 통해 다음 필터의 doFilter를 호출할 수 있다.

web.xml

  <servlet>
      <servlet-name>ListenerTester</servlet-name>
      <servlet-class>com.example.ListenerTester</servlet-class>
      <security-role-ref>
          <role-name>Manager</role-name>
          <role-link>Admin</role-link>
      </security-role-ref>
  </servlet>
  <servlet-mapping>
      <servlet-name>ListenerTester</servlet-name>
      <url-pattern>/ListenTest.do</url-pattern>
  </servlet-mapping>

  <filter>
      <filter-name>BeerRequest</filter-name>
      <filter-class>com.example.web.BeerRequestFilter</filter-class>
      <init-param>
          <param-name>LogFileName</param-name>
          <param-value>UserLog.txt</param-value>
      </init-param>
  </filter>
  <filter-mapping>
      <filter-name>BeerRequest</filter-name>
      <url-pattern>*.do</url-pattern>
  </filter-mapping>

위와 같이 web.xml을 설정하게 되면 기존에 설정한 /ListenTest.do url을 호출하게 되면 BeerRequestFilter.java의 doFilter 메소드가 자동으로 호출되게 된다.
물론 호출 시에는 init() 메소드가 먼저 실행 되게 되고, 마지막에는 destroy() 메소드가 실행 되게 된다.

url-pattern 대신에 servlet-name이 대신 와도 된다.
우선 순위는 url-pattern이 먼저이고, 해당 패턴에 맞는 필터가 모두 실행되고 나서 servlet-name에 맞는 내용이 실행된다.

필터가 여러개일 경우에는 DD(web.xml)에 설정한 순서대로 실행 되며, 실행 과정은 stack에 관련 필터들이 쌓였다가 제거 되는 모습을 상상해 보면 된다.

필터를 클라이언트가 Request 했을 경우 뿐 아니라, 다른 경우에도 실행할 수 있다.
그럴 경우에는 다음과 같이 DD(web.xml)에 적어주면 된다.

  <filter>
      <filter-name>BeerRequest</filter-name>
      <filter-class>com.example.web.BeerRequestFilter</filter-class>
      <init-param>
          <param-name>LogFileName</param-name>
          <param-value>UserLog.txt</param-value>
      </init-param>
  </filter>
  <filter-mapping>
      <filter-name>BeerRequest</filter-name>
      <url-pattern>*.do</url-pattern>
      <dispatcher>REQUEST</dispatcher>
  </filter-mapping>

기본 값은 REQUEST이며, 그 대신에 INCLUDE, FORWARD, ERROR 를 사용할 수 있다.
선언하는 내용 그대로 include 시, forward시, error 시에 작동한다.

신고

'Java > Servlet & JSP' 카테고리의 다른 글

필터 - RESPONSE  (0) 2009.04.28
필터 - REQUEST  (1) 2009.04.16
웹 애플리케이션 보안  (4) 2009.04.10
웹 애플리케이션 배포하기  (0) 2009.04.07
부모 자식 태그간의 통신  (0) 2009.03.26
클래식 커스텀 태그  (0) 2009.03.26

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글 하나 달렸습니다.
  1. 궁금한게있는데요 여기서 맨위에BeerRequestFilter.java파일에서
    if(name != null) {
    fc.getServletContext().log("User " + name + "is updating");
    }
    있는데 이걸 어떻게 확인하나요??
secret
보안을 위해서는 인증(Authentication), 인가(Authorization), 데이터 무결성(Data Integrity), 기밀성(Confidentiality)이 보장 되어야 한다.
인증이란 쉽게 말해 로그인을 통해 누구인지 서버(컨테이너)가 알 수 있도록 체크하는 단계이다.
인가는 인증된 사용자의 권한 레벨을 확인할 수 있도록 체크하는 단계이다.
데이터 무결성이란 클라이언트가 서버에 보낸 정보가 변경되지 않도록 하는 것이며, 기밀성이란 중간에 해당 데이터를 엿보지 못하도록 하는 것이다.

인증 및 인가 작업은 php에서도 많이 하는 작업이므로 그 의미에 대해서 별 다른 설명은 필요해 보이지 않는다.
자세한 설정 방법 및 사용 법은 아래 예제들을 통해 알 수 있다.

우선은 서버 자체에서 지원하는 인증 방식을 알아보면 다음과 같다.

tomcat-user.xml

<tomcat-users>
    <role rolename="Admin"/>
    <role rolename="Member"/>
    <role rolename="Guest"/>
    <user name="Annie" password="admin" roles="Admin, Member, Guest" />
    <user name="Diane" password="coder" roles="Member, Guest" />
    <user name="Ted" password="newbie" roles="Guest" />
</tomcat-users>

서버 설치 디렉토리 또는 그 하위 디렉토리 중 conf 디렉토리에 있는 tomcat-user.xml에 유저 정보를 위와 같이 기록한다.

web.xml

  <login-config>
      <auth-method>BASIC</auth-method>
  </login-config>
 
  <security-role>
      <role-name>Admin</role-name>
  </security-role>
  <security-role>
      <role-name>Member</role-name>
  </security-role>
  <security-role>
      <role-name>Guest</role-name>
  </security-role>
  
  <security-constraint>
      <web-resource-collection>
          <web-resource-name>UpdateRecipes</web-resource-name>
          <url-pattern>/Beer/AddRecipe/*</url-pattern>
          <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
          <http-method>GET</http-method>
          <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
          <role-name>Admin</role-name>
          <role-name>Member</role-name>
      </auth-constraint>
  </security-constraint>

인증방식(auth-method)은 BASIC으로 하고 컨테이너에 지정한 내용(tomcat-user.xml)과 맵핑하는 정보를 기술(security-role)한다.
security-constraint에서는 특정 디렉토리에 인증을 걸 수 있게 url-pattern을 지정한다. 또한 인증을 허가할 http method도 지정한다.
지정을 안한 메소드들은 인증 없이 접근할 수 있다.
auth-constraint에는 접근할 수 있는 유저의 이름을 지정한다.
위와 같이 한 상태로 서버를 재시작 한 다음 http://localhost:8080/Beer/AddRecipe/ 에 접근하면 아래와 같은 로그인 창이 뜨게 된다.



* <auth-constraint> 규칙

<auth-constraint>은 옵션이다.
<auth-constraint> 항목이 있다는 것은 컨테이너에게 관련 URL에 대해 인증을 실시하라고 명령하는 것이다.'
<auth-constraint>가 없다면 해당 URL에 대해 인증없이 접근할 수 있다.
  - <auth-constraint/>와 같은 형식으로 되어 있다면 모든 사용자가 접근할 수 없다는 것이다.

* <role-name> 규칙

<role-name>은 옵션이다.
<role-name> 항목은 접근 허용목록을 컨테이너에게 알려주는 것이다.
<role-name>이 하나도 없다면, 어떤 사용자도 접근할 수 없다.
<role-name>*</role-name>와 같이 되어 있다면, 모든 사용자가 접근 가능한 것이다.
<role-name>은 대소문자를 구분한다.

동일 자원(url)에 대해서 <security-constraint> 항목이 두개 이상이라면, 더 큰 범위에 설정 된 값을 따른다.

서블릿에서 특정 레벨을 가진 역할명을 임의로 하드 코딩해 뒀다면, 해당 레벨명과 DD(web.xml)에서 정의한 레벨명을 서로 맵핑할 필요가 있다.
즉 다음과 같은 경우가 될 것이다.

if(request.isUserInRole("Manager")) {
    ...
} else {
    ...
}

DD에는 Manager란 롤 이름이 없다.
따라서 위와 같은 경우에는 DD에 다음과 같이 설정해 줄 수 있다.

  <servlet>
      <security-role-ref>
          <role-name>Manager</role-name>
          <role-link>Admin</role-link>
      </security-role-ref>
  </servlet>

  <security-role>
      <role-name>Admin</role-name>
  </security-role>

위와 같이 하게 되면 Sevlet에서 사용하는 Manager란 role은 Admin으로 처리 되게 된다.
물론 실제 Manager란 role이 있다 하더라도 위와 같이 설정하게 되면 Manager는 Admin의 role 역할을 수행하게 된다.

* 인증의 종류

인증의 종류에는 총 4가지가 있다.

1. BASIC

BASIC 방법은 위 예제에서 사용해 보았다.
로그인 정보를 base64 encoding 한 상태로 보내기 때문에, 중간에 '이즈드롭퍼(Eavesdropper)'가 가로챌 경우 쉽게 노출 될 수 있다.

2. DIGEST

암호화 매커니즘을 이용하여 전송하기에 좀더 안전하지만, 많이 사용되지 않는 암호화 기법이라서 J2EE 컨테이너가 반드시 지원 안할 수도 있다.

3. CLIENT-CERT

클라이언트가 인증서를 가지고 공인키 인증방식을 사용하여 로그인 하는 방식이다.
클라이언트가 인증서를 가지고 있어야지만 로그인이 되므로, 비즈니스 환경에서만 사용된다.

4. FORM

일반적인 웹폼(html)을 이용하여 로그인 정보를 서버에 전송하는 방식이다.
암호화 되지 않은 로그인 정보를 그대로 전송하므로, '이즈드롭퍼(Eavesdropper)'가 가로챌 경우 쉽게 노출 될 수 있다.

위 4가지 방식을 구현하기 위해서는 위의 예제 가운데 있었던 <login-config> 부분을 아래와 같이 수정하면 된다.

  <login-config>
      <auth-method>BASIC</auth-method>
  </login-config>

  <login-config>
      <auth-method>DIGEST</auth-method>
  </login-config>

  <login-config>
      <auth-method>CLIENT-CERT</auth-method>
  </login-config>

  <login-config>
      <auth-method>FORM</auth-method>
      <form-login-config>
          <form-login-page>/loginPage.html</form-login-page>
          <form-error-page>/loginError.html</form-error-page>
      </form-login-config>
  </login-config>

우리가 일반적으로 보는 FORM 방식의 로그인 인증 방식은 base64 인코딩도 안된 상태로 로그인 정보가 전송 되기 때문에, 보안에 가장 취약하다.
하지만 https를 통해서 로그인 정보를 전송하게 된다면 안전하게 정보를 전달할 수 있다.
그렇게 하기 위해서 DD에 다음과 같이 설정할 수 있다.

  <security-constraint>
      <web-resource-collection>
          <web-resource-name>UpdateRecipes</web-resource-name>
          <url-pattern>/Beer/AddRecipe/*</url-pattern>
          <url-pattern>/Beer/ReviewRecipe/*</url-pattern>
          <http-method>GET</http-method>
          <http-method>POST</http-method>
      </web-resource-collection>
      <auth-constraint>
          <role-name>Admin</role-name>
          <role-name>Member</role-name>
      </auth-constraint>
      <user-data-constraint>
          <transport-guarantee>CONFIDENTIAL</transport-guarantee>
      </user-data-constraint>
  </security-constraint>

transport-guarantee에 들어갈 수 있는 값으로는 다음의 3가지가 있다.

NONE, INTEGRAL, CONFIDENTIAL

NONE : 디폴트 값으로 데이터 보호를 하지 않겠다는 의미이다.
INTEGRAL : 전송 중 데이터가 변경되지 않음을 보장한다는 의미이다. - 데이터 무결성(Data Integrity)
CONFIDENTIAL : 전송 중 데이터를 누구도 훔쳐보지 않았음을 보장한다는 의미이다. - 기밀성(Confidentiality)

그런데 데이터 무결성과 기밀성은 클라이언트나 서버 단에서 보장할 수 있는 것이 아니다.
따라서 위에서 설정한 INTEGRAL, CONFIDENTIAL 과 같은 내용은 다음과 같은 방법으로 보장한다는 의미이다.

전송 보장이 설정된 경우, 클라이언트가 해당 자원에 보안이 되지 않은 상태로 요청을 하게 되면, 컨테이너는 301 코드를 내려 보내게 된다.
301 코드는 클라이언트에게 새로운 주소로 새로운 요청을 하라는 말인데, 여기서는 주소가 아니라 전송 방식을 http에서 https로 수정해서 보내도록 요청하는 것이다.
https로 다시 요청이 들어오면 해당 자원에 인증이 필요함을 확인 후 401 코드를 내려 보낸다.
클라이언트는 401 코드를 확인 후 유저 정보를 안전하게 https로 전송하게 되는 것이다.


신고

'Java > Servlet & JSP' 카테고리의 다른 글

필터 - RESPONSE  (0) 2009.04.28
필터 - REQUEST  (1) 2009.04.16
웹 애플리케이션 보안  (4) 2009.04.10
웹 애플리케이션 배포하기  (0) 2009.04.07
부모 자식 태그간의 통신  (0) 2009.03.26
클래식 커스텀 태그  (0) 2009.03.26

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글  4개가 달렸습니다.
  1. 이해가 팍팍 되는군요 ㅎㅎ 잘 봤습니다 !! 퍼갈께욥~
  2. 좋은 자료 감사합니다. 출처 남길께요.^^
  3. 정말 좋은자료에요.....눈물...
secret
그냥 정기적으로 보내주는 형식의 메일을 잘 확인 안해보는 습관이 있어서, 썬에서 보내주는 메일도 확인도 안해보고 넘기다가 오늘 우연치 않게 지메일을 확인하다 아래 내용을 발견 했네요.
별볼일 없는, 그냥 Head & First 책에 나오는 내용을 정리한 포스팅인데, 그 내용이 뉴스레터에 실리다니...
참으로 창피하네요 ^^;;
내용도 기초적인 내용이고 ㅎㅎ

"커스텀 태그 사용하기"란 포스팅

"커스텀 태그 사용하기"란 포스팅


이메일에 실린 포스트는 http://www.4te.co.kr/566 입니다.
나중에 고수가 된다면, 기초적인 내용이 아닌 정말로 유익한 정보를 블로그에 포스팅을 쓸수 있겠죠 ㅎ

하지만 이 놈의 귀차니즘 때문에 진도가 느려서 탈입니다. ㅎㅎ


신고

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글  2개가 달렸습니다.
  1. 잘지내죠? 자바 공부 열심히 하고 있나바요~~ ㅋㅋ
secret
어떤 파일이 어느 곳에 위치하는지는 중요하다.
php를 개발 할 때 프로그래머가 임의로 정하는 장소와는 달리 Java(JSP)는 어느정도 rule을 가지고 특정 위치에 특정 파일을 배포해야지만, 전체 웹 애플리케이션이 돌아가기 때문이다.
따라서 중요한 몇개의 파일 타입별 배포 위치에 대해 정리해 보면 아래와 같다.

  • 정적인 컨텐츠(html, 이미지) 및 jsp
    • 루트 디렉토리 및 그 하위 디렉토리
    • WEB-INF 밑에 배포 할 경우에는 클라이언트가 직접 접근할 수 없다. 대신 웹 애플리케이션에서 접근을 하게 만들 경우를 위해서 배포할 순 있다.
  • 태그파일(.tag) - http://www.4te.co.kr/567 참고
    • WEB-INF/tags 디렉토리에 배포
  • DD 파일
    • 이름은 web.xml이어야 하며 WEB-INF 바로 밑에 두어야 한다.
  • jar 파일
    • WEB-INF/lib 밑에 두어야 한다.
    • jar파일 내 구조
      • jar 최상위 디렉토리 바로 밑에서부터 시작해야 한다.(패키지 시작부터)
      • jar로 배포할 경우 최상위 디렉토리에 META-INF 디렉토리가 있어야 하며, tld 파일은 META-INF 디렉토리 아래 배포 해야한다.
  • class 파일
    • WEB-INF/classes 밑에 패키지 구조로 배포 되어야 한다.

* WAR 파일로 배포하기

jar 파일로 애플리케이션 전체를 압축한 후 확장자만 war로 바꿔서 배포하는 것을 말한다.
압축하는 디렉토리는 웹 애플리케이션의 내용이 들어있는 디렉토리를 압축해서 배포하면 된다.
압축을 하는 위치는 디렉토리 위에서 압축하는 것이 아니라, 디렉토리 내부에서 압축해야 한다.
또한 압축 파일명은 웹애플리케이션의 이름이 된다(톰캣의 경우)

ex) /webapps/BeerApp 를 압축하기 위해서는 /webapps에서 BeerApp를 압축하는 것이 아니라 BeerApp 아래 들어가서 압축

WAR 파일의 경우 META-INF/MANIFEST.MF 파일에 라이브러리 의존성을 작성해 두면, 배포 시 컨테이너가 애플리케이션을 실행하기 위해 필요한 라이브러리가 있는지 사전 체크할 수 있다.

* 서블릿 맵핑

서블릿 맵핑은 물리적인 디렉토리가 아닌 가상의 디렉토리(URL)에서 실행할 내용을 찾기 위해 사용하는 것이다. 관련 내용은 http://www.4te.co.kr/538를 참고하면 된다.

참고)서블릿 맵핑은 와일드 카드와 같이 패턴 형식으로 맵핑할 수도 있다.

* DD에 환영 파일 설정하기

파일명을 안치고 디렉토리명까지 치면 자동을 찾게 할 파일명을 설정할 수 있다. web.xml에 다음과 같이 적어주면 된다.

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>

위 리스트 중에서 index.html과 index.jsp가 같이 있게 된다면 먼저 나온 index.html을 보여주게 된다.
apache의 httpd.con파일에 있는 다음과 같은 설정과 같은 역할을 해 주는 것이다.

<IfModule mod_dir.c>
    DirectoryIndex index.html index.php
</IfModule>

* DD에 오류 페이지 설정하기

오류 페이지를 web.xml에 설정하는 방법은 http://www.4te.co.kr/565 를 참고하면 된다.
프로그램에서 오류코드를 일부러 방생하고자 할 경우에는 다음과 같이 하면 된다.

response.sendError(HttpServletResponse.SC_FORBIDDEN);

위 내용은 403 에러를 내뱉는 경우를 프로그램적으로 처리한 경우이다. 실제 403 에러를 내 뱉는 것과 프로그램을 통해 내뱉는 것 사이의 차이를 클라이언트는 알 수 없다.

* DD에서 강제로 서블릿 초기화 하기

서블릿을 배포하게 되면 가장 처음 요청하는 클라이언트는 해당 서블릿이 로딩 될 때까지 기다려야 한다. 그리 무겁지 않은 서블릿일 경우에는 자동 로딩을 적용 하지 않아도 되지만, 무거운 서블릿일 경우에는 클라이언트에 의해 로딩되지 않고, 배포 할 때 로딩 되도록 만들어야 한다.
따라서 그럴 경우에는 아래와 같이 web.xml에 정의해 주면 된다.

  <servlet>
      <servlet-name>Chapter1 Servlet</servlet-name>
      <servlet-class>Ch1Servlet</servlet-class>
      <load-on-startup>1</load-on-startup>
  </servlet>

해당 숫자는 초기화 하는 순서를 말한다. 동일한 숫자가 여러개 있다면 web.xml에 정의한 순서대로 로딩한다.

* mime mapping 하기

DD(web.xml)에서 mime type을 확장자와 맵핑해 둘 수 있다.

  <mime-mapping>
      <extension>mpg</extension>
      <mime-type>video/mpeg</mime-type>
  </mime-mapping>




신고

'Java > Servlet & JSP' 카테고리의 다른 글

필터 - REQUEST  (1) 2009.04.16
웹 애플리케이션 보안  (4) 2009.04.10
웹 애플리케이션 배포하기  (0) 2009.04.07
부모 자식 태그간의 통신  (0) 2009.03.26
클래식 커스텀 태그  (0) 2009.03.26
사용자 정의 태그 개발 (1)  (0) 2009.03.23

WRITTEN BY
체리필터
프로그램 그리고 인생...

받은 트랙백이 없고 , 댓글이 없습니다.
secret