Programming/Java

반응형

개인적으로 SiteMesh의 동장 방식이 Tiles보다 좋기 때문에 SiteMesh를 선호하는데, SiteMesh를 사용하면서 언제나 아쉬운 점은 <servlet-mapping>의 <url-pattern>의 값을 /catalog/*와 같은 경로 기반으로 설정한 경우 SiteMesh의 데코레이터 설정 파일에서 경로 패턴에 기반한 매칭을 사용할 수 없다는 점이다. (관련글: SiteMesh를 이용한 웹 페이지 데코레이션, http://javacan.tistory.com/entry/131) 특히 요즘처럼 (REST 방식의 유행으로) 확장자 없는 URL을 제공하는 게 멋처럼 느껴질 때에는 더더욱 SiteMesh의 지원이 아쉬웠다.


이런 아쉬움을 해소하기 위해 <servlet-mapping>의 <url-pattern>의 값에 상관없이 URL 경로를 이용해서 패턴 매칭을 하는 DecoratorMapper 클래스를 작성해보았다.

WemadeConfigDecoratorMapper 클래스 구현 코드

아래 코드는 실제로 구현해서 사용하고 있는 코드이다.

package com.wemade.sitemesh;

import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import com.opensymphony.module.sitemesh.Config;
import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.DecoratorMapper;
import com.opensymphony.module.sitemesh.Page;
import com.opensymphony.module.sitemesh.mapper.AbstractDecoratorMapper;
import com.opensymphony.module.sitemesh.mapper.ConfigLoader;

/**
 * 서블릿 경로가 아닌 컨텍스트 경로를 제외한 요청 URI를 이용해서 패턴 매칭을 수행한다.
 * 
 * Sitemesh 2.4.1 버전의 ConfigDecoratorMapper로부터 코드를 가져와서 작성하였음.
 * 
 * @author 최범균
 * @version 2010. 3. 5.
 */
public class WemadeConfigDecoratorMapper extends AbstractDecoratorMapper {

    private ConfigLoader configLoader = null;

    /** Create new ConfigLoader using '/WEB-INF/decorators.xml' file. */
    public void init(Config config, Properties properties,
            DecoratorMapper parent) throws InstantiationException {
        super.init(config, properties, parent);
        try {
            String fileName = properties.getProperty("config",
                    "/WEB-INF/decorators.xml");
            configLoader = new ConfigLoader(fileName, config);
        } catch (Exception e) {
            throw new InstantiationException(e.toString());
        }
    }

    /**
     * Retrieve {@link com.opensymphony.module.sitemesh.Decorator} based on
     * 'pattern' tag.
     */
    public Decorator getDecorator(HttpServletRequest request, Page page) {
        String thisPath = request.getRequestURI();
        String contextPath = request.getContextPath();
        if (thisPath.startsWith(contextPath)) {
            thisPath = thisPath.substring(contextPath.length());
        }
        String name = null;
        try {
            name = configLoader.getMappedName(thisPath);
        } catch (ServletException e) {
            e.printStackTrace();
        }
        Decorator result = getNamedDecorator(request, name);
        return result == null ? super.getDecorator(request, page) : result;
    }

    /**
     * Retrieve Decorator named in 'name' attribute. Checks the role if
     * specified.
     */
    public Decorator getNamedDecorator(HttpServletRequest request, String name) {
        Decorator result = null;
        try {
            result = configLoader.getDecoratorByName(name);
        } catch (ServletException e) {
            e.printStackTrace();
        }

        if (result == null
                || (result.getRole() != null && !request.isUserInRole(result
                        .getRole()))) {
            // if the result is null or the user is not in the role
            return super.getNamedDecorator(request, name);
        } else {
            return result;
        }
    }

}

sitemesh.xml 파일 설정에서 커스텀 DecoratorMapper 사용

