Exception Handling
Introduction to Exception Handling
Web Applications can generate exceptions and unfortunately the Servlet specification's built-in exception handling mechanism is somewhat limited. Specifically it can only forward the user to an alternative JSP or file; it cannot forward to a servlet or cause non-JSP code to be executed. This is what the Stripes exception handling mechanism aims to solve.
Most of the major interfaces in Stripes allow developers to throw any exceptions they don't want to handle. Handler methods, validation methods, etc. call all throw Throwable! It's up to you to define your exception handling strategy: Stripes will not impose one on you. That said, it's the author's opinion that you should generally propagate (not catch, wrap and throw) any exception that you can't handle, and let the framework take care of it.
The starting point is the ExceptionHandler interface. Implementations of this interface are used to handle any exceptions that arise during a request. They may rethrow exceptions, or execute arbitrary code and return a Resolution to tell Stripes what to do next.
The ExceptionHandler is invoked from the Stripes Filter. This allows it to handle exceptions generated in ActionBeans, in JSPs and even in other Servlet Filters that are lower down the chain than the Stripes Filter.
Default Exception Handler
The DefaultExceptionHandler used by Stripes doesn't do much! If the exception being handled is a ServletException
it is rethrown, otherwise it is wrapped in a StripesServletException
and rethrown. That's it!
However, by extending DefaultExceptionHandler
, you can easily handle different types of exceptions in different ways. All you have to do is add methods, arbitrarily named, that accept exactly 3 parameters: the exception type, the HTTP request, and the HTTP response. Furthermore, if the method returns a Resolution
, it will be executed. For example:
public class MyExceptionHandler extends DefaultExceptionHandler { public Resolution handleDatabaseException(SQLException exc, HttpServletRequest request, HttpServletResponse response) { // do something to handle SQL exceptions return new ForwardResolution(...); } public Resolution handleGeneric(Exception exc, HttpServletRequest request, HttpServletResponse response) { // general exception handling return new ForwardResolution(...); } }
When an exception is handled the DefaultExceptionHandler
looks for the most specific method to handle the exception. If it cannot find one that handles the exact exception type thrown, it will look for a method that handles the parent type and so on up the hierarchy. If no handler can be found, then the exception is rethrown. For this reason it is recommended to always have a handler method that takes Exception
.
Delegating Exception Handler
The DelegatingExceptionHandler works similarly to the DefaultExceptionHandler
described above, but lets you write more than one class that handles exceptions. Each of those classes must implement the AutoExceptionHandler marker interface.
AutoExceptionHandlers
can have one or more methods with the same signature as described earlier:
public Resolution someMethod(Exception e, HttpServletRequest req, HttpServletResponse res); public Resolution someMethod(NullPointerException npe, HttpServletRequest req, HttpServletResponse res);
When it is initialized the DelegatingExceptionHandler
scans the classpath to find all instances of AutoExceptionHandler
. These will be found either in the packages configured in the DelegatingExceptionHandler.Packages
Stripes Filter init-param, or the more general Extension.Packages
.
Writing Your Own Exception Handler
Writing your own exception handler is quite straightforward. The class simply has to implement ExceptionHandler
interface, which specifies two methods (including the inherited one). For example:
public class MyExceptionHandler implements ExceptionHandler { /** Doesn't have to do anything... */ public void init(Configuration configuration) throws Exception { } /** Do something a bit more complicated that just going to a view. */ public void handle(Throwable throwable, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { TransactionUtil.rollback(); // rollback any tx in progress if (AppProperties.isDevMode()) { throw new StripesServletException(throwable); } else { request.setAttribute("exception", throwable); request.getRequestDispatcher("/error.jsp").forward(request, response); } } }
Even easier is to extend DefaultExceptionHandler
and add exception-handling methods as described above.
Configuring an Alternative Exception Handler
Having created your own exception handler, you'll need to configure it. You can do this either by placing your class in a package that's configured as an extensions package (see Extensions), or by using the ExceptionHandler.Class
parameter, e.g.
<init-param> <param-name>ExceptionHandler.Class</param-name> <param-value>com.myco.exception.MyExceptionHandler</param-value> </init-param>
Accessing the ActionBean, ActionBeanContext etc.
The interface of ExceptionHandler
is quite basic:
public void handle(Throwable throwable, HttpServletRequest request, HttpServletResponse response) throws ServletException;
This is primarily because the exception handler can handle exceptions from anywhere in a Stripes application - even prior to things like the ActionBean
and ActionBeanContext
having been created! The interface of AutoExceptionHandler
is similar. This may leave you wondering how to get access to the ActionBean
etc. should one be present. For example you may know, based on the type of exception being handled, that it had to originate from an ActionBean
. Or you might simply wish to handle things differently if an ActionBean
is present.
Checking for and retrieving an ActionBean
is relatively simple, and through it the ActionBeanContext
, ValidationErrors
etc. For example:
/** * If there's an ActionBean present, send the user back where they came from with * a stern warning, otherwise send them to the global error page. */ public void handle(Throwable throwable, HttpServletRequest request, HttpServletResponse response) throws ServletException { ActionBean bean = (ActionBean) request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN); if (bean != null) { bean.getContext().getValidationErrors().addGlobalError(new SimpleError("You made something blow up! Bad user!")); bean.getContext.getSourcePageResolution().execute(request, response); } else { request.getRequestDispatcher("/error.jsp").forward(request, response); } }