I would like to thank those who provided feedback for
Part I of this exception handling series.
A special thanks goes out to
Dan Allen and
Dan Hinojosa for the wonderful constructive feedback.
I'm sure over time these posts will improve and become better.
This entry was originally going to be talking about custom handlers and provide some code; however,
due to family and work factors I have not been able to finish what I wanted for this. Instead we'll be diving
even further into the built-in handlers and providing examples. At the end I'll also give a sneak peek of the
exception handling framework that will make up Part III of this series, hopefully complete in March.
To cut down on the length of this post I've linked to Seam's Fisheye install of the 2.2.0.GA release. With that, let the journey into org.jboss.seam.exception begin!
This class (
org.jboss.seam.exception.Exceptions) is a built in Seam class, all you need to do is obtain a reference to it (either via
injection
@In("exceptions") Exceptions
or
Exceptions.getInstance()
) and call the
getHandlers()
method to add your own exception handlers. Typical interaction is done by adding exception handlers via the annotations or pages.xml. Let's begin the exploration with the
initialize()
method where all the initial setup and creation of the built-in handlers happen. Handlers are added in the following order:
- AnnotationRedirectHandler
- AnnotationErrorHandler
- Handlers declared in pages.xml
- User defined custom handlers (more on this later)
At first
glance, it appears that an old deprecated way of adding exceptions is first
added, then the exception handlers added via page(s).xml; however, this is not the case as
each of those ExceptionHandlers is added to a list, then added at the end. Just before any
handlers that the user specifies with pages.xml or custom handlers the Seam Debug Page is
added.
It's important to know that while in debug (typically only development mode) your
annotated exceptions will take precedence over the Seam Debug Page, and nothing after the
debug page will be triggered (the debug page is a global catch all handler).
The parsing of the XML files happens in the parse()
method which does some basic XML parsing
looking for the exception elements. The class and logging level (if configured) are pulled
from the exception element, and a new ConfigRedirectHandler or ConfigErrorHandler is created
(more on these handlers later). If there is no class attribute specified then a catch-all
handler, that handles Exception.class is created.
The private createHandler()
method does the work of pulling additional information about the configured
handler such as
- ending conversations
- redirecting
- adding FacesMessages
- finding the http error code, etc.
The correct handler is then created and returned to be added into the list.
The only other part of this class that has any importance for the developer/user is the
handle()
method, which is really just an iterator over the handlers looking for a handler for
the exception which was thrown. Though there is a catch: Nested exceptions are unwrapped and
handled from the bottom up (though in the Seam Debug Page the exception is recreated so you
may need to scroll to the bottom to actually find the real exception). If a long running
conversation is active then the "org.jboss.seam.handledException"
object, which is the
exception object, is added to the conversation context. If logging is configured for the
exception handler a log message is added then two events are raised (regardless of logging):
"org.jboss.seam.exceptionHandled." + cause.getClass().getName()
and
org.jboss.seam.exceptionHandled
allowing a further user extension point if so desired. If
no handler is found then an event is raised ("org.jboss.seam.exceptionNotHandled"
) and
the exception is rethrown. Even if this event is observed, (the exception is passed as
a parameter to the observer's method) it will not be able to influence the exception being rethrown.
Any handlers the developer creates and adds to the Exceptions list will be added at the end of
the list (unless the list is manually modified and rearranged, which is allowed).
Depending on the specific
needs of the application this is a great place to add a general catch all handler for the
application (unless a general handler in pages.xml is sufficient). When adding custom
handlers or any handler really, it is important not to create multiple handlers for the same
exception as the first in the list will be called and the list of handlers will terminate
with that handler.
ErrorHandlers (
AnnotationErrorHandler and
ConfigErrorHandler) are a specific type of ExceptionHandler, they're used to send an HTTP Error
code to the browser, probably more useful when combined with Seam's Web Service or REST integration.
There is not a lot of code to these handlers, in fact, they are only data containers used by the
parent class
ErrorHandler which handles sending the error to the browser. However, they are
great examples of reuse for exception handling, and make for good templates to follow in abstracting
logic in custom handlers. The two sub classes hold
- the message (if configured)
- the error code number
- and the flag to end the currently conversation.
All of the logic to make it happen is in the
handle()
method.
The redirect handlers (
AnnotationRedirectHandler and
ConfigRedirectHandler) are in the same category as the error handlers: there's not much to
them, and most of the interesting code is in the parent class
RedirectHandler. The
first bit of code in the
handle()
method is finding the viewId if none was specified
in the handler instance, but it really isn't related to the actual exception handling. Next is the addition of a
defined FacesMessage, ending the conversation (again, if configured), and finally
the redirect.
This is a great handler (
source) along with the associated code in the jboss-seam-debug.jar to fully
understand the potential of exception handlers in Seam. The handler is really
the entry point into the debug system in Seam. Java code wise, there's not much
to it, it's a redirect handler that sets some additional parameters for the redirect.
The whole process of Seam Debug really could be it's own blog entry or series of entries.
It's pretty involved: it includes a PhaseListener, a SerializationProvider, and some
custom Introspection. Because it's such a large, involved piece of code, I'll cover it more
fully in a future post, or perhaps a whole series.
Examples
Annotations
Consistent with the rest of the framework, Seam provides a few Annotations to help with
exception handling, these were briefly explored in
Part I. Here are some examples on
how they're applied to application code.
@ApplicationException(rollback=true)
@Redirect(message="This item is out of stock")
public class OutOfStockException extends Exception {
...
}
The
@ApplicationException
annotation is a mirror of
javax.ejb.ApplicationException
with additional support for ending a conversation. It is also the entry point Seam uses to configure annotation based exception handlers and must be used if the
@HttpError
or
@Redirect
annotations are to be used. In this particular example some of the
defaults are used such as not ending the conversation (with the end attribute) and there
is no redirect happening, but the message (which can also take an EL expression for i18n,
or substitution purposes) will be displayed on the page where the exception occurred.
This next example might be useful with Seam's WebService support, it will return an
HTTP error code 500 (probably not the most useful for the clients, but it's an example, right?).
@ApplicationException(rollback=true)
@HttpError(code=501)
public class OutOfStockWsException extends Exception {
...
}
As noted earlier, these two exception handlers (AnnotationRedirectHandler and
AnnotationErrorHandler) are the first two handlers in the list and will be the first called
to handle the exception, if they are capable, as defined by
boolean isHandler(Exception)
.
The classes use the meta data from the annotations to determine
if the exception can be handled by one of these two classes. Next up is a closer look at
defining the same exception handlers in pages.xml.
Exception handlers can only
be defined in the pages.xml, not in the individual page.xml files,
which in my opinion is a bit of a short coming. There could be times when the same exception should
be handled differently for different pages.
Pages.xml
Because the handlers for pages.xml are roughly the same handlers, different sub classes but they do the same
as the annotation sub classes, the above handlers could be declared via pages.xml with
the following XML:
<exception class="OutOfStockException">
<redirect>
<message>This item is out of stock
</redirect>
</exception>
<exception class="OutofStockWsException">
<http-error code="501" />
</exception>
Pretty simple stuff. These exceptions will follow the annotations in the list (it's worth
noting that each exception that is declared in the XML creates a new instance of the associated
handler, unlike the annotation counterpart). If both were declared in the same application
the annotation ones would be used. Both ways get the job done, and for your own exceptions
it's really just a matter of preference.
The pages.xml declaration really lends itself better
for exceptions that cannot be annotated, such as form Hibernate or JPA, please see Part I for examples.
Part III of this series will demonstrate an exception handler framework that could be plugged into
Seam and will allow chaining for handling (for example send an email or IRC message and create a
bug report for the exception). It should be generic enough to work with Seam, JSF2
(Ed Burns talked about JSF2 Exception handling on his blog), or even the base JRE (yep, 1.5 added Exception handling). It will be hosted on GitHub.