Spring 并且 SiteMesh 错误页面未修饰(跳过主要过滤器)
Spring and SiteMesh Error Page is not decorated (skips main filters)
几天来我一直在为一个相当荒谬的问题而苦苦挣扎:
我正在进行的项目正在使用 Spring MVC 和 FreeMarker 作为模板。
这是 运行 在 Tomcat 容器上(使用 Cargo 在本地测试)。
我正在处理的问题有在标准化错误页面中实现统一行为的简要说明,但涵盖了可能遇到的各种类型的错误。 (后端服务冒出异常,权限不足,http错误等)
目前,结果如下(含图):
- 图 A:页面的正常导航 - 按预期呈现。
- 图 B 和图 C:ControllerAdvice.java 捕获的服务和权限异常 - 同样,没有问题。
- 图 D:任何 HTTP 错误(是的,如果触发该响应,甚至是 418)- 内部 freemarker 模板已正确检索并填充了绑定,但过滤器应用的装饰无法触发。
目前我们正在使用 Spring 配置 servlet 处理,因此 web.xml 非常稀疏:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--
This application uses the config of the mapping by Spring MVC
This is why you will not see servlet declarations here
The web app is defined in
- butler.SpringWebInit
- butler.SpringWebConfig
-->
<context-param>
<description>Escape HTML form data by default when using Spring tags</description>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
<!-- Disabling welcome list file for Tomcat, handling it in Spring MVC -->
<welcome-file-list>
<welcome-file/>
</welcome-file-list>
<!-- Generic Error redirection, allows for handling in Spring MVC -->
<error-page>
<location>/http-error</location>
<!-- Was originally just "/error" it was changed for internal forwarding/proxying/redirection attempts -->
</error-page>
</web-app>
配置由 SpringWebInit.java 处理,我没有对其进行任何修改:
SpringWebInit.java
/**
* Automatically loaded by class org.springframework.web.SpringServletContainerInitializer
*
* @see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-container-config
*
* According to {@link AbstractSecurityWebApplicationInitializer}, this class should be
* annotated with a Order so that it is loaded before {@link SpringSecurityInit}
*/
@Order(0)
public class SpringWebInit extends AbstractAnnotationConfigDispatcherServletInitializer implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("DispatcherServlet loaded");
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null; // returning null, getRootConfigClasses() will handle this as well
}
@Override
protected String[] getServletMappings() {
return new String[] {"/**"}; // Spring MVC should handle everything
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringWebConfig.class, SpringSecurityConfig.class};
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter =
new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true);
return new Filter[] {characterEncodingFilter, new SiteMeshFilter()};
}
}
依次加载 Freemarker 和 Sitemesh 的各种配置:
SpringWebConfig.java
@EnableWebMvc
@Configuration
@PropertySource("classpath:/butler-init.properties")
@ComponentScan({"butler"})
class SpringWebConfig extends WebMvcConfigurerAdapter implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
@Autowired
LoggedInUserService loggedInUserService;
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("Web Mvc Configurer loaded");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userHeaderInterceptor());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCacheControl(
CacheControl.maxAge(30, TimeUnit.MINUTES).noTransform().cachePublic().mustRevalidate());
}
@Bean
FreeMarkerViewResolver viewResolver() throws TemplateException {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(/*true*/false); // Set to false for debugging
resolver.setPrefix("");
resolver.setSuffix(".ftlh");
resolver.setRequestContextAttribute("rContext");
resolver.setContentType("text/html;charset=UTF-8");
DefaultObjectWrapper wrapper =
new DefaultObjectWrapperBuilder(freemarker.template.Configuration.getVersion()).build();
Map<String, Object> attrs = new HashMap<>();
attrs.put("loggedInUserService", wrapper.wrap(loggedInUserService));
resolver.setAttributesMap(attrs);
return resolver;
}
@Bean
FreeMarkerConfigurer freeMarkerConfig() {
Properties freeMarkerVariables = new Properties();
// http://freemarker.org/docs/pgui_config_incompatible_improvements.html
// http://freemarker.org/docs/pgui_config_outputformatsautoesc.html
freeMarkerVariables.put(freemarker.template.Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY,
freemarker.template.Configuration.getVersion().toString());
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setDefaultEncoding("UTF-8");
freeMarkerConfigurer.setTemplateLoaderPath("/WEB-INF/mvc/view/ftl/");
freeMarkerConfigurer.setFreemarkerSettings(freeMarkerVariables);
return freeMarkerConfigurer;
}
@Bean
UserHeaderInterceptor userHeaderInterceptor() {
return new UserHeaderInterceptor();
}
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
SiteMeshFilter.java
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
@Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
// Don't use decorator REST api pages
builder.addExcludedPath("/api/*");
builder.addDecoratorPath("/*", Views.DECORATOR_HEADER_FOOTER);
builder.setIncludeErrorPages(true);
}
}
最后,进入问题的核心,错误处理是通过 DefaultControllerAdvice.java 和 ErrorController.java 本身的组合来处理的,DefaultControllerAdvice.java 提供拦截异常的规则,而 ErrorController.java 本身负责处理映射最后,消息处理(显示有关错误的信息,根据错误类型进行调整等)
默认ControllerAdvice.java
@ControllerAdvice(annotations = Controller.class)
class DefaultControllerAdvice {
private static String EXCEPTION = "butlerexception";
@ExceptionHandler(ServiceException.class)
public String exceptionHandler(ServiceException se, Model model) {
model.addAttribute(EXCEPTION, se.getMessage());
return Views.ERROR;
}
@ExceptionHandler(PermissionException.class)
public String exceptionHandler(PermissionException pe, Model model) {
model.addAttribute(EXCEPTION, "Incorrect Permissions");
return Views.ERROR;
}
/*@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(IOException.class)
public String exceptionHandler(Model model) { // Trying another way of intercepting 404 errors
model.addAttribute(EXCEPTION, "HTTP Error: 404");
return Views.ERROR;
}*/
}
ErrorController.java
@Controller
class ErrorController extends AbstractController {
@Autowired
private LoggedInUserService loggedInUserService;
@RequestMapping(path="error",method = {GET,POST}) // Normal Error Controller, Returns fully decorated page without issue for Exceptions and normal requests.
public String error(RedirectAttributes redirectAttributes, HttpServletResponse response,Model model) {
//if (redirectAttributes.containsAttribute("errorCode")) { // Trying to invisibly use redirection
// Map<String, ?> redirAttribs = redirectAttributes.getFlashAttributes();
// model.addAttribute("butlerexception", "HTTP Error: "+redirAttribs.get("errorCode"));
//} else {
model.addAttribute("butlerexception", "Error");
//}
return ERROR;
}
@RequestMapping("/http-error") // Created to test HTTP requests being proxied via ServiceExceptions, Redirections, etc...
public String httpError(/*RedirectAttributes redirectAttributes,*/ HttpServletResponse response, HttpServletRequest request, Model model){
model.addAttribute("butlerexception", "HTTP Error: " + response.getStatus());
//throw new ServiceException("HTTP Error: " + response.getStatus()); // Trying to piggyback off Exception handling
//redirectAttributes.addFlashAttribute("errorCode", response.getStatus()); // Trying to invisibly use redirection
//redirectAttributes.addFlashAttribute("originalURL",request.getRequestURL());
return /*"redirect:"+*/ERROR;
}
}
到目前为止,我已经尝试过:
- 抛出异常以搭载正在运行的 ControllerAdvice 规则。 - 结果未修饰。
- 添加响应代码规则,IONotFound nad NoHandlerFound 异常 - 结果未修饰。
- 重定向到错误页面 - 结果被正确修饰,但 URL 和响应代码不正确,尝试用原始请求 URL 掩盖 URL 结果正确URL 和代码,但和以前一样缺乏装饰。
此外,从调试日志中,我可以看到来自 Spring 安全的过滤器被正常触发,但涉及装饰网站的过滤器(对于登录请求和匿名请求)无法触发 HTTP仅错误。
目前的一个限制因素是我无法在 web.xml 中对系统进行全面的定义(因为这里和 Spring 文档中的许多解决方案似乎都需要)在此阶段不会对开发造成过度干扰。 (我也无权进行这样的更改(初级))
为方便起见,目前我尝试过的一些解决方案:
- Spring MVC 404 Error Page
- 404 error redirect in Spring with java config
- Generic Error Page not decorated
- Custom Error Page Not Decorated by Sitemesh in Spring Security Application
- Custom 404 using Spring DispatcherServlet
- <error-page> setup doesn't work in Spring MVC
此时我真的不确定还能尝试什么,我到底错过了什么?
编辑:事实证明这是 SiteMesh 中与触发 .setContentType(...)
有关的错误,通过在 sitemesh 之后再次设置 contentType 以触发装饰来解决该错误:Bug report with description and solution
这变成了一个两部分的问题,首先,SiteMesh3 对错误页面的处理意味着它认为它已经处理了所有过滤器,即使错误导致装饰器被跳过。 (expanded upon in this issue on github)
第二部分是当 SpringMVC 调用 .setContentType(...)
.
时,SiteMesh3 似乎只缓冲页面进行装饰
这是错误的,因为 Spring 只会在具有未定义内容类型的元素上触发它,而错误甚至在到达 Spring 之前就已经定义了它们的内容类型。 (expanded upon by my lead in this issue)
我的领导设法解决了这个问题,方法是在触发 .setContentType(...)
的 SiteMesh 之后添加一个过滤器,并强制 SiteMesh 缓冲页面以进行装饰。
有点重,因为这意味着每个请求都设置了两次内容类型,但它确实有效。
编辑:最初在这里有一条注释要求不要投票以避免收到我的领导找到的解决方案的代表,但发现一个博客 post 解释说自我回答不会赚钱代表 - 欢呼!
解决方案 1:
检查您是否已禁用 属性 spring.resources.add-mappings=false
。启用它可以解决问题。但在我的例子中,启用它完全删除了自定义错误页面。
解决方案 2:
根据对 github 问题 https://github.com/sitemesh/sitemesh3/issues/25 的评论,在您的 SiteMeshFilter
中声明自定义选择器:
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
@Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.setCustomSelector(new CustomBasicSelector());
}
private static class CustomBasicSelector extends BasicSelector {
private static final String ALREADY_APPLIED_KEY = BasicSelector.class.getName() + ".APPLIED_ONCE";
public CustomBasicSelector() {
super(true, "text/html");
}
protected boolean filterAlreadyAppliedForRequest(HttpServletRequest request) {
if (request.getDispatcherType().equals(DispatcherType.ERROR)) {
if (Boolean.TRUE.equals(request.getAttribute(ALREADY_APPLIED_KEY + ".ERROR"))) {
return true;
} else {
request.setAttribute(ALREADY_APPLIED_KEY + ".ERROR", true);
return false;
}
}
return super.filterAlreadyAppliedForRequest(request);
}
}
}
几天来我一直在为一个相当荒谬的问题而苦苦挣扎: 我正在进行的项目正在使用 Spring MVC 和 FreeMarker 作为模板。
这是 运行 在 Tomcat 容器上(使用 Cargo 在本地测试)。
我正在处理的问题有在标准化错误页面中实现统一行为的简要说明,但涵盖了可能遇到的各种类型的错误。 (后端服务冒出异常,权限不足,http错误等)
目前,结果如下(含图):
- 图 A:页面的正常导航 - 按预期呈现。
- 图 B 和图 C:ControllerAdvice.java 捕获的服务和权限异常 - 同样,没有问题。
- 图 D:任何 HTTP 错误(是的,如果触发该响应,甚至是 418)- 内部 freemarker 模板已正确检索并填充了绑定,但过滤器应用的装饰无法触发。
目前我们正在使用 Spring 配置 servlet 处理,因此 web.xml 非常稀疏:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!--
This application uses the config of the mapping by Spring MVC
This is why you will not see servlet declarations here
The web app is defined in
- butler.SpringWebInit
- butler.SpringWebConfig
-->
<context-param>
<description>Escape HTML form data by default when using Spring tags</description>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
<!-- Disabling welcome list file for Tomcat, handling it in Spring MVC -->
<welcome-file-list>
<welcome-file/>
</welcome-file-list>
<!-- Generic Error redirection, allows for handling in Spring MVC -->
<error-page>
<location>/http-error</location>
<!-- Was originally just "/error" it was changed for internal forwarding/proxying/redirection attempts -->
</error-page>
</web-app>
配置由 SpringWebInit.java 处理,我没有对其进行任何修改:
SpringWebInit.java
/**
* Automatically loaded by class org.springframework.web.SpringServletContainerInitializer
*
* @see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-container-config
*
* According to {@link AbstractSecurityWebApplicationInitializer}, this class should be
* annotated with a Order so that it is loaded before {@link SpringSecurityInit}
*/
@Order(0)
public class SpringWebInit extends AbstractAnnotationConfigDispatcherServletInitializer implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("DispatcherServlet loaded");
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null; // returning null, getRootConfigClasses() will handle this as well
}
@Override
protected String[] getServletMappings() {
return new String[] {"/**"}; // Spring MVC should handle everything
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {SpringWebConfig.class, SpringSecurityConfig.class};
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter =
new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true);
return new Filter[] {characterEncodingFilter, new SiteMeshFilter()};
}
}
依次加载 Freemarker 和 Sitemesh 的各种配置:
SpringWebConfig.java
@EnableWebMvc
@Configuration
@PropertySource("classpath:/butler-init.properties")
@ComponentScan({"butler"})
class SpringWebConfig extends WebMvcConfigurerAdapter implements InitializingBean {
private final Logger LOG = LoggerFactory.getLogger(getClass());
@Autowired
LoggedInUserService loggedInUserService;
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("Web Mvc Configurer loaded");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userHeaderInterceptor());
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCacheControl(
CacheControl.maxAge(30, TimeUnit.MINUTES).noTransform().cachePublic().mustRevalidate());
}
@Bean
FreeMarkerViewResolver viewResolver() throws TemplateException {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(/*true*/false); // Set to false for debugging
resolver.setPrefix("");
resolver.setSuffix(".ftlh");
resolver.setRequestContextAttribute("rContext");
resolver.setContentType("text/html;charset=UTF-8");
DefaultObjectWrapper wrapper =
new DefaultObjectWrapperBuilder(freemarker.template.Configuration.getVersion()).build();
Map<String, Object> attrs = new HashMap<>();
attrs.put("loggedInUserService", wrapper.wrap(loggedInUserService));
resolver.setAttributesMap(attrs);
return resolver;
}
@Bean
FreeMarkerConfigurer freeMarkerConfig() {
Properties freeMarkerVariables = new Properties();
// http://freemarker.org/docs/pgui_config_incompatible_improvements.html
// http://freemarker.org/docs/pgui_config_outputformatsautoesc.html
freeMarkerVariables.put(freemarker.template.Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY,
freemarker.template.Configuration.getVersion().toString());
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
freeMarkerConfigurer.setDefaultEncoding("UTF-8");
freeMarkerConfigurer.setTemplateLoaderPath("/WEB-INF/mvc/view/ftl/");
freeMarkerConfigurer.setFreemarkerSettings(freeMarkerVariables);
return freeMarkerConfigurer;
}
@Bean
UserHeaderInterceptor userHeaderInterceptor() {
return new UserHeaderInterceptor();
}
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
SiteMeshFilter.java
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
@Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
// Don't use decorator REST api pages
builder.addExcludedPath("/api/*");
builder.addDecoratorPath("/*", Views.DECORATOR_HEADER_FOOTER);
builder.setIncludeErrorPages(true);
}
}
最后,进入问题的核心,错误处理是通过 DefaultControllerAdvice.java 和 ErrorController.java 本身的组合来处理的,DefaultControllerAdvice.java 提供拦截异常的规则,而 ErrorController.java 本身负责处理映射最后,消息处理(显示有关错误的信息,根据错误类型进行调整等)
默认ControllerAdvice.java
@ControllerAdvice(annotations = Controller.class)
class DefaultControllerAdvice {
private static String EXCEPTION = "butlerexception";
@ExceptionHandler(ServiceException.class)
public String exceptionHandler(ServiceException se, Model model) {
model.addAttribute(EXCEPTION, se.getMessage());
return Views.ERROR;
}
@ExceptionHandler(PermissionException.class)
public String exceptionHandler(PermissionException pe, Model model) {
model.addAttribute(EXCEPTION, "Incorrect Permissions");
return Views.ERROR;
}
/*@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(IOException.class)
public String exceptionHandler(Model model) { // Trying another way of intercepting 404 errors
model.addAttribute(EXCEPTION, "HTTP Error: 404");
return Views.ERROR;
}*/
}
ErrorController.java
@Controller
class ErrorController extends AbstractController {
@Autowired
private LoggedInUserService loggedInUserService;
@RequestMapping(path="error",method = {GET,POST}) // Normal Error Controller, Returns fully decorated page without issue for Exceptions and normal requests.
public String error(RedirectAttributes redirectAttributes, HttpServletResponse response,Model model) {
//if (redirectAttributes.containsAttribute("errorCode")) { // Trying to invisibly use redirection
// Map<String, ?> redirAttribs = redirectAttributes.getFlashAttributes();
// model.addAttribute("butlerexception", "HTTP Error: "+redirAttribs.get("errorCode"));
//} else {
model.addAttribute("butlerexception", "Error");
//}
return ERROR;
}
@RequestMapping("/http-error") // Created to test HTTP requests being proxied via ServiceExceptions, Redirections, etc...
public String httpError(/*RedirectAttributes redirectAttributes,*/ HttpServletResponse response, HttpServletRequest request, Model model){
model.addAttribute("butlerexception", "HTTP Error: " + response.getStatus());
//throw new ServiceException("HTTP Error: " + response.getStatus()); // Trying to piggyback off Exception handling
//redirectAttributes.addFlashAttribute("errorCode", response.getStatus()); // Trying to invisibly use redirection
//redirectAttributes.addFlashAttribute("originalURL",request.getRequestURL());
return /*"redirect:"+*/ERROR;
}
}
到目前为止,我已经尝试过:
- 抛出异常以搭载正在运行的 ControllerAdvice 规则。 - 结果未修饰。
- 添加响应代码规则,IONotFound nad NoHandlerFound 异常 - 结果未修饰。
- 重定向到错误页面 - 结果被正确修饰,但 URL 和响应代码不正确,尝试用原始请求 URL 掩盖 URL 结果正确URL 和代码,但和以前一样缺乏装饰。
此外,从调试日志中,我可以看到来自 Spring 安全的过滤器被正常触发,但涉及装饰网站的过滤器(对于登录请求和匿名请求)无法触发 HTTP仅错误。
目前的一个限制因素是我无法在 web.xml 中对系统进行全面的定义(因为这里和 Spring 文档中的许多解决方案似乎都需要)在此阶段不会对开发造成过度干扰。 (我也无权进行这样的更改(初级))
为方便起见,目前我尝试过的一些解决方案:
- Spring MVC 404 Error Page
- 404 error redirect in Spring with java config
- Generic Error Page not decorated
- Custom Error Page Not Decorated by Sitemesh in Spring Security Application
- Custom 404 using Spring DispatcherServlet
- <error-page> setup doesn't work in Spring MVC
此时我真的不确定还能尝试什么,我到底错过了什么?
编辑:事实证明这是 SiteMesh 中与触发 .setContentType(...)
有关的错误,通过在 sitemesh 之后再次设置 contentType 以触发装饰来解决该错误:Bug report with description and solution
这变成了一个两部分的问题,首先,SiteMesh3 对错误页面的处理意味着它认为它已经处理了所有过滤器,即使错误导致装饰器被跳过。 (expanded upon in this issue on github)
第二部分是当 SpringMVC 调用 .setContentType(...)
.
这是错误的,因为 Spring 只会在具有未定义内容类型的元素上触发它,而错误甚至在到达 Spring 之前就已经定义了它们的内容类型。 (expanded upon by my lead in this issue)
我的领导设法解决了这个问题,方法是在触发 .setContentType(...)
的 SiteMesh 之后添加一个过滤器,并强制 SiteMesh 缓冲页面以进行装饰。
有点重,因为这意味着每个请求都设置了两次内容类型,但它确实有效。
编辑:最初在这里有一条注释要求不要投票以避免收到我的领导找到的解决方案的代表,但发现一个博客 post 解释说自我回答不会赚钱代表 - 欢呼!
解决方案 1:
检查您是否已禁用 属性 spring.resources.add-mappings=false
。启用它可以解决问题。但在我的例子中,启用它完全删除了自定义错误页面。
解决方案 2:
根据对 github 问题 https://github.com/sitemesh/sitemesh3/issues/25 的评论,在您的 SiteMeshFilter
中声明自定义选择器:
public class SiteMeshFilter extends ConfigurableSiteMeshFilter {
@Override
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.setCustomSelector(new CustomBasicSelector());
}
private static class CustomBasicSelector extends BasicSelector {
private static final String ALREADY_APPLIED_KEY = BasicSelector.class.getName() + ".APPLIED_ONCE";
public CustomBasicSelector() {
super(true, "text/html");
}
protected boolean filterAlreadyAppliedForRequest(HttpServletRequest request) {
if (request.getDispatcherType().equals(DispatcherType.ERROR)) {
if (Boolean.TRUE.equals(request.getAttribute(ALREADY_APPLIED_KEY + ".ERROR"))) {
return true;
} else {
request.setAttribute(ALREADY_APPLIED_KEY + ".ERROR", true);
return false;
}
}
return super.filterAlreadyAppliedForRequest(request);
}
}
}