Sunday, October 25, 2009

Recipe 9.2. Custom Processing for Declared Exceptions










Recipe 9.2. Custom Processing for Declared Exceptions




Problem



Your application requires specialized handling for certain types of
exceptions.





Solution



Extend the Struts
ExceptionHandler
with your own class such as the one
shown in Example 9-4.




Example 9-4. Extending the Struts exception handler

package com.oreilly.strutsckbk.ch09;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ExceptionHandler;
import org.apache.struts.config.ExceptionConfig;

public class CustomExceptionHandler extends ExceptionHandler {

public ActionForward execute(Exception ex, ExceptionConfig ae,
ActionMapping mapping, ActionForm formInstance,
HttpServletRequest request, HttpServletResponse response)
throws ServletException {
// TODO Add custom code here to completely control handling
return super.execute(ex, ae, mapping, formInstance, request,
response);
}
protected void logException(Exception e) {
// TODO Add custom code here for exception logging
System.out.println("Customized logException for:"+e);
super.logException(e);
}
protected void storeException(HttpServletRequest request, String
property, ActionMessage error, ActionForward forward, String scope) {
// TODO Add custom code here for storing errors
System.out.println("Customized error storing for:"+error);
super.storeException(request, property, error, forward, scope);
}
}





In your struts-config.xml file, specify your
ExceptionHandler's class name as
the value for the handler attribute on each
exception element you want to use the handler:



<exception key="error.exception" 
type="com.oreilly.strutsckbk.ch09.CustomException"
handler="com.oreilly.strutsckbk.ch09.CustomExceptionHandler"
path="/some_error_page.jsp"/>






Discussion



You might think that if you use declarative exceptions you are
"locked in" to the Struts-way of
processing exceptions. In fact, you are not bound to the Struts
exception-handling process for two oft overlooked reasons; Struts was
designed for extensibility, and Struts is open source.



When an Action throws an
Exception, the RequestProcessor
handles it in the
processException

method:



protected ActionForward processException(HttpServletRequest request,
HttpServletResponse response,
Exception exception,
ActionForm form,
ActionMapping mapping)
throws IOException, ServletException {
// Is there a defined handler for this exception?
ExceptionConfig config = mapping.findException(exception.getClass( ));
if (config == null) {
log.warn(getInternal( ).getMessage("unhandledException",
exception.getClass( )));
if (exception instanceof IOException) {
throw (IOException) exception;
} else if (exception instanceof ServletException) {
throw (ServletException) exception;
} else {
throw new ServletException(exception);
}
}

// Use the configured exception handling
try {
ExceptionHandler handler = (ExceptionHandler)
RequestUtils.applicationInstance(config.getHandler( ));
return (handler.execute(exception, config, mapping, form,
request, response));
} catch (Exception e) {
throw new ServletException(e);
}

}




First, the method searches for an exception
configurationrepresented as an
ExceptionConfig objectfor the exception
type. It searches for a local declarative exception; if none can be
found, it looks for a global declarative exception. The search
mechanism takes object inheritance into accountthat is, if
FooException extends
BarException and there is an
exception element for
BarException, then mapping.findException(
)
will use that configuration when
FooException is thrown. If a declarative exception
is found, Struts instantiates the associated
ExceptionHandler and calls the
handler's execute( ) method.



An ExceptionHandler
mimics an Action. Its execute() method accepts the same arguments as
Action.execute( ) plus additional arguments for
the Exception and
ExceptionConfig objects. You create a custom
exception handler by extending the
ExceptionHandler.



The base ExceptionHandler
defines two protected methods designed for extension. The first,

logException(), is called to log the thrown exception.
Here's the source for this method from the
ExceptionHandler class:



protected void logException(Exception e){
log.debug(messages.getMessage("exception.log"), e);
}




If you need to provide custom logging behavior, you can override this
method; however, you should investigate the Struts logging mechanism
before you start adding a lot of custom code (see Recipe 13.2).



The second protected method,
storeException(), is somewhat misnamed because it stores an
ActionMessage (ActionError in
Struts 1.1), created for the exception in the
ExceptionHandler.execute( ) method, in the
HttpServletRequest or
HttpSession. Here's the source
for this method from the ExceptionHandler class:



protected void storeException(
HttpServletRequest request,
String property,
ActionMessage error,
ActionForward forward,
String scope) {

ActionMessages errors = new ActionMessages( );
errors.add(property, error);

if ("request".equals(scope)) {
request.setAttribute(Globals.ERROR_KEY, errors);
} else {
request.getSession( ).setAttribute(Globals.ERROR_KEY, errors);
}
}




The errors are stored in the request or session based on the value of
the scope attribute for the declarative exception.
You override storeException( ) if you want to
change or augment the base behavior. For example,
let's say you wanted to store the error message in a
database:



protected void storeException(HttpServletRequest request, String property,
ActionMessage error, ActionForward forward, String scope) {
MessageResources msgRes =
MessageResources.getMessageResources("ApplicationResources");
String msg = msgRes.getMessage(error.getKey( ));
saveMessage(msg);
super.storeException(request, property, error, forward, scope);
}

private void saveMessage(String msg) {
// store error message in database ...
}




If you need to do more than customize the logging or storing of the
exception, you will need to override the
execute() method. Start by cutting and pasting the code from the
base ExceptionHandler's
execute( ) method, shown in Example 9-5, into your handler's
execute( ) method and modify as needed.




Example 9-5. Base ExceptionHandler's execute method

public ActionForward execute(
Exception ex,
ExceptionConfig ae,
ActionMapping mapping,
ActionForm formInstance,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException {

ActionForward forward = null;
ActionMessage error = null;
String property = null;

// Build the forward from the exception mapping if it exists
// or from the form input
if (ae.getPath( ) != null) {
forward = new ActionForward(ae.getPath( ));
} else {
forward = mapping.getInputForward( );
}

// Figure out the error
if (ex instanceof ModuleException) {
error = ((ModuleException) ex).getActionMessage( );
property = ((ModuleException) ex).getProperty( );
} else {
error = new ActionMessage(ae.getKey( ), ex.getMessage( ));
property = error.getKey( );
}

logException(ex);

// Store the exception
request.setAttribute(Globals.EXCEPTION_KEY, ex);
storeException(request, property, error, forward, ae.getScope( ));

return forward;

}





The execute( ) method
takes the following steps:



  1. The forward path is determined from the
    ExceptionConfig. If no path is specified, then the
    action's input
    path is used (accessed using mapping.getInputForward(
    )
    ).

  2. If the exception is a ModuleException, the
    contained ActionMessage is retrieved. Otherwise, a
    new ActionMessage is generated using the
    key from the ExceptionConfig.

  3. The exception is logged using the logException( )
    method.

  4. The exception is added as an attribute to the
    HttpServletRequest.

  5. The error is stored by calling storeException( ).

  6. The forward is returned.


The ActionMessage generation (step 2) is a common
function you might want to customize. Recipe 9.3 shows an example of this.





See Also



If you want to change the logging of exceptions, consider using the
customization features of Struts'

logging mechanism instead of a custom
exception handler. Recipe 13.2 has details on
this approach. You can find information at http://struts.apache.org/userGuide/configuration.html#config_logging.



The Struts User's Guide discusses
custom exception handlers in the
section found at http://struts.apache.org/userGuide/building_controller.html#exception_handler.



Recipe 9.1 shows you how to configure
exception handling in your struts-config.xml
file. Recipe 9.3 shows a more ambitious use
of customized exception handling.












    No comments: