Thoughts on…

Java Middleware & Systems Management

Custom JSF/Facelet Exception Handling

with 13 comments

Have you ever used a JSF-based application, navigated to some page, only to see a nasty error?

This is what you get out-of-box with facelets. Most of the time it happens when the facelet tries to resolve some EL expression, needs to create some JSF managed bean, but one or more required URL parameters are either missing or have invalid values.

In a development environment, it makes sense to show this page because the various pieces of contextual information (full stack trace + JSF component tree + variables in scope) provide plenty of clues with which to diagnose the issue. However, when you ship a product to a customer or push your changes to a production environment, it would be nice to change the behavior and provide a pleasant error page to the user.

Fortunately, the facelets framework makes overriding this default behavior incredibly simple. The basic premise is to redirect to a custom error page so you can provide a layout that hides the unappealing stack trace, but which still provides a link to view those details (primarily so your customers can report the bugs back to you).

Note: the following code examples will be pulled directly from the RHQ / Jopr code base.

The first step is to add a custom view handler to your web application. Open up the faces-config.xml file and add a custom view handler:

<faces-config ...>
   <application>
      <view-handler>org.rhq.enterprise.gui.common.framework.FaceletRedirectionViewHandler</view-handler>
      ...
   </application>
</faces-config>

Then override the default mechanism for dealing with errors that the facelet framework encounters:

public class FaceletRedirectionViewHandler extends FaceletViewHandler {
    ...
    @Override
    protected void handleRenderException(FacesContext context, Exception ex) throws IOException, ELException,
        FacesException {
        try {
            if (context.getViewRoot().getViewId().equals("/rhq/common/error.xhtml")) {
                /*
                 * This is to protect from infinite redirects if the error
                 * page itself is updated in the future and has an error
                 */
                log.error("Redirected back to ourselves, there must be a problem with the error.xhtml page", ex);
                return;
            }

            getSessionMap().put("GLOBAL_RENDER_ERROR", ex);
            getHttpResponseObject().sendRedirect("/rhq/common/error.xhtml");
        } catch (IOException ioe) {
            log.fatal("Could not process redirect to handle application error", ioe);
        }
    }
}

View full source here

The basic strategy is capture the exception and redirect to your new error page. However, what if there is a compile error on the error page itself? Your new error handling code would capture that error and try to handle it, which would redirect back to the custom error page which still has that error on it! This leads to an infinite redirect, which all modern browsers can and will detect, but it doesn’t provide much useful information as to what error occurred on the page.

You might be asking yourself, “How likely is it that the error page will have an error?” Well, this could happen either as you’re writing the page for the first time, or are updating the page in the future to add other features. Fortunately, there is an easy way of protecting against this. In your error handling code, simply test which JSF viewId you’re coming from and, if it’s your new error page, then revert back to the default handler by calling the superclass method being overridden. Don’t forget to explicitly break/return out of the custom handler, otherwise you’ll still see the infinite recursion.

The next and final step is to write the logic that pulls the exception back out of the session map on the other side of the redirect:

public class GenericErrorUIBean {
    String summary; // the name of the exception class, usually self-descriptive
    String details; // a little more information about the named exception
    List<Tuple> trace; // fine-grained trace details

    public GenericErrorUIBean() {
        trace = new ArrayList<Tuple>();
        Throwable ex = (Exception) getSessionMap().remove("GLOBAL_RENDER_ERROR");

        String message = ex.getLocalizedMessage();
        String stack = StringUtil.getFirstStackTrace(ex);
        trace.add(new Tuple(message, stack));
        while (ex.getCause() != null) {
            ex = ex.getCause();
            message = ex.getLocalizedMessage();
            stack = StringUtil.getFirstStackTrace(ex);
            trace.add(new Tuple(message, stack));
        }

        summary = ex.getClass().getSimpleName();
        details = ex.getMessage();
    }

