Thursday, September 8, 2011

JSF 2 - Global Exception Handling

Build-in exception handling in JEE 6

As I described in a former article we replaced JEE 5 / Seam 2 with JEE 6. One of the features we really liked about Seam 2 was the global exception handling. You could declare exception handling rules in the global pages.xml such as logging, navigation, actions etc. Some might argue that the Servlet Container already allowed this in the web.xml:

<error-page>
 <error-code>404</error-code>
 <location>/404.xhtml</location>
</error-page>
...
<error-page>
 <exception-type>javax.faces.application.ViewExpiredException</exception-type>
 <location>/error.xhtml</location>
</error-page>

But this is really a poor replacement for the Seam features. First of all this JSF/Facelets-based exception page doesn't work because of a bug (with Weld on current JBoss AS 7):
ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host]] (http--127.0.0.1-8082-1) Exception Processing ErrorPage[exceptionType=javax.faces.application.ViewExpiredException, location=/error.xhtml]: javax.servlet.ServletException: Context is already active
 at javax.faces.webapp.FacesServlet.service(FacesServlet.java:606) [jboss-jsf-api_2.1_spec-2.0.0.Beta1.jar:2.0.0.Beta1]
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:329) [jbossweb-7.0.1.Final.jar:7.1.0.Alpha1-SNAPSHOT]
...
Caused by: java.lang.IllegalStateException: Context is already active
 at org.jboss.weld.context.AbstractConversationContext.activate(AbstractConversationContext.java:311) [weld-core-1.1.2.Final.jar:2011-07-26 15:02]
 at org.jboss.weld.jsf.WeldPhaseListener.activateConversations(WeldPhaseListener.java:114) [weld-core-1.1.2.Final.jar:2011-07-26 15:02]
So you need to use a static HTML, Servlet or JSP for this. I really don't have a problem with that because in this case you will not be tempted to include too much in your global exception handling page. Why this? Facelets make it really easy to reuse global templates. Think about a navigation tree that is configurable and stored in a database. Than you get database exceptions and redirect to an error page that uses a global layout template with navigation...nasty things could happen here. Best practice: Keep exception handling pages simple.

But there are further problems. The given <exception-type> doesn't catch super types, it needs to be an exact match (think about things like a myriad of O/R mapper exceptions with a super exception). And by far the worst problem: You can do nothing about the logging of this catched exceptions via this configuration. In Seam you could define if such exceptions should be logged at all and on which severity level. I don't know about your customers or operation team but for us such log entries are a no-go:
ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/ifos].[Faces Servlet]] (http--127.0.0.1-8082-1) Servlet.service() for servlet Faces Servlet threw exception: ...controller.nutzer.AuthorizationException: Permission 'PERSON_LESEN_NUTZERDATEN' not granted!
 at ...controller.nutzer.Principal.check(Principal.java:46) [classes:]
...
ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/ifos].[Faces Servlet]] (http--127.0.0.1-8082-1) Servlet.service() for servlet Faces Servlet threw exception: javax.faces.application.ViewExpiredException: viewId:/Veranstaltungstypen/filter.xhtml - Ansicht /Veranstaltungstypen/filter.xhtml konnte nicht wiederhergestellt werden.
 at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:205) [jsf-impl-2.1.3-b02-jbossorg-2.jar:2.1.3-SNAPSHOT]
...
ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/ifos].[Faces Servlet]] (http--127.0.0.1-8082-2) Servlet.service() for servlet Faces Servlet threw exception: com.sun.faces.context.FacesFileNotFoundException: /Veranstaltungstypen/filtr.xhtml Not Found in ExternalContext as a Resource
 at com.sun.faces.facelets.impl.DefaultFaceletFactory.resolveURL(DefaultFaceletFactory.java:232) [jsf-impl-2.1.3-b02-jbossorg-2.jar:2.1.3-SNAPSHOT]
The log is triggered (JSF 2.1.3) via ExceptionHandlerImpl.handle() -> log(context) -> LOGGER.log(Level.SEVERE, ... with Logger name "context" which is used throughout JSF for all kind of things. Grossly said this <exception-type> feature is outright unusable in the current form.

Wait for JEE 10? Nope...

I really don't know why the standard hasn't added a simple extension like a log severity attribute in the last years. Sometimes I really wonder about some decisions in regards to production requirements, e.g. why is the new JSF resources folder in the web application root path and not under WEB-INF (where the Servlet Container doesn't allow access by default)? The hackers say thank you.

At least in JEE 6 there has been added a somewhat hidden feature - a custom exception handler class.

Remove the <exception-type> garbage from your web.xml. Add this to your faces-config.xml:
<faces-config>
 ...
 <factory>
  <exception-handler-factory>...controller.util.exception.ExceptionHandlerFactory</exception-handler-factory>
 </factory>
</faces-config>
This references the factory class for a custom exception handler:
public class ExceptionHandlerFactory extends javax.faces.context.ExceptionHandlerFactory {

 private final javax.faces.context.ExceptionHandlerFactory parent;

 public ExceptionHandlerFactory(final javax.faces.context.ExceptionHandlerFactory parent) {
  this.parent = parent;
 }

 @Override
 public ExceptionHandler getExceptionHandler() {
  return new ExceptionHandler(this.parent.getExceptionHandler());
 }

}
And finally our exception handler:
public class ExceptionHandler extends ExceptionHandlerWrapper {

 private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandler.class);

 private final javax.faces.context.ExceptionHandler wrapped;

 public ExceptionHandler(final javax.faces.context.ExceptionHandler wrapped) {
  this.wrapped = wrapped;
 }

 @Override
 public javax.faces.context.ExceptionHandler getWrapped() {
  return this.wrapped;
 }

 @Override
 public void handle() throws FacesException {
  for (final Iterator<ExceptionQueuedEvent> it = getUnhandledExceptionQueuedEvents().iterator(); it.hasNext();) {
   Throwable t = it.next().getContext().getException();
   while ((t instanceof FacesException || t instanceof EJBException || t instanceof ELException)
     && t.getCause() != null) {
    t = t.getCause();
   }
   if (t instanceof FileNotFoundException || t instanceof HandledException
     || t instanceof ViewExpiredException) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = facesContext.getExternalContext();
    final Map<String, Object> requestMap = externalContext.getRequestMap();
    try {
     LOG.info("{}: {}", t.getClass().getSimpleName(), t.getMessage());
     String message;
     if (t instanceof ViewExpiredException) {
      final String viewId = ((ViewExpiredException) t).getViewId();
      message = "View is expired. <a href='/ifos"   viewId   "'>Back</a>";
     } else {
      message = t.getMessage(); // beware, don't leak internal info!
     }
     requestMap.put("errorMsg", message);
     try {
      externalContext.dispatch("/error.jsp");
     } catch (final IOException e) {
      LOG.error("Error view '/error.jsp' unknown!", e);
     }
     facesContext.responseComplete();
    } finally {
     it.remove();
    }
   }
  }
  getWrapped().handle();
 }

}
And a (simple example) error.jsp:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Error</title>
</head>
<body>
 <h1>Error</h1>
 <p><%= request.getAttribute("errorMsg") %></p>
</body>
</html>

This is a heavily reduced example with only some example exceptions. HandledException is our abstract super exception for custom service exceptions. You can freely decide what you do for which exception type:
  • dispatch to different views in dependance from exception (what we do)
  • provide different messages
  • specialized logging
  • etc. - all is possible

Now we see such infos in the log file:
INFO  [...controller.util.exception.ExceptionHandler] (http--127.0.0.1-8082-2) AuthorizationException: Permission 'PERSON_LESEN_NUTZERDATEN' not granted!
...
INFO  [...controller.util.exception.ExceptionHandler] (http--127.0.0.1-8082-2) ViewExpiredException: viewId:/Veranstaltungstypen/filter.xhtml - Ansicht /Veranstaltungstypen/filter.xhtml konnte nicht wiederhergestellt werden.
...
INFO  [...controller.util.exception.ExceptionHandler] (http--127.0.0.1-8082-2) FacesFileNotFoundException: /Personen/ter.xhtml Not Found in ExternalContext as a Resource

You might say this all looks a little bit shirtsleeved - no annotations or abstractions etc. The Seam 3 Catch module does exactly that. But we like the freedom of the programmable custom exception handler even if the API is a little bit strange. Currently we are heavily into our "Back to the Roots" trip and don't like to add too many further libraries for such simple things. Another reason: Even the advanced Seam 2 exception features where sometimes not enough for us. If you provide an abstraction then you also give up flexibility. I think the ExceptionHandler code is simple enough and doesn't change often.

29 comments:

  1. great Article for java JSF 2.0 Enthusiasts like us

    ReplyDelete
  2. So useful have been looking for this a long time ago.

    ReplyDelete
  3. Great article!

    I just want to point out that the Seam 3 Faces module provides precisely this ExceptionHandlerWrapper code for you, to offer easy integration with Seam Catch. Seam Catch does a lot more than simply provide another layer of abstraction - it's definitely worth taking a closer look!

    ReplyDelete
  4. I have to concur with Brian. Seam Catch would reduce your error handling to a couple of methods, each only being one or two lines long. You also get the added bonus of CDI injections, exception unwrapping (which you're already doing a small amount of, but Catch unwraps the whole exception, including SQLExceptions), all of your exception logic in one place, easy extension, the list goes on.

    Seam Catch also works fine in Tomcat, or Jetty (provided you have CDI properly setup, which isn't difficult).

    http://seamframework.org/Seam3/CatchModule

    ReplyDelete
  5. Thx for your comments. I could swear I have written something to Seam 3 Catch module in the last section ;)

    I really have great respect for the JBoss Seam guys - but don't get me wrong, Seam 3 is not Seam 2. It's a new product and is on our tech observation list and not must-have list. The maturity and innovation level is not quite there yet.

    The exception handler code is small enough (2 classes and templates) and we can even do things like trigger events on error message patterns (something we missed in Seam 2 catches, not all exception types in sub-frameworks are a good choice) or we can heavily influence what happens without any restrictions.

    What I don't like about Seam 3:
    - too many dependencies...I include Seam Security and need even for simplest login scenarios full picket-link and a bunch of sibling Seam modules, under them:
    - I never understood this Seam persistence/transaction etc. stuff - Seam is a JEE framework, stick to this - let the POJO stuff to the Spring guys, a lot of people are really confused (Seam 2 too) because the docs and examples switch back and force between JEE and POJO style

    But if you are happy with Seam Catch module - it's innovative and nice! Really everyone should give it a try.

    Best regards,
    André

    ReplyDelete
  6. Thank you very much for this professional tutorial

    ReplyDelete
    Replies
    1. I want to throw a custom exception in my controller (Like ApplicationException for example) and catch it in the CustomExceptionHandler, but when I reach the CustomExceptionHandler I find it as a FacesException not the custom I throw in my managed bean.
      why is that?

      Delete
    2. Hi,

      if you throw an exception in a method which is invoked through an expression language term in your template (like a controller prop/method via #{controller.prop} or #{controller.doAction()}) then this exception is wrapped in an ExpressionException or FacesException, see for example in ActionListenerImpl.processAction().

      Because of this you find these lines in my example:
      ###
      while ((t instanceof FacesException || t instanceof EJBException || t instanceof ELException)
      && t.getCause() != null) {
      t = t.getCause();
      }
      ###

      Best regards,
      André

      Delete
  7. Hi... thanks for the tutorial... I'm unable to get the header file for HandledException. I'm using JSF2.0 with Glassfish3.1 and getting Http 1.1 500 Internal Server error...when testing my application with webappsec tool of IBM. Please help

    ReplyDelete
    Replies
    1. HandledException is just a special Exception Wrapper used for our internal application exceptions, something like this:

      package de.init.bakoev.ifos.app.util.exceptions;

      /**
      * Handled Exception.
      *
      * @author André Pankraz
      */
      public class HandledException extends RuntimeException {

      private static final long serialVersionUID = 1L;

      /**
      * Constructor.
      *
      * @param message
      * message
      */
      public HandledException(final String message) {
      super(message);
      }

      /**
      * Constructor.
      *
      * @param message
      * message
      * @param cause
      * cause
      */
      public HandledException(final String message, final Throwable cause) {
      super(message, cause);
      }

      }

      Delete
    2. This is not working. Note that the HandledException is converted to FacesException and hence not going into the proper instanceof if condn... Is there a way to identify HandledException in the handle method?

      Delete
  8. Hi Andre, I noticed that for I guess every request the handle() method is called, shouldn't it be called only when we have an exception? Any way to avoid it?

    ReplyDelete
    Replies
    1. At least for JBoss AS 7.2 SNAPSHOT and Mojarra JSF you are right.

      At the end of doPhase() for Render/Update etc. context.getExceptionHandler().handle(); is allways called.

      getUnhandledExceptionQueuedEvents() is empty and nothing happens, so this is not too much of a problem.


      So far i see no objects are created (besides 1 iterator, even Javas meager Escape Analysis will ditch that) and the methods will be JIT-inlined: so this may seem suspicious out of a performance view, but it really isn't this much of a problem.

      Delete
    2. Hi Andre, I'm having a NullPointerException..
      The 'ViewExpiredException' is being caught by the handle() method but I'm getting a 'NullPointerException' after that in a Seam package and then there is no redirection.
      Here is part of the stack trace:

      10:56:11,388 INFO [STDOUT] getWrapped org.jboss.seam.faces.exception.CatchExceptionHandler@496cff2a
      10:56:18,080 INFO [STDOUT] View Expired Exception! viewId:/pages/monthlyPaymentCalculator.jsf - View /pages/monthlyPaymentCalculator.jsf could not be restored.
      10:56:30,951 INFO [STDOUT] getWrapped org.jboss.seam.faces.exception.CatchExceptionHandler@496cff2a
      10:56:30,951 ERROR [STDERR] java.lang.NullPointerException
      10:56:30,951 ERROR [STDERR] at org.jboss.seam.faces.security.SecurityPhaseListener.performObservation(SecurityPhaseListener.java:152)
      10:56:30,951 ERROR [STDERR] at org.jboss.seam.faces.security.SecurityPhaseListener.observeRestoreView(SecurityPhaseListener.java:91)
      10:56:30,951 ERROR [STDERR] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      10:56:30,951 ERROR [STDERR] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      10:56:30,951 ERROR [STDERR] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      10:56:30,951 ERROR [STDERR] at java.lang.reflect.Method.invoke(Method.java:597)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.util.reflection.SecureReflections$13.work(SecureReflections.java:305)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.util.reflection.SecureReflectionAccess.run(SecureReflectionAccess.java:54)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.util.reflection.SecureReflectionAccess.runAsInvocation(SecureReflectionAccess.java:163)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.util.reflection.SecureReflections.invoke(SecureReflections.java:299)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.introspector.jlr.WeldMethodImpl.invokeOnInstance(WeldMethodImpl.java:188)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.introspector.ForwardingWeldMethod.invokeOnInstance(ForwardingWeldMethod.java:59)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.injection.MethodInjectionPoint.invokeOnInstanceWithSpecialValue(MethodInjectionPoint.java:198)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:282)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:265)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.event.ObserverMethodImpl.notify(ObserverMethodImpl.java:234)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.manager.BeanManagerImpl.notifyObservers(BeanManagerImpl.java:635)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.manager.BeanManagerImpl.fireEvent(BeanManagerImpl.java:622)
      10:56:30,951 ERROR [STDERR] at org.jboss.weld.manager.BeanManagerImpl.fireEvent(BeanManagerImpl.java:616)
      10:56:30,951 ERROR [STDERR] at org.jboss.seam.faces.event.PhaseEventBridge.handlePhase(PhaseEventBridge.java:95)
      10:56:30,951 ERROR [STDERR] at org.jboss.seam.faces.event.PhaseEventBridge.afterPhase(PhaseEventBridge.java:99)
      ...

      The NPE is coming from org.jboss.seam.faces.security.SecurityPhaseListener, viewRoot object is null, in the method:

      private void performObservation(PhaseEvent event, PhaseIdType phaseIdType) {
      UIViewRoot viewRoot = (UIViewRoot) event.getFacesContext().getViewRoot();
      List restrictionsForPhase = getRestrictionsForPhase(phaseIdType, viewRoot.getViewId());
      if (restrictionsForPhase != null) {
      log.debugf("Enforcing on phase %s", phaseIdType);
      enforce(event.getFacesContext(), viewRoot, restrictionsForPhase);
      }
      }

      I am using Seam 3.1.0.Final, JSF 2.0, JBoss 6.1.0.Final, Mojarra-2.0.9

      I suspect because of the security configurations the redirect is being checked that's why the SecurityPhaseListener is being called... is that right? If so, any ideas on how to avoid it?

      Delete
    3. I forgot to mention, we are using pretty faces 3.3.3

      Delete
    4. I just noticed that when I click in a 'a4j' link of an expired view that's when it doesn't work as I mentioned above but if I click in a 'h:commandLink' then the redirection works fine. Just wondering why now...

      Delete
    5. Hi,

      well I cannot give support here for Seam 3.1 - I wouldn't use it, this framework is dead. Seam 2 was great, but there is simply no reason for Seam 3: CDI, JSF improvements etc. are just too good and Seam has lost it's focus.

      You use Seams CatchExceptionHandler that we replace in this article with a more direct & flexible approach.

      For once this Seam code is wrong, it has to check if viewRoot is null - it can be after all.


      But I guess your problem is another one...a4j is an Ajax-based link and will replace parts of your page. So clicking the link will call onClick-javascript code which uses XmlRequestObject to get the provided link-HTML and insert this result into the page.

      The redirect cannot work here, because the XmlrequestObject-response is not a normal Browser-request where such HTTP Header info like redirects are really observed.

      It would be interesting what to do in such cases, we really don't use a4j links and I have not a solution right now. Normally you would have to transfer the Exceltion to tje a4j component and it would have to respond with an according error dialog. I don't use Pretty Faces - may be you should ask stackoverflow, I could look into this, but I have sooooo much things to do. If I encounter this at my daily work...then... ;)

      Delete
    6. Hi Andre, I cannot do much about using Seam in this project, but thanks a lot for your help, it actually saved my day!
      About my problem what I will try to do is to at least post an error message on the screen saying the session is expired and ask the user to click on 'Logout'(h:commandLink)link so it can redirect the user to login page. That will do it..
      Cheers mate, have a good day!

      Delete
    7. Nice I could help - you provided good info for this case.
      RichFaces and other Ajax-based JSF component libraries have special hooks for typical JSF request exceptions like session timeouts, invalid views etc.
      Check the component frameworks documentation for this stuff, this must be handled in a different way than descibed here and is provided by the Ajax components.

      Cheers André

      Delete
    8. hi im using jsf2 and jboss and im looking to redirect the page and i was using this externalContext.dispatch("/error.xhtml"); it was working fine earlier and now its working so i tried with externalContext.dispatch("/error.jsp"); its working if anyone know please let me know it will be very hellp full

      Thanks in advance

      Delete
  9. Could you tell me Why are you using JSP instead of JSF XHTL pages ?
    I think there could be a bug in Seam JSF.

    Can't we do global exception handling with JSF2.1, Primefaces in a better way.

    ReplyDelete
    Replies
    1. Hi,

      Reply might be to late, sry ;)

      Yes I use a JSP instead of Faclet here, because like described above, JBoss 7.0.0 had a bug in this case (Context not active).
      This bug might be gone by now, just try it, it's simple to change and doesn't really influence the other parts.

      I don't know if PrimeFaces has a different out of the box solution for this like Seam had, what I'm describing here is really the JSF standard way, at least for JSF 2.0 and 2.1 didn't change that.

      Other frameworks on top of JSF, for instance Oracle ADF have also additional methods for this (like Seam), e.g. for ADF the Task Flow Exception Handler or the Global Exception Handler. It depends...

      Cheers,
      André

      Delete
  10. 'why is the new JSF resources folder in the web application root path and not under WEB-INF' It really makes sense for JSF resources to be in that path. Why? Because this folder contains images, javascript code and css stylesheets, everything needed to be downloaded by the client. Should it be in WEB-INF folder where Server side files are located?

    ReplyDelete
    Replies
    1. Hi,

      the ressources in this folder are not delivered directly by the implicit web ressource servlet, they are delivered by a special JSF resource servlet, which has to resolve languages, production, version etc. to the proper folder path. So this ressources can and should be in WEB-INF, to prevent unintended access via the default ressource servlet.

      Cheers, André

      Delete
  11. This is not working. Note that the HandledException is converted to FacesException and hence not going into the proper instanceof if condn... Is there a way to identify HandledException in the handle method?

    ReplyDelete
    Replies
    1. Same Anonymous here.. I can find the answer in your comments.. Never mind Andre.. Thanks for the useful resource

      Delete
  12. I noticed, that JSF logs RuntimeExceptions on level WARN _before_ it gets to the ExceptionHandler (see class InvokeApplicationPhase). Do you know if there is way to supress this? I would like to log the exception myself if appropriate. Thank you!

    ReplyDelete