Chapter 9. Exception and Error Handling

Introduction

Any application can shine under normal conditions; how an application responds to unexpected conditions reveals much more about its robustness and production-readiness. As you saw in Chapter 8, the Struts Validator provides a means for handling unexpected user input. However, applications need to handle unexpected system behavior. This behavior typically manifests as exceptions. These exceptions may be business-related, may have meaning to the end user, or they may be system- and coding-related, or may have meaning to system administrators and developers. This chapter will show you some good solutions for handling both of these cases.

9.1. Simplifying Exception Processing in an Action

Problem

You want to reduce the number of try . . . catch blocks within your Action classes.

Solution

Remove the exception-handling code from your Action, and define global and local exception handlers in your struts-config.xml file, as shown in Example 9-1.

Example 9-1. Global and local exception handling (partial)
...
<global-exceptions>
    <exception key="error.unknown.user" 
              type="com.oreilly.strutsckbk.ch09.UnknownUserException"
              path="/securityError.jsp"/>    
</global-exceptions>
...
<action-mappings>
    <action    path="/Login"
               type="com.oreilly.strutsckbk.ch09.LoginAction"
              scope="request"
               name="LoginForm"
            validate="true"
              input="/login.jsp">
        <exception key="error.password.match" 
                  type="com.oreilly.strutsckbk.ch09.PasswordMatchException">
        <forward name="success" path="/login_success.jsp"/>
    </action>
...

Discussion

Prior to Struts 1.1, the handling of exceptions was left to the devloper’s devices. Exceptions returned from calls to the business layer from an Action had to be handled individually in your code. Because the perform( ) method of Struts 1.0 only allowed you to throw IOException and ServletException, you didn’t have much choice in the matter:

