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); 
    } 
}