views:

64

answers:

1

I'm working on a JSF web application in which I need to bring up a "Session Expired" page if the view expires, but a general technical error page for all others. The application only goes to the technical error page when I trigger the exception. Here's the error-page definitions:

<error-page> 
    <exception-type>javax.faces.application.ViewExpiredException</exception-type> 
    <location>/jsps/utility/sessionExpired.jsp</location> 
</error-page> 
<error-page> 
    <exception-type>java.lang.Throwable</exception-type> 
    <location>/jsps/utility/technicalError.jsp</location> 
</error-page> 
<error-page>
    <error-code>500</error-code>
    <location>/jsps/utility/technicalError.jsp</location>
</error-page>

I removed the technicalError.jsp error page elements and it worked fine, but when I put them back I can't get to the sessionExpired.jsp page. How do I tell the web container the order to evaluate these tags so that the right page comes up? Thanks.

A: 

This is because the ViewExpiredException is been wrapped in a ServletException as per the JSF specification. Here's an extract of chapter 10.2.6.2 of the JSF 1.2 specification:

10.2.6.2 FacesServlet

Call the execute() method of the saved Lifecycle instance, passing the FacesContext instance for this request as a parameter. If the execute() method throws a FacesException, re-throw it as a ServletException with the FacesException as the root cause.

How the error pages are allocated is specified in Servlet API specification. Here's an extract of chapter 9.9.2 of Servlet API specification 2.5:

SRV.9.9.2 Error Pages

If no error-page declaration containing an exception-type fits using the class-hierarchy match, and the exception thrown is a ServletException or subclass thereof, the container extracts the wrapped exception, as defined by the ServletException.getRootCause method. A second pass is made over the error page declarations, again attempting the match against the error page declarations, but using the wrapped exception instead.

In class hierarchy, ServletException already matches Throwable (even when its entry is removed, the status code would also already match 500), so its root cause won't be extracted for the second pass.

To prove this specified behaviour, replace javax.faces.application.ViewExpiredException by javax.servlet.ServletException and retry. You'll see the expected error page being displayed.

Easiest way to workaround this is to create a Filter which listens on an url-pattern of /* and does basically the following:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    try {
        chain.doFilter(request, response);
    } catch (ServletException e) {
        Throwable rootCause = e.getRootCause();
        if (rootCause instanceof RuntimeException) { // This is true for any FacesException.
            throw (RuntimeException) rootCause; // Throw wrapped RuntimeException instead of ServletException.
        } else {
            throw e;
        }
    }
}

Update: as per the (deleted) comment of the OP: to reliably test this you cannot do a throw new ViewExpiredException() in a bean constructor or method or so. It would in turn get wrapped in some EL exception. You can eventually add a debug line printing rootCause in the Filter to see it yourself.

If you're using Eclipse/Tomcat, a quick way to test ViewExpiredException is the following:

  1. Create a JSF page with a simple command button, deploy and run it and open it in webbrowser.
  2. Go back to Eclipse, rightclick Tomcat server and choose Clean Tomcat Work Directory. This will restart Tomcat and trash all serialized sessions (important! just restarting Tomcat is not enough).
  3. Go back to webbrowser and press the command button (without reloading page beforehand!).
BalusC
Thanks for the detailed explanation. Unfortunately, the solution does not work. The technical error pages comes up instead of session expired page. I terminated the session by restarting the application in the middle of a session. I found that I can do a response.sendRedirect in that try block instead of throwing a runtime exception, but then I'm hard-coding a path to a view instead of letting the container control it. Not preferred, but I don't know what else to do.
Luke
This works fine here on Tomcat 6.0.20. What servletcontainer are you using? What rootCause are you getting?
BalusC
I didn't mention that I cannot remove the <error-page> block for the error code 500. It's part of a coding standard in my shop.
Luke
Websphere Application Server v6.1.
Luke
I'm getting the expected root cause, ViewExpiredException. It seems like everything *should* be working, but the container is just not "prioritizing" the errors correctly.
Luke
I really appreciate your help so far; it's been a privilege to learn from such a knowledgeable person.
Luke
Websphere, sorry, I don't have that running here right now. I did another test in Glassfish to be sure and it works there as well. Seems to be a Websphere specific issue now .. Try upgrading .. What's the behaviour when you replace `ViewExpiredException` in `<exception-type>` by `ServletException`?
BalusC
I would just get the technical error page. I've decided to forego letting the container handle it and just do a redirect. Since the issue is WebSphere specific and I'm forced to use it, that's the best I'm going to do.
Luke