Handling JSF and Facelets exceptions

October 5th, 2009 Javier Ochoa, Consultant  (email the author)

Say you need to hide internal application errors to your users in a friendly way and also notify the support staff of this exceptional condition. In this post you’ll find a way to do that in a JSF web app. The JSF flavor I’m using is Apache MyFaces 1.1 JSF implementation with the Tomahawk component which provides some extra functionality on top of JSF.

The example I created contains the following:

  • Four xhtml files: contact.xhtml, error.xhtml, raiseExceptions.xhtml, renderingException.xhtml
  • Three backing beans: ContactSupport, ErrorDisplay and ExceptionBean.
  • Two user defined exceptions: AppException and UserAccessException.
  • One Facelets-ViewHandler class, which extends and overrides “handleRenderException” method.

The goal is to catch all kinds of exceptions from the application and treat them differently depending on what kind of exception it is: application, user access, rendering, dbaccess, etc. The exception caught is analyzed and then the either a contact form is shown or an error is displayed. Let’s see the steps to accomplish this:

1. Since this is a web app we can let the web container know how to handle error pages for the exception type or error code in the deployment descriptor (web.xml):


1    <!-- Internal server error -->
2    <error-page>
3        <error-code>500</error-code>
4        <location>/error.jsf</location>
5    </error-page>
6    <!-- All exceptions -->
7    <error-page>
8        <exception-type>java.lang.Throwable</exception-type>
9        <location>/error.jsf</location>
10    </error-page>

2. The error.jsf page has a backing bean ErrorDisplay which first loads the exception from FacesContext and second processes it differently depending the exception type:

a. Load the exception:

Here the idea is to try to get the exception first from Request map and if it is not there then Session map. We’ll see why the session map is required later…

1    private static final String SERVLET_EXCEPTION_KEY = "javax.servlet.error.exception";
2    private void initializeException() {
3        FacesContext ctx = FacesContext.getCurrentInstance();
4        if (ctx.getExternalContext().getRequestMap().containsKey(
5                SERVLET_EXCEPTION_KEY)) {
6            this.exception = (Throwable) ctx.getExternalContext()
7                    .getRequestMap().remove(SERVLET_EXCEPTION_KEY);
8        } else if (ctx.getExternalContext().getSessionMap().containsKey(
9                SERVLET_EXCEPTION_KEY)) {
10            this.exception = (Throwable) ctx.getExternalContext()
11                    .getSessionMap().remove(SERVLET_EXCEPTION_KEY);
12        }
13    }

b. Process the exception:

Get the root exception from the stack using ExceptionUtils and decide whether this is an exception to show as an error or as a contact form. If the contact form is required, add the exception to the session map (line 9 in this listing).

1    Throwable rootException = ((Throwable) (ExceptionUtils
2            .getExceptions(this.exception).get(ExceptionUtils.getExceptions(
3                    this.exception).size() - 1)));
4
5    if (rootException instanceof UserAccessException) {
6        this.title = "Invalid access";
7        this.message = rootException.getMessage();
8    } else {
9        FacesContext.getCurrentInstance().getExternalContext()
10                .getSessionMap().put(SERVLET_EXCEPTION_KEY,
11                        this.exception);
12        try {
13            ((HttpServletResponse) (FacesContext.getCurrentInstance()
14                    .getExternalContext().getResponse()))
15                    .sendRedirect("contact.jsf");
16        } catch (IOException e) {
17            e.printStackTrace();
18        }
19    }

3. So far so good, now we have a way to handle the exception as an error (error.xhtml) if it is UserAccessException (line 5 in previous listing). Now let’s look at the contact.jsf which shows the form to the user. This basically will pull out from the session map the exception and show the message in the details field of this bean:

1    if (FacesContext.getCurrentInstance().getExternalContext()
2            .getSessionMap().containsKey(SERVLET_EXCEPTION_KEY)) {
3        Throwable exception = (Throwable) FacesContext.getCurrentInstance()
4                .getExternalContext().getSessionMap().get(
5                        SERVLET_EXCEPTION_KEY);
6        this.details = ((Throwable) (ExceptionUtils
7                .getExceptions(exception).get(ExceptionUtils.getExceptions(
8                exception).size() - 1))).getMessage();
9    }

4. Ok now we have a way to handle our JSF application exceptions, but what about Facelet rendering problems? 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 page.


1 context.getExternalContext().getSessionMap().put(
2 "javax.servlet.error.exception", ex);
3 ((HttpServletResponse) context.getExternalContext().getResponse())
4 .sendRedirect("contact.jsf");

Of course you’ll get the big picture by looking at the code available here

Be Sociable, Share!

Entry Filed under: Agile and Development

2 Comments Add your own

  • 1. Daniel  |  October 14th, 2010 at 12:41 pm

    Hola me parece interesante el post…

    Primeramente te felicito por lo publicado…

    Mi pregunta es la siguiente:

    Como hacer para que solo cuando existan errores que caigan en un catch aparezca otra pagina que solo me indique el error especifico para el usuario…

    Y si al hacerlo cuando regrese a la pagina que genero el error poder ingresar correctamente los datos y ya no se vuelva a ir a la pagina de error…

  • 2. Javier Ochoa  |  October 18th, 2010 at 9:36 pm

    Daniel.
    A ver si entendi la pregunta. ¿Quieres saber si con este método puedes validar formas?

    Creo que la idea de este método es capturar excepciones innesperadas, y pues datos inválidos no deberían según entiendo ser una excepción de tu aplicación, pero debe ser el flujo normal. No crees? Dime talvez no entendí bien la pregunta :)

    I will translate Q/A:

    Daniel: How is it possible to catch form input errors? And when coming back to the form, allow the user to enter the data again.

    Me: I think this method is for unexpected exceptions in your app. You might want to use a different solution for form processing and validation, which is a normal flow of your app. Did I misunderstand the question?

Leave a Comment

Required

Required, hidden


five + = 10

Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackback this post  |  Subscribe to the comments via RSS Feed

© 2010-2014 Summa All Rights Reserved