public ActionForward perform( ActionMapping mapping, 
                              ActionForm form,
                              HttpServletRequest request, 
                              HttpServletResponse response)
    throws IOException, ServletException { ...

Any checked exception thrown within the body of perform() had to be caught and handled. In some cases, it was appropriate to catch the exception, generate an ActionError, and forward to the input page, much like a validation failure. But more often, the exception couldn’t be handled by the application, so the developer returned a ServletException wrapped around the application exception. With Struts 1.1, the perform() method was deprecated and the execute( ) method was introduced. Unlike perform(), execute( ) can throw any exception, and once an exception is thrown, you can process using exception handlers configured in your struts-config.xml file.

If you migrated an Action class from Struts 1.0 to Struts without using declarative exception handling, the class might look something like Example 9-2.

Example 9-2. Action without declarative exception handling
package com.oreilly.strutsckbk.ch09;

import java.io.IOException;

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

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class LoginAction extends Action {
    public ActionForward execute(ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response)
             throws Exception {

        String username = null;
        String password = null;
        
        ActionErrors errors = new ActionErrors( );

        try {
            username = (String) PropertyUtils.getSimpleProperty(form, 
                                                         "username");
            password = (String) PropertyUtils.getSimpleProperty(form, 
                                                         "password");
        } catch (Exception e) {
            throw new IOException("Unable to retrieve username and 
                                                          password");
        }
        
        SecurityService service = new SecurityService( );
        try {
            service.authenticate( username, password);
        } catch (UnknownUserException e1) {
            errors.add(ActionErrors.GLOBAL_ERROR,
                new ActionError("error.unknown.user"));
            saveErrors(request, errors);
            return mapping.findForward("securityError");
        } catch (PasswordMatchException e) {
            errors.add(ActionErrors.GLOBAL_ERROR, 
            new ActionError("error.password.match"));
        }        

        // Report any errors we have discovered back to the original form
        if (!errors.isEmpty( )) {
            saveErrors(request, errors);
            return (mapping.getInputForward( ));
        }
        
        User user = new User( );
        user.setUsername(username);
        request.getSession( ).setAttribute("user", user);

        return mapping.findForward("success");
    }
}

There’s a lot of code here whose sole purpose is handling exceptions. The first try . . . catch block handles technical exceptions thrown in getting data from the form. The more interesting application exceptions are thrown by the SecurityService. The UnknownUserException is handled by generating an error, saving the errors in the request, and forwarding to a Struts forward. The PasswordMatchException is handled similarly, except that the page is forwarded to the input path for the action, where the error will probably be reported and the user can try again.

All of this exception-handling code can be eliminated by using declarative exception handling. Example 9-3 shows the LoginAction with the exception handling removed.

Example 9-3. LoginAction without exception handling
package com.oreilly.strutsckbk.ch09;

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

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class LoginAction extends Action {

    public ActionForward execute(ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response)
             throws Exception {

        String username = (String) 
             PropertyUtils.getSimpleProperty(form, "username");
        String password = (String) 
             PropertyUtils.getSimpleProperty(form, "password");
        
        SecurityService service = new SecurityService( );
        service.authenticate( username, password);

        User user = new User( );
        user.setUsername(username);
        request.getSession( ).setAttribute("user", user);

        return mapping.findForward("success");
    }
}

The exceptions are now handled declaratively, as shown in the Solution. The UnknownUserException is declared as a global exception:

<global-exceptions>
    <exception key="error.unknown.user" 
              type="com.oreilly.strutsckbk.ch09.UnknownUserException"
              path="/securityError.jsp"/>
    ...   
</global-exceptions>

The key specifies a MessageResources message that will be used to generate an ActionError. The path specifies the name of the resource to forward to. If path is omitted, the input page for an action will be used. The generated ActionError is stored in an ActionErrors object and can be displayed on the destination page using the <html:errors/> tag.

Warning

Any ActionErrors stored in the request by your Action will be replaced if your Action throws an exception handled declaratively. If you want to preserve existing ActionErrors, you’ll need to create a custom ExceptionHandler .

Because this exception is declared as a global-exception, any action that throws an UnknownUserException will be handled by this declaration unless overridden by a local exception.

A local exception is defined by nesting the exception element within the action element to which it applies. It defines action-specific handling for specified types of exceptions. The PasswordMatchException is handled by a local exception:

<action path="/Login" ...>
    <exception key="error.password.match" 
              type="com.oreilly.strutsckbk.ch09.PasswordMatchException"
              path="/login.jsp"/>
    <forward .../>
</action>

So what happens when you throw an exception that doesn’t have a declared handler? These exceptions will be wrapped in a ServletException and ultimately thrown by the ActionServlet.service( ) method to be handled by the application server.

See Also

For more information on custom display of error messages, see Recipe 9-6.

You can provide custom exception handling by extending the Struts exception handler. This approach is discussed in Recipe 9.2.

If you want to provide a fallback exception-handling approach instead of relying on the container, take a look at Recipe 9.4.

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 configuration—represented as an ExceptionConfig object—for 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 account—that 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.

9.3. Using Exception Error Codes

Problem

You want to use an exception class that accepts a logical error code or key and optional arguments that can be used to display a localized error message.

Solution

Use or extend my ErrorCodeException, shown in Example 9-6.

Example 9-6. Exception that accepts a numeric error code
package com.oreilly.strutsckbk.ch09;

public class ErrorCodeException extends Exception {
    
    public ErrorCodeException(int code) {
        this.code = code;
    }
    public ErrorCodeException(int code, Object[] args) {
        this.code = code;
        this.args = args;
    }
    public ErrorCodeException(int code, Object[] args, String msg) {
        super(msg);
        this.code = code;
        this.args = args;
    }
    public ErrorCodeException(int code, Object[] args, String msg, 
                                                  Throwable cause) {
        super(msg, cause);
        this.code = code;
        this.args = args;
    }

    public int getCode( ) {
        return code;
    }

    public Object[] getArgs( ) {
        return args;
    }
    private Object[] args;
    private int code;
}

Use my ErrorCodeExceptionHandler, shown in Example 9-7, to handle these exception types.

Example 9-7. Exception handler for the ErrorCodeException
package com.oreilly.strutsckbk.ch09;

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

import org.apache.struts.Globals;
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 ErrorCodeExceptionHandler extends ExceptionHandler {

    public ActionForward execute(
            Exception ex,
            ExceptionConfig ae,
            ActionMapping mapping,
            ActionForm formInstance,
            HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException {
        
        if (!(ex instanceof ErrorCodeException)) {
            return super.execute(ex, ae, mapping, formInstance, request, 
            response);
        }
        
        ErrorCodeException errCodeEx =(ErrorCodeException) ex;
        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( );
        }

        ErrorCodeException ece =(ErrorCodeException) ex;
        String code = Integer.toString(ece.getCode( ));
        error = new ActionMessage(code, ece.getArgs( ));
        property = error.getKey( );

        logException(ex);

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

        return forward;
    }
}

Finally, declare a global exception handler in your struts-config.xml for this exception:

<global-exceptions>
    ...
    <exception key="error.exception" 
              type="com.oreilly.strutsckbk.ch09.ErrorCodeException"
           handler="com.oreilly.strutsckbk.ch09.ErrorCodeExceptionHandler"
               path="/error_page.jsp"/>
</global-exceptions>

Discussion

A lot of large software applications report problems using error codes as well as text. The Oracle database reports errors using an error code formatted as ORA-9999. You can use an Oracle utility program to retrieve the meaning of the error code. This approach allows the application to localize text messages based on the user’s language and country. The ErrorCodeException shown in Example 9-6 can be used in a similar manner in your Struts application. You create this exception class using a numeric error code and an optional set of Object arguments. The error code serves as a key for retrieving a localized message from your MessageResources properties file. The arguments are used at runtime as substitution parameters in that message.

When an Action or business service creates an ErrorCodeException, it can populate the exception with an appropriate error code and an array of arguments for message substitution. The ErrorCodeExceptionHandler shown in Example 9-7 is designed to process ErrorCodeExceptions. The code in the execute( ) method was originally copied from the Struts base ExceptionHandler (org.apache.struts.action.ExceptionHandler) and modified as needed. This custom handler first checks if the error is of type ErrorCodeException; if not, it allows the Struts exception handler to process the Exception by calling super.execute( ). Otherwise, the exception is handled as follows:

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

  2. The error code is retrieved from the ErrorCodeException and converted to a String.

  3. An ActionMessage is created using the error code String and the Object array from the ErrorCodeException.

  4. The exception is logged using the logException( ) method (implemented by the super class).

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

  6. The error is stored by calling storeException( ) (implemented by the super class).

  7. The forward is returned.

Only steps 2 and 3 are specific to this handler. The other steps are identical to the handling in the base ExceptionHandler.

Though not required, you can extend the ErrorCodeException class to create exception types for specific functional areas of your application. Using this approach allows you to control exception processing based on type, yet give the benefits provided by using an error code and message arguments.

If you have an existing application that relies heavily on predefined error codes, this Solution can help bridge the gap with exception handling. The error codes and corresponding error messages can be defined in your application’s default MessageResources file, or they can be stored in their own properties file. Since you are coding the exception handler, you can do it how you want.

The ErrorCodeException and corresponding handler are useful if you need to localize the generated message in support of internationalization. Since the exception handler uses MessageResources, the message can be localized by creating a properties file for specific locales.

See Also

Best practices for localizing exceptions have been discussed a number of times on the struts-user mailing list. You’ll find the thread at http://marc.theaimsgroup.com/?l=struts-user&m=109474569119868&w=2 to be particularly interesting.

Creating a custom exception handler is easier than you think. See Recipe 9.2 for details.

9.4. Using a Global Error Page

Problem

Your application should display the same error page for any server error or uncaught exception thrown from a Struts Action, a servlet, or a JSP page.

Solution

Declare a global error page, such as the one shown in Example 9-8, to handle all exceptions and errors in your web.xml file as well as your struts-config.xml file.

Example 9-8. Global JSP error page
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ page language="java" isErrorPage="true" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>

<html:html>
<head>
   <title>Struts Cookbook Chapter 9 : Global error page</title>
   <style type="text/css">
       h2{background:darkblue;color:white}
       h3{background:darkblue;color:white}
   </style>
</head>
<body>
<div align="center">
   <c:choose>
      <c:when test="${not empty pageContext.exception}">
         <c:set var="problemType">JSP Exception</c:set>
         <c:set var="appException" value="${pageContext.exception}"/> 
         <c:set var="causeException" value="${appException.cause}"/>
      </c:when>
      <c:when test="${not empty requestScope['javax.servlet.
                                                 error.exception']}">
         <c:set var="problemType">Servlet Exception</c:set>
         <c:set var="appException" value="${requestScope['javax.
                                               servlet.error.exception']}"/> 
         <c:set var="causeException" value="${appException.rootCause}"/>
      </c:when>
      <c:when test="${not empty requestScope['org.apache.struts.action.
                                                 EXCEPTION']}">
         <c:set var="problemType">Struts Exception</c:set>
         <c:set var="appException" value="${requestScope['org.apache.
                                               struts.action.EXCEPTION']}"/>
         <c:set var="causeException" value="${appException.cause}"/>
      </c:when>
      <c:otherwise>
         <c:set var="problemType">Unidentified Server Error</c:set>
      </c:otherwise>
   </c:choose>
      <!-- end determine error -->

<!-- start framework -->
<table cellpadding="0" cellspacing="0" border="0" width="750">
    <tr>
        <td valign="top" colspan="2">    
         <table cellpadding="4" cellspacing="0" border="0" 
          width="100%">
            <tr valign="top">
               <td>
                  <!-- start user review -->    
                  <table cellpadding="4" cellspacing="0" border="0" 
                  width="100%">
                     <tr>
                        <td>
                             <h2>System problem</h2>
                        </td>
                     </tr>
                  </table>
                  <table cellpadding="2" cellspacing="1" border="0" 
                  width="80%">
                     <tr>
                        <td colspan="2">
                           A system error has occured. If the
                           problem persists, please contact the User Helpdesk.
                        </td>
                     </tr>
                     <tr><td colspan="2">
                           <html:errors/>
                     </td></tr>
                     <tr valign="top">
                        <td>
                           <b>Problem type</b>
                           <br/><c:out value="${problemType}"/>
                        </td>
                        <td>
                           <b>Problem details</b>
                           <c:if test="${not empty
                            requestScope['javax.servlet.error.message']}">
                            <br/>
                           <c:out value=
                             "${requestScope['javax.servlet.error.message']}"
                           />
                           </c:if>
                           <c:if test="${not empty appException}">
                              <br/><c:out value="${appException.message}"/>
                              &nbsp;
                           </c:if>
                        </td>
                     </tr>
                     <c:if test="${not empty causeException}">
                     <tr>
                        <td>
                           <b>Caused by</b>
                           <br/><c:out value="${causeException}"/>
                        </td>
                        <td>
                           <b>Cause details</b>
                           <br/><c:out value="${causeException.message}"/>
                           &nbsp;
                        </td>
                     </tr>
                     </c:if>
                  </table>
                  <table id="showDetailsLinkDiv" style="{display:inline}"
                       cellpadding="2" cellspacing="1" border="0" width="80%">
                     <tr>
                        <td align="left">
                           [ <a href="javascript:showDetails( )">
                             Show details</a> ]
                        </td>
                     </tr>
                  </table>
                  <table id="hideDetailsLinkDiv" style="{display:none}"
                       cellpadding="2" cellspacing="1" border="0" width="80%">
                     <tr>
                        <td align="left">
                           [ <a href="javascript:hideDetails( )">
                             Hide details</a> ]
                        </td>
                     </tr>
                  </table>
              <!-- begin details -->
                  <div id="stackTraceDiv" style="{display:none}">
                  <c:if test="${not empty appException}">
                     <p></p>
                     <table cellpadding="4" cellspacing="0" 
                            border="0" width="100%">
                        <tr>
                           <td>
                                 <h3>Exception stack trace</h3>
                           </td>
                        </tr>
                     </table>
                     <b><c:out value="${appException}"/></b>
                     <br/>
                     <table align="center" cellpadding="0" cellspacing="0"
                            border="0" width="90%" class="pod">
                        <c:forEach var="stackItem" 
                                 items="${appException.stackTrace}">
                           <tr><td><c:out value="${stackItem}"/></td></tr>
                         </c:forEach>
                     </table>
                  </c:if>
                  <c:if test="${not empty causeException}">
                     <p></p>
                     <table cellpadding="4" cellspacing="0" 
                            border="0" width="100%">
                        <tr>
                           <td>
                                 <h3>Cause stack trace</h3>
                           </td>
                        </tr>
                     </table>
                     <b><c:out value="${causeException}"/></b>
                     <br/>
                     <table align="center" cellpadding="0" cellspacing="0"
                           border="0" width="90%" class="pod">
                        <c:forEach var="stackItem"
                             items="${causeException.stackTrace}">
                           <tr><td><c:out value="${stackItem}"/></td></tr>
                         </c:forEach>
                     </table>
                  </c:if>
                  </div>
              <!-- end details -->
               </td>
            </tr>
         </table>
         </td>
    </tr>
</table>
      
   <script language="javascript">
      function showDetails( ) {
        document.getElementById("showDetailsLinkDiv").style.display = "none";
        document.getElementById("hideDetailsLinkDiv").style.display = "inline";
        document.getElementById("stackTraceDiv").style.display = "inline";
      }
      function hideDetails( ) {
        document.getElementById("showDetailsLinkDiv").style.display = "inline";
        document.getElementById("hideDetailsLinkDiv").style.display = "none";
        document.getElementById("stackTraceDiv").style.display = "none";
      }
   </script>

</div>
</body>
</html:html>

Create an error-page element for handling HTTP Status 500 errors in your web.xml file, specifying the location as the error page:

<error-page>
    <error-code>500</error-code> 
    <location>/error.jsp</location> 
</error-page>

For exceptions thrown from Struts Actions, create a global exception handler for exceptions of type java.lang.Exception in the struts-config.xml file with the path set to the error page:

<global-exceptions>
    <exception key="error.general" 
              type="java.lang.Exception"
              path="/error.jsp"/>    
    ...
</global-exceptions>

Discussion

Exception handling tends to be an afterthought. You’re sitting there testing the path for your application when a 500 error pops up and your boss says “Ugh. That’s ugly. I want the same page to come up no matter what the problem and don’t make the details obvious, but the users need to be able to display the details so they can tell tech support what happened.”

The JSP shown in Example 9-8 can be used for this because it is designed to handle any type of exception that can be thrown by the container. If the exception has a causing exception, the details can be shown for that exception as well. Figure 9-1 shows the display when a NullPointerException is thrown from an Action.

Error page with details hidden
Figure 9-1. Error page with details hidden

Clicking “Show Details” reveals the stack trace, as shown in Figure 9-2.

Error page with details shown
Figure 9-2. Error page with details shown

When a ServletException containing a nested root cause is thrown by a servlet, the error page shows the cause details as well. Figure 9-3 shows the error page with the stack trace details shown.

Exception thrown by a servlet
Figure 9-3. Exception thrown by a servlet

If you scroll down the page in Figure 9-3, you will see the stack trace for the causing exception, as shown in Figure 9-4.

Stack trace of causing exception
Figure 9-4. Stack trace of causing exception

To handle any situation, the error page determines what kind of exception has been thrown. The JSTL c:choose block contains the following logic:

<c:choose>
    <c:when test="${not empty pageContext.exception}">
        <c:set var="problemType">JSP Exception</c:set>
        <c:set var="appException" value="${pageContext.exception}"/> 
        <c:set var="causeException" value="${appException.cause}"/>
    </c:when>
    <c:when test="${not empty requestScope['javax.servlet.error.exception']}">
        <c:set var="problemType">Servlet Exception</c:set>
        <c:set var="appException"
             value="${requestScope['javax.servlet.error.exception']}"/> 
        <c:set var="causeException" value="${appException.rootCause}"/>
    </c:when>
    <c:when test="${not empty 
             requestScope['org.apache.struts.action.EXCEPTION']}">
        <c:set var="problemType">Struts Exception</c:set>
        <c:set var="appException"
             value="${requestScope['org.apache.struts.action.EXCEPTION']}"/>
        <c:set var="causeException" value="${appException.cause}"/>
    </c:when>
    <c:otherwise>
        <c:set var="problemType">Unidentified Server Error</c:set>
    </c:otherwise>
</c:choose>

This block determines if the error is a JSPException, ServletException, or an exception thrown by Struts. For each exception, the causing exception is retrieved.

Tip

You get the causing exception for a ServletException using the rootCause property instead of the normal cause property.

The rest of the page displays the exception message and exception stack trace for the exception and its cause, if any. For exceptions thrown by Struts, the html:errors tag renders any error messages. JavaScript provides the magic for showing and hiding the exception details. The trick is to put the stack trace details in a div tag and then use a JavaScript function to toggle the CSS display style from none to inline.

The one common error this page doesn’t handle is an HTTP Status 404 (Page Not Found) response. If you wanted to use this page for handling these errors, add an additional error-page element to the web.xml. However, you will probably want to handle this error differently, perhaps forwarding to the main menu page of your application or some other location.

This solution works well for intranet applications; for external applications, though, you will probably want to display less information for security purposes. One thing you can do is use this error page in development and testing and then replace it with a terser version when you deploy to production.

See Also

Recipe 9.1 shows you the details on configuring a global exception using Struts declarative exception handling. Recipe 9.6 shows alternatives to html:errors for displaying error messages. Recipe 13-5 shows you a JSP page that you can use specifically for debugging.

For other approaches to localizing exception messages, take a look at some guidelines from Sun’s Java Blueprints at http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/i18n/i18n8.html.

Brian Goetz’s excellent series on exception handling from JavaWorld is a must-read for any Java developer. He discusses the use of message catalogs for exception localization in Part 3 of the series. You can find the entire series at http://www.javaworld.com/javaworld/jw-08-2001/jw-0803-exceptions.html.

9.5. Reporting Errors and Messages from an Action

Problem

You want to display error messages to the user when things go wrong and success messages when things go right.

Solution

For Struts 1.1, use ActionErrors for reporting errors and ActionMessages for informational messages. For Struts 1.2, use ActionMessages for informational messages and errors.

Discussion

Struts gives you a least four classes and three custom tags for creating, storing, and displaying errors and messages. In Struts 1.1, the ActionErrors class is used to hold a collection of errors, represented by ActionError instances. Each ActionError consists of a key to be looked up from the default MessageResources bundle and an optional set of Objects (up to four) to be used as parameters for message substitution. The Action class in Example 9-9 shows typical usage of ActionErrors.

Example 9-9. Creating ActionErrors from an Action
package com.oreilly.strutsckbk.ch09;

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

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.Globals;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class RegistrationAction extends Action {

    public ActionForward execute(ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response)
             throws Exception {
        
        ActionErrors errors = new ActionErrors( );

        String username = (String) PropertyUtils.getSimpleProperty(
                                                 form, "username");
        String password = (String) PropertyUtils.getSimpleProperty(
                                                 form, "password");
        
        if (username.equals(password)) {
            ActionError error = new ActionError(
                   "error.register.sameAsPassword", username);
            // Use  Action Message with Struts 1.2
            //ActionMessage error = new ActionMessage(
            //       "error.register.sameAsPassword", username);
            errors.add("username", error);
        }
        
        if (!errors.isEmpty( )) {
            saveErrors(request, errors);
            return mapping.getInputForward( );
        }
        User user = new User( );
        user.setUsername(username);
        user.setPassword(password);

        SecurityService service = new SecurityService( );
        service.add(user);

        return mapping.findForward("success");
    }
}

If you need to create informational messages, use the ActionMessages and ActionMessage classes in a similar fashion, as shown in Example 9-10.

Example 9-10. Creating ActionMessages from an Action
package com.oreilly.strutsckbk.ch09;

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

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
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.ActionMessages;

public class LoginAction extends Action {

    public ActionForward execute(ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response)
             throws Exception {
        
        ActionMessages messages = new ActionMessages( );
        
        String username = (String) PropertyUtils.getSimpleProperty(
                                                 form, "username");
        String password = (String) PropertyUtils.getSimpleProperty(
                                                 form, "password");
        SecurityService service = new SecurityService( );
        service.authenticate( username, password);

        User user = new User( );
        user.setUsername(username);
        request.getSession( ).setAttribute("user", user);
        
        ActionMessage message = new ActionMessage(
                  "message.login.success", username);

        messages.add(ActionMessages.GLOBAL_MESSAGE, message);

        if (!messages.isEmpty( )) {
            saveMessages( request, messages );
        }

        return mapping.findForward("success");
    }
}

Though you’ll find little difference in the APIs you use to create errors or messages, the approaches for rendering errors or messages can differ. You can display all errors on a JSP page this way:

<html:errors/>

You would think you could do the same thing with the html:messages tag, but the html:messages tag behaves differently than the html:errors tag. By default, the html:messages tag retrieves the values stored as errors—not messages—from the HttpServletRequest. You must explicitly set the message attribute to true to retrieve values stored as messages.

Tip

The html:messages tag was originally created to display error messages without requiring that the message resources contain HTML. After ActionMessages were added to Struts, the html:messages tag was amended to support display of information messages in addition to error messages.

Unlike the html:errors tag, the html:messages tag is a looping tag, like logic:iterate. The tag iterates through ActionErrors, ActionMessages, or an array of strings. The required id attribute defines a page-scoped variable that contains the formatted message. You can use the bean:write or the JSTL c:out tag to display each message:

<html:messages message="true" id="msg">
    <c:out value="${msg}"/><br />
</html:messages>

Unlike the html:errors tag, the html:messages tag doesn’t use a predefined header and footer for display. The presentation details are left to you. Recipe 9.6 shows you some ways to create custom presentation for your errors and messages.

What if you are using Struts 1.2? Well, the ActionError class has been deprecated in favor of ActionMessage in Struts 1.2. On the other hand, ActionErrors has not been deprecated; however, in most places you can use an ActionMessages object where you used to use ActionErrors.

Tip

To preserve the Struts core API, ActionErrors was not deprecated.

So, how do you identify errors versus messages? It depends on the attribute name that they are stored under in the request.

The saveErrors( ) method now accepts ActionMessages instead of ActionErrors; yet it stores the object in the request as errors using the key identified by the constant org.apache.struts.Globals.ERROR_KEY. The saveMessages( ) method accepts ActionMessages. It stores the object in the request using the key identified by the org.apache.struts.Globals.MESSAGE_KEY constant. The html:errors and html:messages tags are used in Struts 1.2 just like in Struts 1.1.

If you have ever needed to store messages HTTP session, you can use a new method, added in Struts 1.2, of the base Action class:

saveMessages(HttpSession session, ActionMessages messages);

This method was added to provide a workaround for message display when the rendering page is the result of a redirect instead of a forward. When the messages are saved in the servlet request, the JSP page that displays those messages must be the result of a forward; if the JSP were redirected to the messages would be lost. With the new saveMessages( ) method, messages are saved in the session and are therefore available whether the page is the result of a forward or a redirect.

Tip

You can redirect instead of forward to the “success page” to prevent double-submission problems. See Recipe 7.9 for more details.

Now that the messages are saved in the session, what’s to prevent them from showing up on a page where they shouldn’t? The Struts developers anticipated this problem. The ActionMessages class provides the isAccessed( ) method to indicate if the messages have been displayed (accessed) or not. The RequestProcessor, in the new processCachedMessages( ) method, checks this value and removes any messages stored in the session that have been accessed. So, though you store the messages in the session, they will be cleaned on the next request after they have been displayed.

Here’s how you would use the new saveMessages method in the Action shown in Example 9-10:

if (!messages.isEmpty(  )) {
    saveMessages( request.getSession(true), messages );
}

You can change the forward for the success page to a redirect:

<forward name="success" path="/login_success.jsp" redirect="true"/>

Your messages will still be available on the success page, and you don’t have to worry about double postings.

See Also

Struts 1.2 introduced the ability to have session-scoped action messages. The Struts 1.2 release notes at http://struts.apache.org/userGuide/release-notes.html describe this feature.

You can use the html:messages tag to render errors or messages; see Recipe 9.6 for additional details on this and other related tags.

9.6. Formatting Error Messages

Problem

Your application requires a custom look and feel for error messages beyond the abilities of the html:errors tag.

Solution

Use the logic:messagesPresent and the html:messages tags to display the error messages in a custom format. The JSP fragment (errors.inc.jsp) shown in Example 9-11 can be included on any page that may need to display errors.

Example 9-11. Custom error display
<logic:messagesPresent>
<table border="1" bgcolor="orange" width="100%" align="center">
    <tr><td>
        <p>
            <img src="/images/icon-warning.gif" border="0"
              vspace="2" hspace="10" align="center">
            WARNING: <bean:message key="errors.heading"/>
        </p>    
        <ul>
            <html:messages id="error">
                <li><bean:write name="error"/></li>
            </html:messages>
        </ul>
    </td></tr>
</table>
<p>
</logic:messagesPresent>

Tip

The images used in the examples are included with the online source.

Discussion

Errors displayed using the JSP code in Example 9-11 result in a display similar to Figure 9-5.

Custom formatted errors
Figure 9-5. Custom formatted errors

Though the html:errors tag is a convenient way of displaying error messages, it’s fairly restrictive in its formatting. By default, it displays all errors for a page, starting with a header markup, then each error message, and ending with footer markup.

Tip

Using the property attribute, you can tell the html:errors tag to display error messages for a specific field. This ability is commonly used to display validation errors beside the invalid input field.

This tag relies on predefined messages in your MessageResources to control the look and feel of the display. These messages are shown in Table 9-1.

Table 9-1. html:errors formatting resources

Message key

Description

Sample value

errors.header

Markup rendered for the header

<font color="red">Validation Errors<ul>

errors.prefix

Markup rendered before each error message

<li>

errors.suffix

Markup rendered after each error message

</li>

errors.footer

Markup rendered for the footer

</ul></font><p />

Though you can use these special message resources to format errors many ways, having HTML markup in a properties file spells trouble. After all, one of the goals of using an MVC framework such as Struts is to separate—not commingle—data and its presentation.

The Solution presents an approach where the entire markup for presentation is contained on the JSP page. First, the logic:messagesPresent tag is used to determine if any errors will display. This tag processes its tag body if it can find a scoped variable stored under the org.apache.struts.Global.ERROR_KEY key. The tag body contains an HTML table used for formatting. A warning icon and message are displayed. Then, the html:messages tag lists each error message:

<ul>
    <html:messages id="error">
        <li><bean:write name="error"/></li>
    </html:messages>
</ul>

The html:messages tag doesn’t render the message; rather, it acts like the logic:iterate tag. It iterates over a set of errors, creating a page-scoped variable containing the error message with a name equal to the value of the id attribute. The error message can be rendered using the bean:write or c:out tags.

So what would you do if you needed to display different types of messages? Say you wanted to display validation errors as warnings, and exceptions as more severe problems. All you need to do is store the different messages in the request using attribute names of your own choosing. Start by creating a base Action, like the one shown in Example 9-12, which provides methods that save the different errors in the servlet request. If you are using a base Action, you can add the methods shown in Example 9-12 to your own class.

Example 9-12. Base Action for saving custom error types
package com.oreilly.strutsckbk.ch09;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMessages;

public class BaseAction extends Action {

    public static final String APP_WARNING_KEY = "APP_WARNING_KEY";
    public static final String APP_ERROR_KEY = "APP_ERROR_KEY";
    
    protected void saveAppWarnings(HttpServletRequest request,
                                   ActionMessages messages) {
        saveAppMessages(request, messages, APP_WARNING_KEY);
    }

    protected void saveAppErrors(HttpServletRequest request, 
                                 ActionMessages messages) {
        saveAppMessages(request, messages, APP_ERROR_KEY);
    }

    private void saveAppMessages(HttpServletRequest request, 
                                 ActionMessages messages, String key) {
        // Remove any messages attribute if none are required
        if ((messages == null) || messages.isEmpty( )) {
          request.removeAttribute(key);
          return;
        }

        // Save the messages we need
        request.setAttribute(key, messages);
    }
}

Actions that subclass this base Action call these specialized methods to store the messages based on severity. The custom Action shown in Example 9-13 subclasses this base Action, calling the saveAppWarnings and saveAppErrors methods as needed.

Example 9-13. Custom error handling by an Action
package com.oreilly.strutsckbk.ch09;

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

import org.apache.commons.beanutils.PropertyUtils;
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.ActionMessages;

public class ValidatingLoginAction extends BaseAction {

    public ActionForward execute(ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response)
             throws Exception {

        ActionMessages appWarnings = form.validate(mapping, request);
        saveAppWarnings(request, appWarnings);
        
        String username = (String) 
               PropertyUtils.getSimpleProperty(form, "username");
        String password = (String) 
               PropertyUtils.getSimpleProperty(form, "password");
        
        ActionMessages appErrors = new ActionMessages( );
                
        try {
            SecurityService service = new SecurityService( );
            service.authenticate( username, password);            
        }
        catch (PasswordMatchException e) {
            appErrors.add("password", new ActionMessage("error.
                                               password.match"));
        }

        saveAppErrors(request, appErrors);
        
        if (!appWarnings.isEmpty( ) || !appErrors.isEmpty( )) {
            return mapping.getInputForward( );
        }
        
        User user = new User( );
        user.setUsername(username);
        request.getSession( ).setAttribute("user", user);
        
        ActionMessages messages = new ActionMessages( );
        ActionMessage message = new ActionMessage("message.login.success",
                                                  username);

        messages.add(ActionMessages.GLOBAL_MESSAGE, message);

        if (!messages.isEmpty( )) {
            saveMessages( request.getSession(true), messages );
        }

        return mapping.findForward("success");
    }
}

If you look closely at the Action in Example 9-13, you’ll see that it does something kind of strange: The ActionForm’s validate method is called within the execute( ) method. Why would you do something like this? Well, remember from the requirements, you want to store validation errors as warnings; in other words, the errors should be stored under a custom request attribute name. The only way to accomplish this is for the Action to call the ActionForm’s validate method. Since your Action controls the validation workflow, configure the corresponding action mapping in the struts-config.xml file so Struts doesn’t call the ActionForm’s validate method:

<action    path="/ValidatingLogin"
           type="com.oreilly.strutsckbk.ch09.ValidatingLoginAction"
          scope="request"
           name="LoginForm"
        validate="false"
          input="/validating_login.jsp">
    <forward name="success" path="/login_success.jsp" redirect="true"/>
</action>

Now that you’ve got the warnings and errors being stored under separate attribute names, you can create a common JSP fragment to display them. The JSP fragment (errors2.inc.jsp) shown in Example 9-14 uses the name attribute of the logic:messagesPresent tag and html:messages tag to indicate the type of error to process.

Example 9-14. JSP fragment for displaying custom errors
<logic:messagesPresent name="APP_ERROR_KEY">
    <table border="1" bgcolor="orange" width="100%" align="center"><tr><td>
    <p>
        <img src="/images/icon-alert.gif" border="0"
          vspace="2" hspace="10" align="center">
        <bean:message key="errors.heading"/>
    </p>    
    <ul>
        <html:messages id="error" name="APP_ERROR_KEY">
           <li><bean:write name="error"/></li>
        </html:messages>
    </ul>
</td></tr></table>
<p>
</logic:messagesPresent>
<logic:messagesPresent name="APP_WARNING_KEY">
    <table border="1" bgcolor="yellow" width="100%" align="center"><tr><td>
    <p>
        <img src="/images/icon-warning.gif" border="0" 
          vspace="2" hspace="10" align="center">
        <bean:message key="warnings.heading"/>
    </p>    
    <ul>
        <html:messages id="error" name="APP_WARNING_KEY">
           <li><bean:write name="error"/></li>
        </html:messages>
    </ul>
</td></tr></table>
<p>
</logic:messagesPresent>

Now, when the errors are displayed, you’ll see a page like the one shown in Figure 9-6.

Display of different error types
Figure 9-6. Display of different error types

If you’re new to web development, this last example may seem a bit extreme, but it demonstrates the flexibility afforded by Struts. If you don’t like the way Struts handles errors, then you can do it yourself. As long as you utilize the Struts APIs, you’ll find that the Struts tags can cope with most customizations in an intelligent and logical way.

See Also

Ted Husted discusses custom error formatting in his invaluable set of Struts Tips. The specific tip (#17) can be found at http://husted.com/struts/tips/017.html.

Rick Reumann, a frequent poster to the struts-user mailing list and all-around good guy, prefers to call the ActionForm’s validate method as he explains in the archived posting at http://marc.theaimsgroup.com/?l=struts-user&m=109242668231755&w=2.

If you are using declarative exception handling, you can use a similar approach for storing the exception error message by creating a custom exception handler. Custom exception handlers are discussed in Recipe 9.2.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.26.221