sitemesh.xml 파일에서는 아래 코드와 같이 앞서 구현한 DecoratorMapper를 사용하도록 설정한다.

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

    <page-parsers>
        <parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser" />
    </page-parsers>
    <decorator-mappers>
        <mapper class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
            <param name="property.1" value="meta.decorator" />
            <param name="property.2" value="decorator" />
        </mapper>
        <mapper class="com.opensymphony.module.sitemesh.mapper.FrameSetDecoratorMapper"/>
        <mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
            <param name="decorator" value="printable" />
            <param name="parameter.name" value="printable" />
            <param name="parameter.value" value="true" />
        </mapper>
        <mapper class="com.opensymphony.module.sitemesh.mapper.FileDecoratorMapper"/>
        <mapper class="com.wemade.sitemesh.WemadeConfigDecoratorMapper">
            <param name="config" value="${decorators-file}" />
        </mapper>
    </decorator-mappers>
</sitemesh>

web.xml 파일 및 decorator.xml 파일 설정

web.xml 파일에서 다음과 같이 경로 기반의 패턴을 사용하고 있다고 해 보자.

<web-app ...>
    ...
    <servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>/my/*</url-pattern>
        <url-pattern>/data/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

위 코드에서 ControllerServlet은 경로 기반의 <url-pattern>을 사용하고 있는데, 앞서 구현한 커스텀 DecoratorMapper를 사용함으로써 다음과 같이 데코레이터 파일에서 URL 패턴을 이용해서 매칭을 할 수 있게 된다.

<decorators defaultdir="/decorators">

    <decorator name="my" page="/WEB-INF/view/common/decorator/my.jsp">
        <url-pattern>/my/*</url-pattern>
    </decorator>

    <decorator name="data" page="/WEB-INF/view/common/decorator/data.jsp">
        <url-pattern>/data/*</url-pattern>
    </decorator>

</decorators>

그 동안 경로 기반의 서블릿 매핑을 사용할 때 SiteMesh 때문에 섭섭함이 있었던 개발자에게 본 글이 조금이나마 도움이 되길 바란다.

 

반응형

SiteMesh 설정 및 사용법

2013. 7. 16. 18:12
반응형
SiteMesh를 이용한 웹 페이지 레이아웃 설정

SiteMesh를 이용하여 웹 페이지의 레이아웃을 처리하는 방법을 살펴본다.

SiteMesh의 동작 방식과 설치

웹 어플리케이션을 구성하고 있는 웹 페이지들은 대부분은 페이지 레이아웃이 동일하게 구성되어 있다. 예를 들어, 미디어 다음의 스포츠 게시판을 보면, 각 페이지는 아래 그림과 동일한 형태로 구성되어 있는 것을 확인할 수 있다.


위 그림에서 내용 부분을 제외한 나머지 헤더, 푸터, 좌측 메뉴, 그리고 우측 주요기사는 모든 페이지에서 동일한 위치에 나타낸다. 즉, 게시글 목록 페이지와 게시글 쓰기 페이지는 모두 위 그림과 동일한 레이아웃을 갖는 것이다.

이렇게 동일한 레이아웃을 여러 페이지에 적용해야 할 때, 가장 쉽게 사용할 수 있는 방법이 <jsp:include>나 <%@ include %>를 사용하는 것이다. 하지만, 이는 중복된 코드를 발생시킬 가능성이 높기 때문에, Tiles나 Velocity가 제공하는 레이아웃 기능을 사용하여 구현하게 된다. 추가적으로 SiteMesh를 사용하여 레이아웃을 여러 페이지에 적용할 수 있다.

Tiles나 Velocity 또는 <jsp:include>를 사용하는 방식이 전체 페이지 중 내용 부분에 해당하는 코드만을 생성하는 방식이라면, SiteMesh는 완전한 HTML 페이지를 생성한 뒤 Decorator 패턴을 사용하여 HTML 페이지에 레이아웃을 입히는 방식이다. 본 글에서는 SiteMesh의 동작방식에 대해서 살펴보고, SiteMesh를 사용하여 여러 웹 페이지에 레이아웃을 동일하게 적용하는 방법을 살펴볼 것이다.

SiteMesh의 동작 방식

SiteMesh는 Tiles와 같은 프레임워크와 달리 완전한 HTML 코드로부터 레이아웃이 적용된 새로운 HTML 코드를 생성해낸다. 아래 그림은 SiteMesh의 동작방식을 설명한 것이다.


위 그림에서 데코레이터에 전달되는 HTML 페이지는 <html>, <head>, <body> 등을 포함한 완전한 HTML 페이지이다. 이때 데코레이터에 전달되는 HTML 페이지는 레이아웃과 관련된 내용은 포함되지 않는다. 데코레이터는 레이아웃 정보를 담고 있는 JSP 페이지로서, 앞서 생성한 HTML 페이지에 저장된 (<title> 등의) 메타 정보와 <body> 태그에 포함된 내용을 추출한 뒤, 레이아웃의 알맞은 위치에 추출한 내용을 삽입하여 최종 결과를 생성하게 된다.

예를 들어, 앞서 그림에서 welcome.jsp의 경우를 살펴보자. welcome.jsp는 레이아웃과 관련된 코드를 생성하지 않고 단지 메타 정보와 내용 부분에 들어가는 정보만을 생성하게 된다. welcome.jsp가 생성한 HTML 페이지는 데코레이터에 전달된다. 데코레이터는 welcome.jsp가 생성한 내용으로부터 메타 정보와 BODY 부분을 추출한 뒤 데코레이터의 알맞은 위치에 삽입하여 최종 결과를 생성한다.

SiteMesh 설치

SiteMesh는 서블릿 환경에서 동작하며, http://www.opensymphony.com/sitemesh/download.action 사이트에서 최신 버전을 다운로드 받을 수 있다. 이 글을 쓰는 시점에서 최신 버전은 2.3 버전으로서 sitemesh-2.3.jar 파일을 다운로드 받은 뒤, 웹 어플리케이션 콘텍스트의 WEB-INF/lib 디렉토리에 복사하면 설치가 완료된다.

SiteMesh를 이용한 레이아웃 적용

SiteMesh를 사용하여 웹 페이지에 레이아웃을 적용하기 위해서는 다음의 두 가지를 필요로 한다.

  • SiteMesh 설정 파일
  • 데코레이터

SiteMesh 설정 1, web.xml

SiteMesh를 설정하기 위해서는 먼저 SiteMesh가 제공하는 PageFilter(서블릿 필터)를 설정해주어야 한다.
아래 코드는 설정 예이다.

<?xml version="1.0" encoding="UTF-8"?>
< web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
                      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>
            com.opensymphony.module.sitemesh.filter.PageFilter
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>


위 코드는 / 로 들어오는 모든 요청에 대해서 PageFilter를 적용한다고 설정하였다. PageFilter는 요청 URL과 매칭되는 데코레이터를 검색한 뒤, 데코레이터가 발견될 경우 결과 HTML에 매칭되는 데코레이터를 적용한다. 따라서, 데코레이터가 적용되어야 하는 URL의 경우 반드시 PageFilter에 매핑시켜주어야 한다.

SiteMesh 설정 2, decorators.xml 작성

web.xml 파일에 PageFilter 매핑을 설정한 다음에는, 실제 데코레이터에 대한 정보를 담고 있는 decorators.xml 파일을 작성해주어야 한다. decorators.xml 파일은 데코레이터에 대한 설정 정보를 담게 된다.

decorators.xml 파일은 다음과 같은 형태로 데코레이터 목록을 기술한다.

<decorators defaultdir="/decorators">
    <decorator name="submenu" page="submenu_decorator.jsp">
        <pattern>/sub/*</pattern>
    </decorator>

    <decorator name="main" page="main_decorator.jsp">
        <pattern>/*</pattern>
    </decorator>

</decorators>


위 코드에서 <decorators> 태그의 defaultdir 속성은 데코레이터 JSP가 위치할 경로를 의미한다. 이 경로는 웹 어플리케이션 콘텍스트 내에서의 경로를 의미한다.

<decorator> 태그는 한 개의 데코레이터를 설정한다. <decorator> 태그의 두 속성은 다음과 같다.

  • name - 데코레이터의 이름
  • page - 데코레이터로 사용될 JSP 페이지

<pattern> 태그는 데코레이터를 적용할 패턴을 의미한다. 이 패턴은 서블릿 매핑에서의 패턴과 비슷하다. 예를 들어, /sub/submain1.jsp 나 /sub/menu/submenu1.jsp 로 요청이 들어올 경우 'submenu' 데코레이터가 적용되며, 그 외 /main.jsp나 /another/another1.jsp와 같이 /sub/* 에 포함되지 않는 요청의 경우는 'main' 데코레이터가 적용된다.

만약 정확하게 일치하는 <pattern> 값이 존재할 경우 해당 데코레이터를 사용한다. 예를 들어, 아래의 설정을 보자.

<decorator name="submenu" page="submenu_decorator.jsp">
    <pattern>/sub/*</pattern>
</decorator>
    
<decorator name="submain" page="submain_decorator.jsp">
    <pattern>/sub/submain1.jsp</pattern>
</decorator>


이 경우 /sub/submain1.jsp는 submenu 데코레이터와 submain 데코레이터에 모두 매핑되지만, 좀더 정확하게 일치하는 submain 데코레이터가 사용된다.

한 개의 <decorator> 태그는 0개 이상의 <pattern> 태그를 포함할 수 있다.

서블릿 매핑 시 주의 사항

서블릿 매핑을 사용할 경우 <pattern> 값은 서블릿의 경로를 따른다. 예를 들어, 다음과 같이 서블릿 매핑을 설정했다고 하자.

<servlet-mapping>
    <servlet-name>content</servlet-name>
    <url-pattern>/catalog/*</url-pattern>
< /servlet-mapping>


이 경우, 지정한 서블릿 매핑에 해당되는 요청에 데코레이터를 적용하고자 한다면, 다음과 같이decorators.xml의 <pattern> 태그의 값으로 서블릿 경로명을 지정해주어야 한다.

<decorator name="catalog" page="catalog_decorator.jsp">
	<pattern>/catalog</pattern>
</decorator>


만약 서블릿 경로명이 아닌 /catalog/* 를 <pattern> 태그의 값으로 지정할 경우 해당 데코레이터가 적용되지 않는다.

데코레이터 작성

SiteMesh의 데코레이터는 JSP 페이지로서, SiteMesh가 제공하는 커스텀 태그를 사용하여 결과 HTML 페이지를 데코레이션하게 된다. 아래 코드는 간단하게 작성해본 SiteMesh의 데코레이터 코드이다.

<%@ page contentType="text/html; charset=UTF-8" %>
< %@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator" %>
< html>
    <head>
        <title><decorator:title default="테크리포트" /></title>
        <decorator:head />
    </head>
    <body>
    <div>헤더</div>
    <hr/>
    
    <decorator:body />
    
    <hr/>
    <div>푸터</div>
    </body>
< /html>


위 코드에서 눈여겨 볼 부분은 decorator로 시작하는 커스텀 태그이다. 사용자의 요청을 처리한 결과 페이지는 데코레이터에 전달되는데, 이때 커스텀 태그를 사용하여 전달된 페이지의 내용을 사용할 수 있게 된다. 예를 들어, <decorator:title> 커스텀 태그는 전달된 페이지의 <title> 태그의 값을 구하게 된다. 사용가능한 커스텀 태그는 다음과 같다.

<decorator:head />

HTML의 <head> 태그의 내용을 삽입한다.

<decorator:body />

<body> 태그의 내용을 삽입한다.

<body> 태그에 명시된 프로퍼티의 값을 데코레이터 JSP에 삽입하고 싶다면 다음과 같이 <decorator:getProperty> 커스텀 태그를 사용하면 된다.

<body onload="<decorator:getProperty property="body.onload" />">
…
   <decorator:body />
…
</body>


<decorator:title [ default="..." ] />

<title> 태그에 명시된 타이틀을 삽입한다. 만약 <title> 태그의 값이 발견되지 않을 경우 default 속성에 명시한 값을 삽입한다.

<decorator:getProperty property="." [default="."] [writeEntireProperty="." ]/>

원본 HTML 페이지의 프로퍼티를 삽입한다. 이때 사용가능한 프로퍼티는 다음과 같이 생성된다.

  • HTML Tag:
    < html> 태그의 모든 속성이 프로퍼티로 추가된다.
  • TITLE Tag:
    < title> 태그의 내용이 'title' 프로퍼티로 추가된다.
  • META Tags:
    이름과 내용을 갖는 모든 <meta> 태그는 'meta.이름' 프로퍼티로 추가된다.
  • BODY Tag:
    모든 <body> 태그의 속성이 'body.속성이름' 프로퍼티로 추가된다.

<decorator:getProperty> 커스텀 태그에서 사용가능한 속성은 다음과 같다.

  • property (필수) - 삽입할 프로퍼티의 이름(키)
  • default (선택) - 프로퍼티가 존재하지 않을 경우 삽입할 값
  • writeEntireProperty (선택) - 프로퍼티의 이름 및 이름 앞의 공백을 함께 삽입할 지의 여부를 지정한다. 허용되는 값은 'true', 'yes', 또는 '1' 이다.

예를 들어, 원본 페이지에서 다음과 같이 <body> 태그를 작성했다고 하자.

<body onload="document.someform.somefield.focus();">


그리고 데코레이터 JSP에서 다음과 같이 <decorator:getProperty> 커스텀 태그를 사용했다고 하자.

<body bgcolor="White" <decorator:getProperty property="body.onload" 
           writeEntireProperty="true" />>


이 경우 최종적으로 생성되는 코드는 다음과 같다.

<body bgcolor="White" onload="document.someform.somefield.focus();">


테스트 코드

데코레이션 될 JSP 코드

간단하게 SiteMesh의 데코레이터를 통해 레이아웃이 적용될 HTML 페이지를 생성하는 JSP 페이지를 다음과 같이 작성해보자. 이 JSP의 경로는 /sub/submain1.jsp 라고 하자.

<%@ page contentType="text/html; charset=UTF-8" %>
< html>
< head>
    <title>서브 메인 1</title>
    <script type="text/javascript">
    window.onload = function() {
    }
    </script>
< /head>
< body>
    서브 메인 1
< /body>
< /html>


데코레이터 JSP

데코레이터 JSP인 /decorators/submenu_decorator.jsp를 아래와 같이 작성해보았다.

<%@ page contentType="text/html; charset=UTF-8" %>
< %@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator" %>
< html>
    <head>
        <title><decorator:title default="테크리포트" /></title>
        <decorator:head />
    </head>
    <body>
    <div>공통 헤더</div>
    <hr/>
    <decorator:body />
    <hr/>
    <div>공통 푸터</div>
    </body>
< /html>


데코레이터 JSP를 작성했으면 decorators.xml 파일에 등록해주어야 한다. 아래 코드는 등록 예이다. 앞서 원본 JSP의 경로를 /sub/submain1.jsp로 지정하였으므로, 아래 코드에서 <pattern>의 값을 '/sub/*'로 지정하였다.

<decorators defaultdir="/decorators">
    <decorator name="submenu" page="submenu_decorator.jsp">
        <pattern>/sub/*</pattern>
    </decorator>
< /decorators>


테스트 결과

이제 웹 브라우저에서 실제로 출력 결과를 확인해보자. 웹 브라우저에서 http://…/[contextPath]/sub/submain1.jsp를 입력한 뒤, 출력된 결과의 소스 코드는 다음과 같을 것이다.

<html>
    <head>
        <title>서브 메인 1</title>
        
    
    <script type="text/javascript">
    window.onload = function() {
    }
    </script>

    </head>
    <body>
    <div>공통 헤더</div>
    <hr/>
    
    서브 메인 1

    <hr/>
    <div>공통 푸터</div>
    </body>
< /html>


위 코드를 보면 원본 JSP가 출력한 결과가 데코레이터를 통해 알맞은 위치에 삽입된 것을 확인할 수 있다.

[참고]

반응형
반응형
java.lang.IllegalArgumentException: taglib definition not consistent with specification version 
최신버전에 이클립스를 사용할 경우 나타나는 현상. 
web.xml의 taglib 위 아래로 jsp-config 태그로 감싸준다.

  
    http://java.sun.com/jstl/core
    /WEB-INF/tld/c.tld
  


반응형

+ Recent posts

반응형