    private static String getFirstStackTrace(Throwable t) {
        if (t == null) {
            return null;
        }
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        return sw.toString();
    }
}

View full source here

Depending on what you want your error page to look like, you can use a variety of methods for chopping up the stack trace into more manageable and/or user-friendly bits. In this case, the solution I used for the RHQ platform loops over the full trace one stack frame at a time. At each frame, we record the name of the exception-class and map that to the exception-stack at that frame. In this fashion, we can generate of list of tuples which can be iterated over in our error.xhtml facelet with either the ui:repeat or a4j:repeat tag.

The end result is a page that looks at professional by hiding the ugly errors and showing the root cause in small, easy to understand language.
Note, however, that the page can still be used as a debugging tool. See the “view the stack trace” link at the bottom? Clicking it opens up a modal dialog which display the full stack trace (which is useful for reporting bugs through customer support [for downloaded products], or emailing the webmaster [for hosted applications]).

For brevity, the source code of the error.xhtml facelet has been left out of this blog entry, but you can view the full source here

This is just one of the many solutions employed by the RHQ platform to provide a great web driven interface that centralizes the monitoring and management of your enterprise systems. To find out more, please visit one of the links below:

RHQ (base management platform) – http://www.rhq-project.org/
Jopr (Jboss specific extensions to RHQ) – http://www.jboss.org/jopr/

Advertisements

Written by josephmarques

April 27, 2009 at 7:29 am

Posted in webdev

Tagged with

13 Responses

Subscribe to comments with RSS.

  1. This is exactly what I needed, thanks. One issue remains, though; when deployed on a WebSphere 5.1 server, the System.out log still (also) contains the original exception:

    [11-5-09 14:30:03:271 CEST] b5285b9 WebGroup E SRVE0026E: [Servlet Error]-[Error calling action method of component with id data:_id14]: javax.faces.FacesException: Error calling action method of component with id data:_id14
    at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:72)
    at javax.faces.component.UICommand.broadcast(UICommand.java:109)
    at javax.faces.component.UIViewRoot._broadcastForPhase(UIViewRoot.java:97)
    at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:171)
    at org.apache.myfaces.lifecycle.InvokeApplicationExecutor.execute(InvokeApplicationExecutor.java:32)
    at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:95)
    at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:70)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:139)
    at com.ibm.ws.webcontainer.servlet.StrictServletInstance.doService(StrictServletInstance.java:110)
    at com.ibm.ws.webcontainer.servlet.StrictLifecycleServlet._service(StrictLifecycleServlet.java:174)
    at com.ibm.ws.webcontainer.servlet.IdleServletState.service(StrictLifecycleServlet.java:313)
    at com.ibm.ws.webcontainer.servlet.StrictLifecycleServlet.service(StrictLifecycleServlet.java:116)
    at com.ibm.ws.webcontainer.servlet.ServletInstance.service(ServletInstance.java:283)
    at com.ibm.ws.webcontainer.servlet.ValidServletReferenceState.dispatch(ValidServletReferenceState.java:42)
    at com.ibm.ws.webcontainer.servlet.ServletInstanceReference.dispatch(ServletInstanceReference.java:40)
    at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.handleWebAppDispatch(WebAppRequestDispatcher.java:1036)
    at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.dispatch(WebAppRequestDispatcher.java:544)
    at com.ibm.ws.webcontainer.webapp.WebAppRequestDispatcher.forward(WebAppRequestDispatcher.java:210)
    at com.ibm.ws.webcontainer.srt.WebAppInvoker.doForward(WebAppInvoker.java:139)
    at com.ibm.ws.webcontainer.srt.WebAppInvoker.handleInvocationHook(WebAppInvoker.java:332)
    at com.ibm.ws.webcontainer.cache.invocation.CachedInvocation.handleInvocation(CachedInvocation.java:71)
    at com.ibm.ws.webcontainer.cache.invocation.CacheableInvocationContext.invoke(CacheableInvocationContext.java:120)
    at com.ibm.ws.webcontainer.srp.ServletRequestProcessor.dispatchByURI(ServletRequestProcessor.java:258)
    at com.ibm.ws.webcontainer.oselistener.OSEListenerDispatcher.service(OSEListener.java:334)
    at com.ibm.ws.webcontainer.http.HttpConnection.handleRequest(HttpConnection.java:56)
    at com.ibm.ws.http.HttpConnection.readAndHandleRequest(HttpConnection.java(Compiled Code))
    at com.ibm.ws.http.HttpConnection.run(HttpConnection.java(Compiled Code))
    at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:937)
    Caused by: javax.faces.el.EvaluationException: /somepage.xhtml @35,137 action=”#{myBean.someActionThatThrowsAnException}”: com.acme.application.exception.MyApplicationException: the text of the application exception
    at com.sun.facelets.el.LegacyMethodBinding.invoke(LegacyMethodBinding.java:73)
    at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:61)
    … 27 more
    Caused by: com.acme.application.exception.MyApplicationException: the text of the application exception
    at com.acme.application.backingbeans.MyBean.someActionThatThrowsAnException(MyBean.java:50)
    […snip]

    I would like the FaceletRedirectionViewHandler to swallow any exceptions that it catches so that they don’t also appear in the WebSphere System.out log.

    I realize that asking this here is akin to requesting advice to getting GNU programs to run under MS Windows on Richard Stallman’s blog, but hey 🙂

    László van den Hoek

    May 11, 2009 at 1:10 pm

  2. […] As these happen at the very end of the execution stack, they need a special treatment described in this post. Just be sure to use the same Key for the Session Map so it can be pulled out on the Contact […]

  3. […] As these happen at the very end of the execution stack, they need a special treatment described in this post. Just be sure to use the same Key for the Session Map so it can be pulled out on the Contact […]

  4. Thanks great post! Spent three days struggling with javax.el.ELException

    jsfcoder

    June 3, 2010 at 12:03 pm

    • It seems that I have the same proble with javax.el.ELException.

      I use MyFaces 1.2 Implementation and Facelet 1.1.4 with maven for dependencies.

      But in myfaces 1.2 el are managed internaly in javax.faces.el package…

      jsfcoder, did you face the same issue ? Did you found any solution ?

      mattlc

      July 19, 2010 at 3:36 am

  5. Nice article, actually it’s exactly what i needed.
    unfortunately the sources links seems to be old.
    can you update it please
    thanks

    Marwan

    September 20, 2010 at 10:09 am

    • Done. Links now point to the new git repository.

      josephmarques

      September 21, 2010 at 9:26 pm

  6. Hi guys
    i added new handler class, add it to faces-config.xml but i got following error:
    java.lang.IllegalStateException: No RenderingContext
    at org.apache.myfaces.trinidad.render.CoreRenderer.encodeEnd(CoreRenderer.java:413)
    at org.apache.myfaces.trinidad.component.UIXComponentBase.encodeEnd(UIXComponentBase.java:839)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:937)
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:933)
    at com.sun.facelets.FaceletViewHandler.renderView(FaceletViewHandler.java:594)
    at org.apache.myfaces.lifecycle.RenderResponseExecutor.execute(RenderResponseExecutor.java:41)
    at org.apache.myfaces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:140)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:266)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:271)
    at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:177)
    at org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)

    any idea?

    Simo

    August 31, 2012 at 7:29 am

  7. “Custom JSF/Facelet Exception Handling Thoughts on”
    ended up being a remarkable read and thus I actually was in fact
    really pleased to locate the blog post. Thanks,Keira

  8. Thanks a lot. It was of a quick help.

    Bims

    September 5, 2013 at 9:10 am

  9. Very good blog post.Really thank you! Fantastic. bddfekedkebg

    Johnk937

    July 16, 2014 at 10:38 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: