SSISO Community

시소당

사이트를 조직화 해주는 프레임워크, SiteMesh

사이트를 조직화 해주는 프레임워크, SiteMesh

  • 개요
    • 불행히도 2005년을 살아가고 있는 개발자들은 다수의 프레임워크를 선택할 수 있는 행운을 갖게 되었다. 하지만 이 행운은 여러 프레임워크의 특성과 장단점, 프레임워크간의 관계를 면밀히 검토해야 할 결코 즐겁지만은 않은 비명을 안겨 주었다. 뿐만 아니라 SI 업체들은 저마다의 방법론과 낡은 자체 개발 프레임워크를 성격이 다른 도메인에 강제하는 일도 비일비재하다. 필자가 프레임워크로써 갖춰야 할 중요한 요건으로 생각하는 것은 견고함과 유연함이다. 수많은 개발자로 이뤄진 팀에게 가야 할 길을 제시하고 그 길에서 벗어나지 않도록 강제하는 견고함과 언제나 발생하고 마는 변화에 능동적으로 대응할 수 있는 유연함. 이번 호에서는 J2EE 프런트 사이드 프레임워크 중 유연함과 견고함을 두루 갖춘 SiteMesh를 소개한다.
  • 목차
    • [1] 웹 페이지 장식을 위한 프레임워크
    • [2] 서블릿 필터
    • [3] 이클리스 품 안으로
    • [4] SiteMesh 뒤져보기
    • [5] SiteMesh 구조와 흐름
    • [6] 연결자를 만들어 보자
    • [7] 남은 것은 개발의 즐거움뿐
    • [8] 문서 정보


또 다른 면도 있다. 프로그램은 책이나 잡지 같은 문자라기보다는 말과 같아서 시간이 지나면서 변해간다. 
프로그램은 제대로 돌아가기만 해서는 부족하다. 다른 사람이 바로 이해할 수 있어야 한다. 
또, 쉽게 고칠 수 있도록 코드를 조직화해야 한다. 어떤 작업을 처리할 수 있는 방법이 열 가지 있다고 하자. 
아마도 이 중에서 일곱 가지 정도는 어색하고, 기능이 떨어지고, 이상할 것이다. 
나머지 세 가지 중에서 과연 어떤 것이 일 년 후에 같은 작업을 처리할 때도 쓸 수 있을까?

- Guy L. Steele Jr, Effective Java 서문 中


[#1] 웹 페이지 장식을 위한 프레임워크

SiteMesh는 웹 페이지 레이아웃 및 데코레이션 프레임워크이다. 일반적으로 사이트는 통일되고 일관된 룩앤필과 네비게이션, 레이아웃을 유지해야 하며 SiteMesh는 이러한 사항들을 조직화할 수 있도록 도와주고 또한 변화와 수정에 탁월한 대처 능력을 가지고 있다.

SiteMesh는 웹 서버에 요구된 정적/동적 요청(request)을 가로채고 요청된 페이지 자원을 파싱하며 설정된 프로퍼티와 렌더링할 데이터를 컨텐츠로부터 읽어 들인 후 원래의 페이지에 장식과 수정을 가하여 최종 페이지를 생성한다. 이것은 GoF의 장식자 패턴(decorator pattern)에 기반하고 있으며, 작동하는 시퀀스는 서블릿 스펙 2.3부터 새롭게 추가된 필터(filter)를 기반으로 하고 있다. SiteMesh는 모든 정적/비정적 페이지를 그래픽 객체와 컨테이너로 간주하며 이러한 개념은 각각의 페이지들을 객체로 바라보고 컴포넌트화 할 수 있는 방법을 제시한다. 그 결과 모든 페이지들은 결합, 분리되고 재사용될 수 있다. 이것은 GoF의 복합 패턴(composite pattern)에 기초하고 있다.

SiteMesh는 견고하다. 적절하게 정의된 디자인 템플릿과 레이아웃은 원하는 모든 자원(HTML, JSP/서블릿)들에 적용될 것이며 이것을 구현하기 위해 SIteMesh는 HTML 작성자에게 통일되고 간결한 형태의 HTML만을 요구할 것이다. 이로 인하여 사이트는 전체적으로 쉽게 유지관리 및 확장될 수 있을 것이다. 또한 SiteMesh는 유연하다. 장식자와 프로퍼티의 조합은 새로운 확장을 가능하게 하며 SiteMesh의 틀을 이루고 있는 파서(parser)와 맵퍼(mapper)를 구현하여 새로운 서비스를 만들어낼 수도 있다. 여기서 독자와 함께 진행할 내용과 순서는 다음과 같다.

  • 필터에 익숙하지 않은 독자들을 위해 간단한 필터의 예제를 수행해 보고 개념을 익히도록 하자.
  • www.opensymphony.com/sitemesh에서 예제로 배포되는 SiteMesh 샘플 웹 애플리케이션을 설치하고 이클립스 워크벤치(Eclipse workbench)에 개발 환경을 구축해 보자.
  • 구축된 환경을 바탕으로 SiteMesh의 구조와 예제소스를 살펴보자. 이 글의 모든 예제와 이론적 바탕은 그곳에서 온 것이다.
  • SiteMesh의 작동 순서와 흐름을 살펴보자.
  • 맵퍼를 확장하여 SiteMesh의 능력을 확장시켜보자

이 글의 모든 예제는 J2SE 5.01, 톰캣 5.5.72, 이클립스 3.0.13에서 개발되고 테스트되었다(톰캣 5.5.7은 J2SE 5.0을 요구한다). 물론 톰캣 4.x와 J2SE2로 테스트 환경을 구성해도 좋을 것이다. 편의상 이클립스 3.0.1에는 톰캣 플러그인 4, Lomboz, EMF(Eclipse Modeling Framework) 2.0.16을 설치했다(참고자료 8~13).

자동화된 개발 환경은 개발자로부터 복잡한 설정과 반복적인 배포의 수고를 덜어주고 잦은 리팩토링을 가능하게 할 것이며, 이는 개발자에게 창조적인 사고에 필요한 시간을 더 많이 할애해줄 것이다. 개발자들은 앞으로 더 게을러질지도 모르겠다.


[#2] 서블릿 필터

필터는 http-request, http-response의 헤더와 바디를 수정할 수 있는 객체이다. 필터는 스스로 응답(response)을 생성하지 않는다는 점에서 웹 컨텐츠와는 다르다. 필터는 모든 종류의 웹 컨텐츠(정확히는 그것에 대한 http-requests와 http-responses)를 필터링할 수 있다( http://java.sun.com/j2ee/1.3/docs/tutorial/doc/Servlets8.html). 필터는 http-request의 전처리 단계이며 http-response의 후처리 단계이다. 필터가 수행할 수 있는 역할은 다음과 같다.

  • 페이지가 호출되기 전 요청을 가로챌 수 있다.
  • 페이지가 호출되기 전 응답을 검사할 수 있다.
  • 요청 및 요청 헤더(request header)의 데이터를 수정할 수 있고 고유한 버전의 요청객체로 랩핑(wrapping)할 수 있다.
  • 응답 및 응답 헤더(response header)의 데이터를 수정할 수 있고 고유한 버전의 응답 객체로 랩핑할 수 있다.
  • 페이지가 호출된 후 요청을 가로챌 수 있다.

다음은 자바월드에 포스팅 된 필터의 예제(http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html)로써 정적 혹은 비정적 페이지에 요청이 도달하여 응답이 완료되기까지의 수행 시간을 기록한 것이다. 테스트 컨테이너인 톰캣 5.5.7로의 적재는 앞에서 다운받은 timer.war 파일을 TOMCAT_HOME/webapps에 복사한 후 톰캣을 재 기동하는 것만으로 충분하다. 우선 예제를 수행해보고 로그를 살펴보자 <화면 1>을 보면 톰캣 기동 후 http://localhost:/timer에 접근하면 콘솔 표준 출력을 통해 접근한 url의 소요 시간이 기록되는 것을 확인할 수 있다. index.html, index.jsp, 또 index.jsp, servlet-2_3-fcs-spec.pdf 등 총 4번의 요청을 시도했다. index.jsp의 첫 번째 호출에 대한 응답 시간은 두 번째 호출의 그것에 비해 컴파일 시간이 추가적으로 소요됐음을 확인할 수 있다.

<화면 1> TimerFilter가 남긴 로그

2005. 2. 12 오후 7:19:10 org.apache.catalina.startup.Catalina start
정보: Server startup in 4236 ms
2005. 2. 12 오후 7:19:41 org.apache.catalina.core.ApplicationContext log
정보: /timer/: 0ms
2005. 2. 12 오후 7:19:46 org.apache.catalina.core.ApplicationContext log
정보: /timer/index.jsp: 1242ms
2005. 2. 12 오후 7:19:53 org.apache.catalina.core.ApplicationContext log
정보: /timer/index.jsp: 0ms
2005. 2. 12 오후 7:24:50 org.apache.catalina.core.ApplicationContext log
정보: /timer/servlet-2_3-fcs-spec.pdf: 170ms

예제 애플리케이션의 TimerFilter.java의 구현을 살펴보자. 필터 클래스는 javax.servlet.Filter 인터페이스를 구현해야 하며 다음의 세 콜백 메쏘드를 정의해야 한다.

  • void init() : 필터가 서비스하기 전에 호출되며 FilterConfig 객체를 설정할 수 있다.
  • void destroy() : 필터의 서비스가 종료된 후 호출된다.
  • void doFilter() : 실제로 필터가 작동할 행위를 구현한다.

<리스트 1>은 예제에 첨부된 TimerFilter.java의 doFilter() 구현으로 수행 시간을 기록하는 지극히 간단한 소스로 구성되어 있다. <리스트 2>는 예제에 첨부된 web.xml의 일부를 발췌한 것으로 작성된 필터를 웹 애플리케이션에 설정하는 방법을 나타내고 있다.

<리스트 1> TimerFilter.java의 doFilter() 구현

01 public void doFilter(ServletRequest request, ServletResponse response,
02                      FilterChain chainthrows IOException, ServletException {
03     long before = System.currentTimeMillis();
04     chain.doFilter(request, response);
05     long after = System.currentTimeMillis();
06 
07     String name = "";
08     if (request instanceof HttpServletRequest) {
09       name = ((HttpServletRequest)request).getRequestURI();
10     }
11     config.getServletContext().log(name + ": " (after - before"ms");
12 }

<리스트 2> web.xml에 TimerFilter 등록 및 맵핑

01 <web-app>
02     <filter>
03         <filter-name>timerFilter</filter-name>
04         <filter-class>TimerFilter</filter-class>
05     </filter>
06 
07     <filter-mapping>
08         <filter-name>timerFilter</filter-name>
09   <url-pattern>/*</url-pattern>
10     </filter-mapping>
11 </web-app>

지금까지 필터의 개념을 살펴보고 예제를 수행해 보았다. http://java.sun.com/j2ee/1.3/docs/tutorial/doc/Servlets8.html에서 더 많은 예제를 찾아볼 수 있으며 실제로 톰캣 4.x 버전의 한글 처리 문제로 널리 사용되고 있다. 서블릿 스펙 2.3의 제 6장은 모두 필터에 관한 내용이므로 확인해 보는 것도 도움이 될 것이다.


[#3] 이클리스 품 안으로

드디어 SiteMesh다. 언제나처럼 SiteMesh의 예제를 다운받아 서블릿 컨테이너에 적재할 것이며 작동되는 모습을 살펴보고 예제 소스를 분석해 봄으로써 SiteMesh의 전체적인 구조와 행위를 파악해 볼 것이다. www.opensymphony.com/sitemesh/download.html에서 배포되는 SiteMesh 2.2.1의 sitemesh-example.war 파일을 다운받는다. 혹시 파일이 sitemesh-example.zip이면 sitemesh-example.war로 변경하자. 다운받은 웹 아카이브를 적재하는 방법은 다음과 같다.

  • 간단한 방법 : sitemesh-example.war 파일을 TOMCAT_HOME/webapps 경로에 복사한 후 톰캣을 재 기동한다. 브라우저에 http://localhost:/sitemesh-example을 입력한다.
  • 조금 복잡한 방법 : 이클립스 워크벤치에 Lomboz 프로젝트를 추가하고 톰캣 관련 설정을 마친 후 sitemesh-example.war를 임포트하고 톰캣을 기동한 후 http://localhost:/sitemesh-example을 입력한다.

동작을 살피고 테스트를 수행하기 위해서는 첫 번째 방법으로도 충분하다. 하지만 실제 개발 환경을 구축하고, 맵퍼의 확장까지 테스트하기 위해서는 두 번째 방법을 권하고 싶다. 첫 번째 방법에 비해 상대적으로 조금 복잡할 뿐 어렵지는 않다. 실제 개발 환경을 직접 구축해보는 것은 여러 면에서 큰 도움이 될 수 있을 것이다.

그럼 두 번째의 조금 복잡한 방법의 설정을 함께 진행해 보자. 우선 앞서 말한 대로 이클립스와 EMF, Lomboz, 톰캣 플러그인의 설치 및 설정이 완료됐다는 가정 아래 진행한다. Lomboz와 톰캣 플러그인은 설치 후 몇 가지 설정이 필요하다. 마음 같아서야 모든 것을 담고 싶지만 한정된 지면인지라 그저 죄송스러울 따름이다. 실제로 이클립스+Lomboz+톰캣 플러그인 관련한 훌륭한 문서가 많은 것으로 알고 있다. 그런 것들을 참조한다면 진행에 무리가 없을 것으로 생각된다.

이클립스의 비어 있는 워크벤치가 주는 설레임과 함께 시작해 보자. 전체 과정을 갈무리한 이미지를 보며 설명하는 것이 가장 좋겠지만 지면의 제약으로 자세한 설명으로 대체한다(마음 같아서야 톰캣을 인스톨하고 이클립스에 여타 다른 플러그인까지 모두 애드온한 상태에서 예제 파일 복사하여 프로젝트 설정까지 모두 맞춘 후 CD로 제작하여 배포하고 싶다).

이클립스의 [File | New Project]를 선택하여 [Project Select Wizard]를 시작한다. 프로젝트 트리에서 자바 노드를 확장하여 [Lomboz J2EE Project]를 선택한다. [J2EE Project Creation Wizard]가 실행될 것이다. [Project name] 란에 SiteMeshExample이라고 입력하고 다음 단계로 진행한다. [Java settings]라는 이름의 [build path]를 정의하는 창이 나타난다. 우리의 프로젝트에 맞게 수정해야 하지만 디폴트 설정대로 진행하고 모든 설정이 완료된 후 수정하자. 역시 다음으로 진행. [Create J2EE Module] 창에서 하나의 웹 모듈만을 추가할 것이다. 웹 모듈 탭에서 Add? 버튼을 클릭한 후 [SiteMeshExampleWeb]이라고 입력한다. 추가되면 [You must add a server]라는 메시지를 확인할 수 있다.

이곳에서 추가한 Lomboz 서버를 사용하진 않지만 편의상 톰캣을 지정해 주도록 하자. [Targeted Servers] 탭으로 이동한 후 type의 콤보 박스에서 [Apache Tomcat v5.0.x]를 선택한 후 추가하고 [Finish] 버튼을 클릭한다. SiteMeshExample 프로젝트가 멋지게 생성되어 있을 것이다. 이제 빌드 패스를 수정해야 한다. SiteMeshExample 프로젝트를 오른쪽 클릭하고 프로퍼티를 클릭하여 프로퍼티 창을 오픈하자. 프로퍼티 창에서 [Java Build Path]를 선택한 후 [source] 탭으로 이동하여 [Add Folder]를 클릭한 후 j2src 폴더의 체크박스를 선택한 후 [OK] 버튼을 클릭한다. 확인 창이 나타나면 [Yes]를 선택하자.

소스 경로를 설정한 후에는 [source] 탭 아래 부분에 [Default output folder]를 브라우즈하여 /SiteMeshExample/SiteMeshExampleWeb/WEB-INF/classes 경로로 수정한다. 새롭게 추가되는 자바 파일의 디폴트 패키지는 j2src에서 시작되고 컴파일된 클래스 파일은 /SiteMeshExample/SiteMeshExampleWeb/WEB-INF/classes에 위치되어 설정할 SiteMeshExampleWeb 웹 애플리케이션이 참조된다. 빌드 경로의 설정이 끝났으면 왼쪽 트리 메뉴에서 [Tomcat] 항목으로 이동하여 톰캣 플러그인 설정을 추가하자. [General] 탭의 [Is a Tomcat Project] 체크박스를 체크하고 [Context name] 항목에 [/SiteMeshExampleWeb]을 입력한다. 루트 컨텍스트(root context)를 나타내는 /가 누락되지 않도록 주의하자. [subdirectory to set as web application root] 항목에도 /SiteMeshExampleWeb이라고 입력한다. 이 과정은 SIteMeshExmpleWeb의 컨텍스트와 루트 폴더를 설정하는 과정이다. 설정한 내용을 톰캣의 server.xml에 기록하기 위해 다음과 같은 절차를 수행해야 한다.

[SiteMeshWeb 프로젝트를 오른쪽 클릭 | Tomcat Project | Update Context Definition] 업데이트가 완료되면 톰캣 설치 폴더 하위 conf 폴더 내의 server.xml 설정 파일에 SiteMeshExampleWeb 컨텍스트가 추가된 것을 확인할 수 있다. 이제 개발 환경의 설정이 끝났다. 이클립스의 [Tomcat start] 버튼을 클릭하여 톰캣을 기동하고 http://localhost:/SiteMeshExampleWeb에 접근하면 환영 페이지를 확인할 수 있다. 마지막으로 sitemesh-example.war 예제 파일을 개발 환경으로 임포트해야 한다. SiteMeshWeb 프로젝트를 오른쪽 클릭한 후 [Import] 메뉴를 선택한다. 선택 창에서 [Zip file]을 선택한 후 [next] 버튼을 클릭한다. [From zip file] 항목에 다운받은 sitemesh-example.zip 파일을 선택하고 [Into folder] 항목은 웹 루트인 SiteMeshExample/SiteMeshExampleWeb을 선택한다. 중복된 파일은 모두 덮어 쓰도록 한다. 톰캣을 재 기동하고 http://localhost:/SiteMeshExampleWeb에 접근해 보자 SiteMesh 예제 사이트가 열릴 것이다.

지금까지 SiteMesh 예제 사이트를 이클립스 워크벤치에 구축해 보았다. 비단 SiteMesh 뿐만 아니라 J2EE 프로젝트를 위한 이클립스 설정은 이와 같은 방법과 크게 다르지 않으리라고 본다. 추가로 권하고 싶은 것은 SIteMesh를 좀 더 깊숙이 살펴보기 위해 SiteMesh 개발 환경을 설정하는 것이다 www.opensymphony.com/sitemesh/download.html에서 sitemesh-2.2.1.zip을 다운받아 압축을 풀고 sitemesh-2.2.1.zip/src/java의 모든 패키지를 구축한 개발 환경의 j2src 폴더 밑에 복사한다. sitemesh-2.2.1.zip/lib 밑의 모든 jar 파일을 구축한 개발 환경에 lib 폴더를 추가하여 복사한 후 프로젝트의 빌드 패스에 복사한 jar 파일들을 라이브러리 패스에 추가하자. 이렇게 되면 SiteMesh 라이브러리 개발 환경까지 완료 된다.

SiteMesh의 소스를 살펴보면서 전체적인 흐름을 파악한다면 도움이 될 것이다. 또 글의 마지막 단계에서 제작할 맵퍼를 컴파일 하기 위해서도 필요한 단계이다.


[#4] SiteMesh 뒤져보기

이제부터 SiteMesh의 예제 사이트를 살펴보고 장식자(decorator), 장식자 페이지(decorator page), 장식자 연결자(decorator mapper)에 대해 알아보자.

<화면 2> SiteMesh 예제 사이트

처음 우리를 맞는 것은 index.html이다. <화면 1>은 크게 네 영역으로 나누어 볼 수 있다. A라고 표기된 패널 영역과 B 헤더 영역, C 풋터 그리고 D 바디 영역이다.

<리스트 3> index.html

01 <html>
02     <head>
03         <title>SiteMesh Sample Site</title>
04     </head>
05     <body>
06         <p>Welcome to the SiteMesh sample...</p>
07         <h3>Samples</h3>
08         <ul>
09             <li><a href="counter.jsp">JSP counter</a></li>
10             <li><a href="mandelbrot.pl">Perl Mandelbrot</a></li>
11             <li><a href="inline.jsp">Inline Example</a></li>
12             <li><a href="toggledecorator.jsp">Toggling Decorator</a></li>
13             <li><a href="agent.jsp">Agent Mapped Decorator</a></li>
14             <li><a href="meta.html">Meta Test</a></li>
15             <li><a href="velocity.html">Velocity Decorator</a></li>
16             <li><a href="freemarker.html">Freemarker Decorator</a></li>
17             <li><a href="badpanel.html">Invalid panel source Test (not found)</a></li>
18             <li><a href="badsource.html">Invalid panel source Test (incorrect JSP)</a></li>
19         </ul>
20     </body>
21 </html>

뭔가 복잡한 웹 화면과 대조적으로 <리스트 3>의 내용은 상당히 간략하다. 크게 두 부분으로 나누어 볼 수 있는데 SiteMesh Sample Site 문구가 담긴 title 엘리먼트와 컨텐츠를 담고 있는 body 엘리먼트이다. SiteMesh가 <리스트 1>이 나타내야 하는 화면에 패널을 추가하고, 타이틀 내용을 가져다 헤더를 만들고, 풋터를 추가했다는 것을 알 수 있다. 적용된 장식자는 main이고 장식자 페이지는 /decorators/main.jsp이다.

<리스트 4> 장식자 페이지, /decorators/main.jsp

01 <%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
02 <%@ taglib uri="http://www.opensymphony.com/sitemesh/page" prefix="page" %>
03 <html>
04      <head>
05          <title><decorator:title default="Mysterious page..." /></title>
06          <link href="<%= request.getContextPath() %>/decorators/main.css" rel="stylesheet" type="text/css">
07          <decorator:head />
08      </head>
09  
10      <body>
11          <table width="100%" height="100%">
12              <tr>
13                  <td valign="top">
14                      <page:applyDecorator page="/tiny-panel.html" name="panel" />
15                      <page:applyDecorator page="/counter.jsp" name="panel" />
16                      <page:applyDecorator page="/google.html" name="panel" />
17                      <%--page:applyDecorator page="/random.pl" name="panel" /--%>
18                  </td>
19                  <td width="100%">
20                      <table width="100%" height="100%">
21                          <tr>
22                              <td id="pageTitle">
23                                  <decorator:title />
24                              </td>
25                          </tr>
26                          <tr>
27                              <td valign="top" height="100%">
28                                  <decorator:body />
29                              </td>
30                          </tr>
31                          <tr>
32                              <td id="footer">
33                                  <b>Disclaimer:</b>
34                                  This site is an example site to demonstrate SiteMesh. It serves no other purpose.
35                              </td>
36                          </tr>
37                      </table>
38                  </td>
39              </tr>
40          </table>
41  
42          <%-- Construct URL from page request and append 'printable=true' param --%>
43          <decorator:usePage id="p" />
44          <%
45              HttpServletRequest req = p.getRequest();
46              StringBuffer printUrl = new StringBuffer();
47              printUrl.appendreq.getRequestURI() );
48              printUrl.append("?printable=true");
49              if (request.getQueryString()!=null) {
50                  printUrl.append('&');
51                  printUrl.append(request.getQueryString());
52              }
53          %>
54          <p align="right"><a href="<%= printUrl %>">printable version</a> ]</p>
55  
56      </body>
57  </html>

<리스트 4>의 main이라 명명된 장식자는 SiteMeshExampleWeb의 전반적인 페이지에 적용되기 위해 제작된 메인 레이아웃이다. 장식자 페이지 /decorators/main.jsp의 소스를 보며 SIteMesh의 개략적인 기능을 살펴보자. 먼저 5라인의 <decorator:title/>은 장식될 페이지(여기서는 index.html)의 title 엘리먼트의 텍스트를 가져와 삽입한다. default 어트리뷰트는 title 엘리먼트의 텍스트를 얻는데 실패할 경우 보여줄 텍스트이다. 14~16라인의 를 통해 장식자를 현재의 장식자에 포함했다. page 어트리뷰트를 통해 삽입할 외부 자원(장식되어야 할 페이지)을 선택하고 name을 지정하여 장식할 장식자를 지정한다. 예제에서 사용된 장식자는 panel이다.

<화면 2>의 A 영역을 표현하고 있는 부분이 14~16라인이라는 사실을 어렵지 않게 알 수 있다. 23라인에서는 <decorator:title/> 태그를 이용하여 페이지 상단의 헤더를 장식했고 28번 라인에서 <decorator:body/> 태그를 이용하여 페이지의 본문을 장식하였다. 32라인부터 페이지에 하단을 장식할 풋터가 자리하고 있다. 43라인은 <decorator:usePage/> 태그를 이용하여 요청 객체에 접근한 후 현재 페이지의 url에 ?printable=true 쿼리스트링을 삽입하여 프린트 페이지의 링크를 생성하였다.

이 링크는 index.html?printable=true로 연결되고 SiteMesh는 printable=true라는 쿼리스트링을 파싱하게 되면 printable 장식자를 적용하여 왼쪽 패널과 헤더, 풋터를 제외한 인쇄하기 좋은 페이지로 장식할 것이다.

<표 1> SiteMesh 태그 라이브러리

장식자 태그, 페이지 태그 장식자 페이지를 생성할 때 사용되는 태그
<decorator:head /> 장식될 페이지의 <head>태그의 내용을 삽입
<decorator:body /> 장식될 페이지의 <body>태그의 내용을 삽입
<decorator:title /> 장식될 페이지의 <title>태그의 내용을 삽입
<decorator:getProperty /> 장식이 완료된 HTML 페이지의 <body> 태그 내에 이벤트 핸들러를 생성하기 위해 사용
<decorator:usePage /> 장식자 페이지에서 장식될 페이지의 페이지 객체를 얻을 수 있게 함
페이지 태그 장식자 페이지 내에서 다른 장식자를 포함할 때 사용
<page:applyDecorator /> 현재 장식자 페이지 내에 장식될 페이지와 장식자를 지정하여 삽입한다.
<page:param> <page:applyDecorator /> 사용시 해당 장식자에게 파라미터를 전달하기 위해 사용

그렇다면 브라우저가 톰캣에게 보낸 GET /SiteMeshExampleWeb/index.html http-request는 어떻게 됐기에 원래의 index.html에 대한 요청이 무시되었을까? <리스트 5>의 내용을 보면 다음과 같이 필터를 정의하고 있다.

<리스트 5> /WEB-INF/web.xml 중 필터가 정의된 부분

1 <filter>
2     <filter-name>sitemesh</filter-name>
3     <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
4 </filter>
5 
6 <filter-mapping>
7     <filter-name>sitemesh</filter-name>
8     <url-pattern>/*</url-pattern>
9 </filter-mapping>

SiteMeshExampleWeb에게 요청되는 모든 것은 PageFilter가 가로채어 처리하도록 필터가 설정되어 있다. 이 부분에 대해서는 나중에 다시 살펴보자.

SiteMesh의 기본적인 얼개를 살펴보았다. 이제부터 (여전히 궁금한 채 남아 있는) 어떻게 main이라는 장식자가 /decorators/main.jsp를 장식자 페이지로 갖는지, 또 왜 index.html에 장식자 main을 적용한 것인지, panel 장식자는 무엇이고 쿼리스트링에 따른 printable 장식자를 적용하게 된 것은 어떻게 가능한지 설정을 살펴보자. SiteMesh의 설정을 담당하고 있는 것은 /WEB-INF/sitemesh.xml과 /WEB-INF/decorator.xml 두 파일이다. 우선 decorator.xml의 설정 내용, 여러 장식자의 정의와 특정 장식자가 적용될 페이지의 리스트가 설정되어 있다.

<리스트 6> /WEB-INF/decorator.xml

01 <?xml version="1.0" encoding="ISO-8859-1"?>
02 
03 <decorators defaultdir="/decorators">
04     <!-- Any urls that are excluded will never be decorated by Sitemesh -->
05     <excludes>
06         <pattern>/exclude.jsp</pattern>
07         <pattern>/exclude/*</pattern>
08     </excludes>
09 
10     <decorator name="main" page="main.jsp">
11         <pattern>/*</pattern>
12     </decorator>
13 
14     <decorator name="panel" page="panel.jsp"/>
15     <decorator name="printable" page="printable.jsp"/>
16     <decorator name="black" page="black.jsp"/>
17     <decorator name="nopanelsource" page="nopanelsource.jsp"/>
18     <decorator name="badpanelsource" page="badpanelsource.jsp"/>
19 
20     <decorator name="velocity" page="velocity.vm">
21         <pattern>/velocity.html</pattern>
22     </decorator>
23 
24     <decorator name="freemarker" page="freemarker.ftl">
25         <pattern>/freemarker.html</pattern>
26     </decorator>
27 
28     <decorator name="test" page="test.jsp">
29         <pattern>/agent.jsp</pattern>
30     </decorator>
31 </decorators>

엘리먼트는 장식자들의 리스트를 정의하고 있다. defaultdir="/decorators" 어트리뷰트는 기본적으로 장식자 페이지들이 위치하고 있는 경로를 나타낸다. 엘리먼트는 장식자가 적용되지 않아야 할 패턴의 리스트이다.

main 장식자는 모든 패턴의 리소스에 적용되며 main.jsp를 장식자 페이지로 사용함을 알 수 있다. 그 외의 panel, printable, black, nonpanelsource, badpanelsource는 모두 패턴을 지정하지 않았고, 프리메이커(freemaker)와 벨로시티는 특정 페이지를 적용했다. 적절한 패턴이 설정되지 않은 장식자는 main 장식자 소스에서 보았듯이 다른 장식자 내에서 를 통해 포함할 때 사용되거나 곧 살펴볼 장식자 연결자에서 특정 장식자를 지정할 때에 사용된다. 후자의 예로써 printable 장식자는 sitemesh.xml에 정의된 PrintableDecoratorMapper 장식자 연결자를 통하여 사용된다.

지금까지 독자 여러분은 장식자와 장식자 페이지에 대해 알아보았다. 이제 장식자 연결자를 알아볼 차례다. 장식자 연결자는 SIteMesh에게 장식에 사용될 적합한 장식자를 선정해주는 역할을 하며 기본적으로 지원되는 장식자 연결자는 다음과 같은 조건에 따라 장식자 선정을 수행하도록 고안되었다.

  • 요청된 페이지나 경로에 따라 장식에 참여할 장식자의 결정
  • 요청이 일어난 시간 또는 로케일(locale), 브라우저에 따른 장식자의 결정
  • url 쿼리스트링, 요청 속성, 메타 태그 속성에 따라 장식자의 결정
  • 웹 페이지 사용자의 설정에 따른 장식자의 결정

필요에 따라 독자 여러분은 특정 조건에 반응하도록 장식자 연결자를 제작할 수도 있을 것이다. 장식자 연결자는 /WEB-INF/sitemesh.xml에 정의되어 있다. 해당 경로에서 설정 파일을 찾는데 실패하면 sitemesh-2.2.1.jar에 패키징되어 있는 sitemesh-default.xml 파일로 대체한다.

<리스트 7> /WEB-INF/sitemesh.xml

01 <sitemesh>
02     <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
03     <excludes file="${decorators-file}"/>
04 
05     <page-parsers>
06         <parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
07     </page-parsers>
08 
09     <decorator-mappers>
10         중략...
11     </decorator-mappers>
12 </sitemesh>

는 content-type에 따라 적용할 파서를 정의한다. 현재 text/html을 파싱할 때 사용되는 FastPageParser 만이 지원된다. 밑으로 를 통해 장식자 연결자를 정의하고 있다.

PageDecoratorMapper

메타 태그를 통하여 장식자를 선택할 때 사용된다. decorator_test라는 이름의 장식자를 특정 페이지에 적용하고 싶다면 와 같이 페이지에 메타 태그를 추가하거나 또는 <HTML decorator="decorator_test">와 같이 <HTML> 태그를 수정하여 사용한다.

1 <mapper class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
2     <param name="property.1" value="meta.decorator" />
3     <param name="property.2" value="decorator" />
4 </mapper>

AgentDecoratorMapper

브라우저에 따른 장식자를 선택할 수 있도록 한다. 브라우저 타입은 일반적인 경우와 같이 request.getHeader("User-Agent")를 통해 구해온다. 다른 연결자들이 특정 장식자를 지칭하여 반환하는 것과 비교적으로 AgentDecoratorMapper는 적용될 장식자의 장식자 페이지명을 브라우저에 따라 수정한다. 예를 들어 장식자 연결자 체인에 의해 main 장식자가 선정됐다면 장식자 페이지는 /decorators/main.jsp가 적용될 것이다. 하지만 http 클라이언트가 익스플로러라면 main 장식자에 /decorators/main-ie.jsp 장식자 페이지를 적용하게 된다. 모질라 기반 브라우저라면 /decorators/main-ns.jsp가 장식자 페이지로 결정될 것이다. 예상한대로 선정된 장식자 페이지(브라우저에 따른 서브픽스가 추가된)를 찾는데 실패한다면 기본 장식자 페이지인 main.jsp가 적용될 것이다.

1 <mapper class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
2     <param name="match.MSIE" value="ie" />
3     <param name="match.Mozilla [" value="ns" />
4     <param name="match.Opera" value="opera" />
5     <param name="match.Lynx" value="lynx" />
6 </mapper>

PrintableDecoratorMapper

PrintableDecoratorMapper는 url 쿼리스트링의 printable=true라는 문자열에 반응한다. /WEB-INF/decorator.xml에 정의된 장식자 중 printable 장식자를 리턴할 것이다.

1 <mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
2     <param name="decorator" value="printable" />
3     <param name="parameter.name" value="printable" />
4     <param name="parameter.value" value="true" />
5 </mapper>

ParameterDecoratorMapper

ParameterDecoratorMapper는 설정에 정의된 파라미터와 동일한 스트링이 url 쿼리스트링에 존재할 때 정의해 놓은 장식자를 선정하도록 되어 있다. 앞의 예에서는 confirm=true 문자열과 일치할 경우 somedecorator가 장식자로 선정될 것이다. 몇 가지 장식자 연결자의 쓰임새를 살펴보았다. 지금까지 장식자, 장식자 페이지, 장식자 연결자에 대해 모두 알아보았다. 이제부터 SiteMesh가 어떤 흐름을 가지고 필터를 포함한 4개의 오브젝트를 장식에 참여시키는지 알아보자. 현재 파싱이 지원되는 content-type은 text/html이므로 text/html request를 기준으로 한다.

1 <mapper class="com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper">
2     <param name="decorator.parameter" value="somedecorator" />
3     <param name="parameter.name" value="confirm" />
4     <param name="parameter.value" value="true" />
5 </mapper>


[#5] SiteMesh 구조와 흐름

󰋎 request 단계

모든 http-request는 PageFilter에서 가로채어진다. PageFilter는 컨텍스트 초기화 시점에 decorators.xml에 정의된 엘리먼트를 읽어 들여 요청된 http-request의 url-pattern에 따라 장식을 수행할지 여부를 결정한다.

󰋏 parsing 단계

PageFilter는 요청된 자원에 대한 웹 애플리케이션의 수행 결과를 doChain(request, response) 메쏘드를 호출하여 받아낸다. 응답 인자에 ServletResponse가 아닌 HttpServletResponseWrapper를 상속받아 확장한 PageResponseWrapper 객체를 사용한다. 따라서 페이지 아웃풋은 writer로 보내지지 않는다. PageResponseWrapper에서 캡처된 페이지 아웃풋 데이터는 content-type에 따라 선택된 파서에 의해 파싱되어 진다. 현재는 반환 페이지의 content-type이 text/html일 경우만 FastPageParser가 작동한다(보다시피 웹 애플리케이션과 관계없이 수행 결과를 파싱하므로 자바 기반이 아닌 사이트에도 문제 없이 서비스될 수 있을 것이다). 개인적으로 PageResponseWrapper가 HttpServletResponseWrapper를 상속받는 대신 ServletResponse 인터페이스를 구현하고 HttpServletResponseWrapper 클래스를 private 필드로 갖도록 컴포지션을 사용하는 것이 좋지 않았을까 생각한다.

󰋐 장식자 선정 단계

적합한 장식자를 결정한다. 이 과정은 연결자 클래스의 getDecorator() 메쏘드에서 이뤄진다. 처음 장식자 선정을 시도한 연결자 클래스가 적합한 장식자를 얻어낸다면 리턴할 것이고 아니면 장식자 체인상의 상위 링크의 getDecorator() 메쏘드를 호출하게 된다. 정확한 장식자가 결정되기까지 이 작업은 반복된다. 따라서 장식자 연결자 체인 상에 잘못 구현된 장식자 연결자가 있다면 장식자 선정 단계는 실패할 것이다.

󰋑 장식 단계

장식자가 결정되고 장식자로부터 장식자 페이지를 구해 올 수 있다면 PageFilter는 applyDecorator() 메쏘드를 호출하여 장식을 수행한다. applyDecorator() 내에서 장식자 페이지에 대한 요청을 생성하고 장식자 페이지는 FastPageParser에 의해 파싱된 결과 페이지의 적절한 부분들을 커스텀 태그를 통해 가져와 출력 페이지를 작성할 것이다. 복잡할 것 같던 SiteMesh의 시퀀스는 생각보다 간략하다.


[#6] 연결자를 만들어 보자

장식자 연결자를 직접 만들어 보자. 제작할 연결자는 HttpSession에 저장된 특정 키 값에 저장된 장식자의 이름에 따라 장식자를 리턴해 주는 것이 목적이다. <리스트 8>을 코딩 및 컴파일하고 /WEB-INF/sitemesh.xml에 SessionDecoratorMapper의 설정을 알려주도록 한다. 가급적이면 ParameterDecoratorMapper의 정의 바로 아래 추가하자. 등록된 다른 장식자들의 행동 패턴을 파악할 필요가 있는 부분이다.

<리스트 8> SessionDecoratorMapper.java

01 public class SessionDecoratorMapper extends AbstractDecoratorMapper {
02   private String sessionKey;
03   
04   public void init(Config config, Properties properties, DecoratorMapper parentthrows InstantiationException {
05       super.init(config, properties, parent);
06       sessionKey = properties.getProperty("session.key"null);
07   }
08   
09   public Decorator getDecorator(HttpServletRequest request, Page page) {
10     Decorator result = null;
11       
12       HttpSession session = request.getSession(false);
13      
14       if (session != null &&  session.getAttribute(sessionKey)!= null) { 
15         result = getNamedDecorator(request, (Stringsession.getAttribute(sessionKey));
16       }
17               
18       return result == null super.getDecorator(request, page: result;
19   }
20 }

<리스트 9> /WEB-INF/sitemesh.xml에 등록된 모습

1 <mapper class="kr.co.imaso.sitemesh.module.mapper.SessionDecoratorMapper">
2   <param name="session.key" value="decorator_selected" />
3 </mapper>


[#7] 남은 것은 개발의 즐거움뿐

아이디어의 빈곤과 마감의 압박으로 급조된 예제인지라 조금 부실하더라도 이해하기 바란다. 독자들은 직접 멋진 연결자와 나아가 파서까지 생성해 보기 바란다. 대형 포탈 사이트에서 제공하는 사용자 별 스킨 기능을 구성하는 것도 어렵지 않을 듯하다. 장식자와 파서의 확장은 무궁무진해 보인다. 특정 폴더의 gif/jpeg 자원에 대한 뷰어를 위해 특별한 파서와 장식자를 작성해도 좋을 것이다. jpeg 이미지의 촬영 정보를 레이아웃에 맞춰 보여줄 수도 있을 것이고 이미지 위에 워터마킹을 위한 처리도 가능할 것으로 보인다. 생각만 해도 즐겁지 않은가?

마지막으로 주의할 사항 두 가지를 짚고 넘어가자.

  • 첫 번째로 SiteMesh는 필터를 기반으로 작동한다. 필터는 서블릿 스펙 2.3에 추가된 기능이므로 스펙 2.3을 지원하는 서블릿 컨테이너를 사용해야 한다.
  • 두 번째 URL 요청이 아닌 RequestDispatcher.include()나 forward()를 통한 자원의 접근은 http-request가 없으므로 필터를 호출하지 않는다. 따라서 장식되지 않은 원래 자원이 반환될 것이다. 이 문제는 서블릿 스펙 2.4를 통해 해결할 수 있다. 서블릿 스펙 2.4에 따르면 다음과 같이 web.xml의 설정 상에 명시할 수 있다
1 <filter-mapping>
2     <filter-name>Logging Filter</filter-name>
3     <url-pattern>/products/*</url-pattern>
4     <dispatcher>REQUEST</dispatcher>
5     <dispatcher>FORWARD</dispatcher>
6     <dispatcher>INCLUDE</dispatcher>
7 </filter-mapping>

톰캣 5.5.7에서 앞의 설정이 예상대로 작동하지 않은 부분도 있었지만 forward()와 include()에 의한 필터의 호출은 문제없이 수행됐다. SiteMesh 프레임워크는 도입 계획에 따라 여러 가지 모습을 보여 줄 수 있다. 객체지향과는 다분히 거리가 먼 웹 페이지 구조를 조직화하고, 컴포넌트화 할 수 있는 방법을 제시하여 사이트를 한층 견고하게 하는데 일조를 할 수도 있으며, SiteMesh가 제공하는 유연함을 바탕으로 이전에는 불가능할 것 같았던 새로운 서비스를 구성할 수도 있을 것이다. 이제 남은 것은 개발하는 즐거움뿐이다.


정리 : 강경수 elegy@imaso.co..kr


[#8] 문서정보

  • 작성자 : 최원준 (위키편집: 김승권)
  • 작성일 : 2005년 4월 5일
  • 문서버전 : 1.0
  • 상태 : 작성완료
  • 참고자료
    • 이 문서는 월간 마이크로소프트웨어의 "개발자 추천 숨어있는 오픈소스" 코너에 기고된 글입니다.
    • 1. http://www.opensymphony.com/sitemesh/
    • 2. http://www.javajigi.net/article/framework/etc/Advanced_SiteMesh.html
    • 3. http://java.sun.com/j2ee/1.3/docs/tutorial/doc/
    • 4. http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html
    • 5. http://today.java.net/pub/a/today/2004/03/11/sitemesh.html
    • 6. Design Patterns, Erich Gamma 외, 김정아 역, 피어슨 에듀케이션 코리아, 2002
    • 7. Effective Java, Joshua Bloch, 이해일 역, 대웅미디어, 2003
    • 8. http://java.sun.com/j2se/1.5.0/download.jsp
    • 9. http://jakarta.apache.org/site/binindex.cgi#tomcat
    • 10. http://www.eclipse.org/downloads/index.php
    • 11. http://www.sysdeo.com/eclipse/tomcatPlugin.html
    • 12. http://forge.objectweb.org/projects/lomboz
    • 13. http://download.eclipse.org/tools/emf/scripts/downloads.php
  • 개정이력

1914 view

4.0 stars