반응형
개인적으로 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.madvirus.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.madvirus.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 때문에 섭섭함이 있었던 개발자에게 본 글이 조금이나마 도움이 되길 바란다.