Error and 404 page decoration workaround

Ethan Larson <[email protected]> Fri, 12 Nov 2010 13:51:15 -0800 (PST)
Newsgroups gmane.comp.web.sitemesh.general
Message-ID <[email protected]>
------=_Part_16652_510111761.1289598675858
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit

I'd like to put this out there in case some other poor soul has the same problem. The issue, in a nutshell, is that SiteMesh was decorating my 404 pages as long as they were generated by my servlet container (in this case Resin), but NOT when I was manually sending a 404 page via response.sendError(HttpServletResponse.SC_NOT_FOUND). 

After digging in to the SiteMesh code, I figured out what was going on. Basically, it comes down to the APPLIED_ONCE flag that SiteMeshFilter sets as a request attribute. Here are the relevant sections from my web.xml, and two scenarios: 

web.xml: 
<web-app ...> 
<error-page> 
<error-code>404</error-code> 
<location>/error/404.html</location> 
</error-page> 


<servlet><!-- Logs the error and forwards to another resource which actually produces the 404 page. In my specific case, it's the home page with an error message --> 
<servlet-name>errorHandler</servlet-name> 
<servlet-class>com.blah.servlets.ErrorHandlingServlet</servlet-class> 
</servlet> 
<servlet-mapping> 
<servlet-name>errorHandler</servlet-name> 
<url-pattern>/error/404.html</url-pattern> 
</servlet-mapping> 

<filter> 
<filter-name>sitemesh</filter-name> 
<filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class> 
</filter> 

<filter-mapping> 
<filter-name>sitemesh</filter-name> 
<url-pattern>*.html</url-pattern> 
<dispatcher>REQUEST</dispatcher> 
<dispatcher>FORWARD</dispatcher> 
<dispatcher>ERROR</dispatcher> 
</filter-mapping> 

<servlet> 
<servlet-name>fooServlet</servlet-name> 
<servlet-class>com.blah.servlets.FooServlet</servlet-class> 
</servlet> 
<servlet-mapping> 
<servlet-name>fooServlet</servlet-name> 
<url-pattern>/foo/*.html</url-pattern> 
</servlet-mapping> 
... 
</web-app> 

URI: /thisPageIsNotMappedToAServlet 
1. The servlet container can't find a mapping for the URI, so it immediately does an error request for /error/404.html 
2. SiteMeshFilter has not yet run, so it sets the APPLIED_ONCE flag and attempts to obtain the content from /error/404.html 
3. ErrorHandlingServlet logs the error and forwards to the home page, which ultimately produces the response 
4. SiteMeshFilter correctly grabs the content and decorates it. Success! 

URI: /foo/mappedToAServlet.html 
1. The servlet container finds a mapping for the URI, so it invokes FooServlet 
2. SiteMeshFilter has not yet run, so it sets the APPLIED_ONCE flag and attempts to obtain the content from /foo/mappedToAServlet.html 
3. FooServlet can't find the resource identified by '/foo/mappedToAServlet.html', so it calls 'resp;onse.sendError(HttpServletResponse.SC_NOT_FOUND);' 
4. The servlet container initiates an error request for /error/404.html, as specified in the web.xml 
5. SiteMeshFilter sees the APPLIED_ONCE flag and DOES NOT attempt to obtain content from the error request 
6. ErrorHandlingServlet logs the error and forwards to the home page, which ultimately produces the response 
7. When the request processing gets back to the original invocation of SiteMeshFilter (in step 2), the content has already been written as part of the error handling. The content for the original request is null, and SiteMeshFilter does nothing. Fail :( 

Now for my solution. I wrote a very small filter called ClearSitemeshAppliedOnceFilter. Here's the doFilter method: 

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
throws ServletException, IOException { 
HttpServletRequest request = (HttpServletRequest)servletRequest; 
request.removeAttribute("com.opensymphony.sitemesh.APPLIED_ONCE"); 
filterChain.doFilter(servletRequest, servletResponse); 
} 

It's declared in the web.xml thusly: 

<filter> 
<filter-name>clearSiteMeshAppliedOnce</filter-name> 
<filter-class>com.remilon.filters.ClearSitemeshAppliedOnceFilter</filter-class> 
</filter> 
<filter-mapping> 
<filter-name>clearSiteMeshAppliedOnce</filter-name> 
<url-pattern>*</url-pattern> 
<dispatcher>ERROR</dispatcher> 
</filter-mapping> 

All it does is clear the APPLIED_ONCE flag for error requests only. Then SiteMeshFilter will attempt to obtain content from said error requests, and the decoration will occur. Yay! 

A couple questions: 

1. Am I missing an easier way to do this? It seems like something that SiteMesh should handle on its own without me having to tack on another filter. 
2. Anyone see pitfalls with this approach? 

I've posted the workaround here as well: http://jira.opensymphony.com/browse/SIM-168 

Cheers, 
Ethan 

------=_Part_16652_510111761.1289598675858
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<html><head><style type=3D'text/css'>p { margin: 0; }</style></head><body><=
div style=3D'font-family: Times New Roman; font-size: 12pt; color: #000000'=
>I'd like to put this out there in case some other poor soul has the same p=
roblem. The issue, in a nutshell, is that SiteMesh was decorating my 404 pa=
ges as long as they were generated by my servlet container (in this case Re=
sin), but NOT when I was manually sending a 404 page via response.sendError=
(HttpServletResponse.SC_NOT_FOUND).<br><br>After digging in to the SiteMesh=
 code, I figured out what was going on. &nbsp;Basically, it comes down to t=
he APPLIED_ONCE flag that SiteMeshFilter sets as a request attribute. &nbsp=
;Here are the relevant sections from my web.xml, and two scenarios:<br><br>=
web.xml:<br>&lt;web-app ...&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&lt;error-page&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;error-code&gt;404&lt=
;/error-code&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;location&gt;/error/404.html&lt=
;/location&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/erro=
r-page&gt;<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;s=
ervlet&gt;&lt;!-- Logs the error and forwards to another resource which act=
ually produces the 404 page. In my specific case, it's the home page with a=
n error message --&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet-name&gt;errorHan=
dler&lt;/servlet-name&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet-class&gt;com.=
blah.servlets.ErrorHandlingServlet&lt;/servlet-class&gt;<br>&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/servlet&gt;<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet-mapping&gt;<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
lt;servlet-name&gt;errorHandler&lt;/servlet-name&gt;<br>&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&lt;url-pattern&gt;/error/404.html&lt;/url-pattern&gt;<br>&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/servlet-mapping&gt;<br><br>&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter&gt;<br>&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&lt;filter-name&gt;sitemesh&lt;/filter-name&gt;<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
lt;filter-class&gt;com.opensymphony.sitemesh.webapp.SiteMeshFilter&lt;/filt=
er-class&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/filter=
&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-mapp=
ing&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&gt;sitemesh&lt;/filter-name=
&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;*.html&lt;/url-pattern&gt;<=
br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;<br>&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&nbsp;&lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;<br>&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&nbsp;&nbsp;&lt;dispatcher&gt;ERROR&lt;/dispatcher&gt;<br>&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/filter-mapping&gt;<br><br>&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet&gt;<br>&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=
&lt;servlet-name&gt;fooServlet&lt;/servlet-name&gt;<br>&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp=
;&lt;servlet-class&gt;com.blah.servlets.FooServlet&lt;/servlet-class&gt;<br=
>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/servlet&gt;<br>&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;servlet-mapping&gt;<br>&nbsp;=
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb=
sp;&nbsp;&nbsp;&lt;servlet-name&gt;fooServlet&lt;/servlet-name&gt;<br>&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n=
bsp;&nbsp;&nbsp;&lt;url-pattern&gt;/foo/*.html&lt;/url-pattern&gt;<br>&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/servlet-mapping&gt;<br>&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&lt;/web-app&gt;<br><br>=
URI: /thisPageIsNotMappedToAServlet<br>1. The servlet container can't find =
a mapping for the URI, so it immediately does an error request for /error/4=
04.html<br>2. SiteMeshFilter has not yet run, so it sets the APPLIED_ONCE f=
lag and attempts to obtain the content from /error/404.html<br>3. ErrorHand=
lingServlet logs the error and forwards to the home page, which ultimately =
produces the response<br>4. SiteMeshFilter correctly grabs the content and =
decorates it. Success!<br><br>URI: /foo/mappedToAServlet.html<br>1. The ser=
vlet container finds a mapping for the URI, so it invokes FooServlet<br>2. =
SiteMeshFilter has not yet run, so it sets the APPLIED_ONCE flag and attemp=
ts to obtain the content from /foo/mappedToAServlet.html<br>3. FooServlet c=
an't find the resource identified by '/foo/mappedToAServlet.html', so it ca=
lls 'resp;onse.sendError(HttpServletResponse.SC_NOT_FOUND);'<br>4. The serv=
let container initiates an error request for /error/404.html, as specified =
in the web.xml<br>5. SiteMeshFilter sees the APPLIED_ONCE flag and DOES NOT=
 attempt to obtain content from the error request<br>6. ErrorHandlingServle=
t logs the error and forwards to the home page, which ultimately produces t=
he response<br>7. When the request processing gets back to the original inv=
ocation of SiteMeshFilter (in step 2), the content has already been written=
 as part of the error handling. &nbsp;The content for the original request =
is null, and SiteMeshFilter does nothing. Fail :(<br><br>Now for my solutio=
n. &nbsp;I wrote a very small filter called ClearSitemeshAppliedOnceFilter.=
 Here's the doFilter method:<br><br>public void doFilter(ServletRequest ser=
vletRequest, ServletResponse servletResponse, FilterChain filterChain)<br>&=
nbsp;throws ServletException, IOException {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;HttpServletRequest request =3D (HttpServletRequest)serv=
letRequest;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;request.remo=
veAttribute("com.opensymphony.sitemesh.APPLIED_ONCE");<br>&nbsp;&nbsp;&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filterChain.doFilter(servletRequest, servlet=
Response);<br>}<br><br>It's declared in the web.xml thusly:<br><br>&lt;filt=
er&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-name&g=
t;clearSiteMeshAppliedOnce&lt;/filter-name&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&=
nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-class&gt;com.remilon.filters.ClearSitemes=
hAppliedOnceFilter&lt;/filter-class&gt;<br>&lt;/filter&gt;<br>&lt;filter-ma=
pping&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;filter-nam=
e&gt;clearSiteMeshAppliedOnce&lt;/filter-name&gt;<br>&nbsp;&nbsp;&nbsp;&nbs=
p;&nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;*&lt;/url-pattern&gt;<br>&nbsp=
;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;dispatcher&gt;ERROR&lt;/disp=
atcher&gt;<br>&lt;/filter-mapping&gt;<br><br>All it does is clear the APPLI=
ED_ONCE flag for error requests only. &nbsp;Then SiteMeshFilter will attemp=
t to obtain content from said error requests, and the decoration will occur=
. Yay!<br><br>A couple questions:<br><br>1. Am I missing an easier way to d=
o this? It seems like something that SiteMesh should handle on its own with=
out me having to tack on another filter.<br>2. Anyone see pitfalls with thi=
s approach?<br><br>I've posted the workaround here as well: http://jira.ope=
nsymphony.com/browse/SIM-168<br><br>Cheers,<br>Ethan<br></div></body></html=
>
------=_Part_16652_510111761.1289598675858--