Programming Jakarta Struts, 2nd Edition [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Jakarta Struts, 2nd Edition [Electronic resources] - نسخه متنی

Chuck Cavaness

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








10.5 Exception Handling Provided by Struts


Prior to Version 1.1, the Struts framework
provided very minimal exception handling for applicationsit
was left to you to extend the framework with your own
exception-handling capabilities. This encouraged each development
group to approach a solution from a different direction and made it
difficult to discuss common solutions.

In Version 1.1, Struts added a small but effective exception-handling
framework for your applications. The approach that the Struts
designers took follows the EJB and Servlet specifications for
handling security, allowing developers to use a declarative and/or a
programmatic approach.


Using the AppException Class


The
org.apache.struts.util.ModuleException class is included with the Struts
framework. This class is a wrapper around an
ActionError and extends
java.lang.Exception. It provides the ability to
throw exceptions in a Struts application like:

throw new ModuleException("error.password.mismatch");

where the argument to the ModuleException
constructor is a resource bundle key. The framework will
automatically create ActionError objects from
these exceptions and store them in the appropriate scope. You can
extend the ModuleException class with
application-specific exceptions for your application.

One problem with using this class, however, is that it couples your
application, and specifically your exception-handling code, to the
Struts framework. It relies on classes that are specific to the
Struts framework, which is problematic if you are using an
application tier such as EJB. You don't want to
couple your entire application to Struts if you can avoid it. If you
are building a small web application where you will never need to
replace Struts with something else and you are not worried about
coupling your model components to Struts, the
ModuleException can add value. Otherwise, you
might be better off avoiding its use.


10.5.1 Declarative Versus Programmatic Exception Handling


Declarative
exception handling is accomplished by expressing an
application's exception-handling policy, including
which exceptions are thrown and how they are to be handled, in a text
file (typically using XML) that is completely external to the
application code. This approach makes it easier to modify the
exception-handling logic without major recompilation of the code.

Programmatic exception handling is the opposite.
It is the traditional method, involving writing application-specific,
intra-method code to handle the exceptions, rather than simply
modifying an external configuration file. However, it is quite a bit
more complex within a Struts application.

As with other Struts configuration options, the declarative mappings
are done in the Struts configuration file. As you saw in Chapter 4, you can specify the exceptions that may
occur and what to do if they occur, both at a global level and for a
specific action mapping. For a discussion of the parameters available
for the exception-handling elements, refer back to Chapter 4.

Example 10-3 shows a partial Struts configuration
file that declares three different exceptions that may be thrown from
the login action.


Example 10-3. A Struts configuration file that uses declarative exception handling

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<action-mappings>
<action
path="/login"
type="com.oreilly.struts.storefront.security.LoginAction"
name="loginForm"
scope="request"
input="/login.jsp">
<!--The following exceptions can be thrown during the login action -->
<exception
key="security.error.changepassword"
path="/changePassword.jsp"
type="com.oreilly.struts.framework.exceptions.ExpiredPasswordException"/>
<exception
key=" security.error.loginfailed"
type="com.oreilly.struts.framework.exceptions.InvalidLoginException"
path="/login.jsp"/>
<exception
key="security.error.accountlocked"
type="com.oreilly.struts.framework.exceptions.AccountLockedException"
path="/accountLocked.jsp"/>
</action>
</action-mappings>
</struts-config>

The exception element
that is defined either in the action mapping or in the global
exceptions section specifies the path to which to forward when one of
the specified exceptions occurs during the corresponding action
invocation. For example, if an
ExpiredPasswordException is thrown during the
login action, the controller will forward
control to the changePassword.jsp page.
Likewise, if an AccountLockedException is thrown,
control will be forwarded to the
accountLocked.jsp page.

Whenever an exception is not programmatically handled in the
Action class, the
RequestProcessor gets a chance to see if there is
an exception element configured for that specific
exception type. If there is, control is forwarded to the resource
specified in the path attribute of the
exception element. Example 10-4
shows the processException() method from the
RequestProcessor class.


Example 10-4. The processException( ) method from the Struts RequestProcessor

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

Notice that an ExceptionConfig object may be
returned from the findException() method at the
beginning of the processException() method. The
ExceptionConfig object is an in-memory
representation of the exception element specified
in the configuration file. If the findException()
method doesn't find an exception
element for the specific type of exception that occurred, the
exception is thrown back to the client without going through a Struts
exception handler. Unless the exception is an
IOException or one of its subclasses, the
exception will be wrapped by a ServletException
instance and rethrown.

If there is an exception element specified in the
action mapping for the specific type of exception that occurs, an
ExceptionConfig object is returned from the
findException() method. The getHandler(
)
method is then called on the
ExceptionConfig object, and the handler retrieved
is used to process the exception.

The Struts framework has a default
exception-handler class that is used to process the exceptions if you
don't configure one of your own. The default handler
class is
org.apache.struts.action.ExceptionHandler. The execute() method
of this handler creates an ActionError, stores it
into the proper scope, and returns an
ActionForward object that is associated with the
path attribute specified in the
exception element. To summarize, if you declare an
exception element inside an
action element, the default exception handler will
create and store an ActionError into the specified
scope and give control to the resource specified in the
path attribute.

As you saw back in Chapter 4, the exception element
also allows you to override the exception handler's
behavior if you want a different behavior when an exception occurs.
You can do this by specifying a fully qualified Java class that
extends the
org.apache.struts.action.ExceptionHandler class in
the exception's handler
attribute. This class will override the execute()
method of ExceptionHandler in order to perform the
specialized behavior. For example, your application exceptions could
extend the BaseException class shown in Example 10-5.


Example 10-5. An exception class that supports a message key and arguments

package com.oreilly.struts.framework.exceptions;
import java.util.List;
import java.util.ArrayList;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* This is the common superclass for all application exceptions. This
* class and its subclasses support the chained exception facility that allows
* a root cause Throwable to be wrapped by this class or one of its
* descendants. This class also supports multiple exceptions via the
* exceptionList field.
*/
public class BaseException extends Exception{
protected Throwable rootCause = null;
private List exceptions = new ArrayList( );
private String messageKey = null;
private Object[] messageArgs = null;
public BaseException( ){
super( );
}
public BaseException( Throwable rootCause ) {
this.rootCause = rootCause;
}
public List getExceptions( ) {
return exceptions;
}
public void addException( BaseException ex ){
exceptions.add( ex );
}
public void setMessageKey( String key ){
this.messageKey = key;
}
public String getMessageKey( ){
return messageKey;
}
public void setMessageArgs( Object[] args ){
this.messageArgs = args;
}
public Object[] getMessageArgs( ){
return messageArgs;
}
public void setRootCause(Throwable anException) {
rootCause = anException;
}
public Throwable getRootCause( ) {
return rootCause;
}
public void printStackTrace( ) {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream outStream) {
printStackTrace(new PrintWriter(outStream));
}
public void printStackTrace(PrintWriter writer) {
super.printStackTrace(writer);
if ( getRootCause( ) != null ) {
getRootCause( ).printStackTrace(writer);
}
writer.flush( );
}
}

The BaseException class in Example 10-5 contains a messageKey that
can be used as a key in the Struts resource bundle. This key can be
passed into the constructor of the ActionError
class, and the Struts framework will match it to a message in the
Struts resource bundle. This class also contains an object array that
the creator of the exception can populate. These objects can then be
used to substitute into a message from the bundle that contains
substitution parameters based on the MessageFormat
class. A message in the bundle might look like this:

global.error.invalid.price=The price must be between {0} and {1}.

When creating an ActionError object, you can pass
an array of objects as the second parameter, and each object will be
substituted into the parameters enclosed by the braces. The 0th
element in the array will be inserted into the {0} position, the
object at index 1 will be inserted into the {1} position, and so on.
Chapter 12 covers this topic in more detail.

Example 10-6 illustrates how to extend the
default exception-handler class and
provide specialized behavior for substituting the arguments from the
exception into the ActionError constructor.


Example 10-6. A specialized exception handler

package com.oreilly.struts.chapter10examples;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ExceptionHandler;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionError;
import org.apache.struts.util.AppException;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ExceptionConfig;
import com.oreilly.struts.framework.exceptions.BaseException;
public class SpecialExceptionHandler extends ExceptionHandler {
protected ActionForward execute(Exception ex,
ExceptionConfig config,
ActionMapping mapping,
ActionForm formInstance,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
ActionForward forward = null;
ActionError error = null;
String property = null;
/* Get the path for the forward either from the exception element
* or from the input attribute.
*/
String path = null;
if (config.getPath( ) != null) {
path = config.getPath( );
}else{
path = mapping.getInput( );
}
// Construct the forward object
forward = new ActionForward(path);
/* Figure out what type of exception has been thrown. The Struts
* AppException is not being used in this example.
*/
if( ex instanceof BaseException) {
// This is the specialized behavior
BaseException baseException = (BaseException)ex;
String messageKey = baseException.getMessageKey( );
Object[] exArgs = baseException.getMessageArgs( );
if ( exArgs != null && exArgs.length > 0 ){
// If there were args provided, use them in the ActionError
error = new ActionError( messageKey, exArgs );
}else{
// Create an ActionError without any arguments
error = new ActionError( messageKey );
}
}else{
error = new ActionError(config.getKey( ));
property = error.getKey( );
}
// Store the ActionError into the proper scope
// The storeException method is defined in the parent class
storeException(request, property, error, forward, config.getScope( ));
return forward;
}
}

The specialized behavior that you perform in your handler class is up
to you. The behavior shown in Example 10-6 makes the
error messages more informative by inserting arguments into the
ActionError.


For further information on how to install a custom exception handler,
see "The Struts Configuration DTD"
in Chapter 4.

There are plenty of other instances where you might need to override
the default behavior. The default exception handler provided by the
Struts framework doesn't support an exception object
that stores
multiple exceptions. If your
application needs to support this behavior, you'll
need to create your own ExceptionHandler class.

In most cases, the Struts exception handler is sufficient. Only when
you need specialized exception handling that can't
be obtained from the Struts exception handler should you bother to
create your own. Figure 10-3 illustrates a sequence
diagram for the Struts default exception-handling mechanism.


Figure 10-3. A sequence diagram for Struts exception handling

Using the Struts declarative exception-handling mechanism does not
preclude you from also using a programmatic approach. In fact, they
can work quite well together. Your Action classes
will get the first opportunity to handle any specific exceptions and
only if an exception is not caught and handled by the
Action instance will it be caught by the
processActionPerform() method in the
RequestProcessor class. The
RequestProcessor will then use the declarative
exception-handling mechanism to process the error. The next section
discusses how to handle exceptions using a programmatic approach.


10.5.2 Using Programmatic Exception Handling


The alternate approach to the
declarative exception handling provided by Struts is to build the
application-specific exception handling into the code itself. This
means that you will have to extend the framework with behavior
specific to your application.

As mentioned earlier in this chapter, there are two basic courses of
action when an exception is thrown within an
Action class. If the exception is an application
exception, the course of action is to log the exception, create and
store an ActionError into the appropriate scope,
and forward control to the appropriate
ActionForward. Recall from the discussion of
declarative exception handling that this is the same behavior that
the Struts default exception handler performs.

In the case of the Storefront application, application exceptions
would all be descendants of BaseException;
it's easy to detect when an application exception
occurs because you can simply use a catch block for
BaseException. If the exception is not an instance
of BaseException, you can assume that
it's a system exception and should be treated as
such. The course of action for system exceptions is normally to log
the exception and return an ActionForward for the
system error page.

At first, you might be tempted to add try/catch blocks in your
Action classes and perform the exception handling
like this:

try{
// Peform some work that may cause an application or system exception
}catch( BaseException ex ){
// Log the exception
// Create and store the action error
ActionErrors errors = new ActionErrors( );
ActionError newError = new ActionError( ex.getErrorCode( ), ex.getArgs( ) );
errors.add( ActionErrors.GLOBAL_ERROR, newError );
saveErrors( request, errors );
// Return an ActionForward for the Failure resource
return mapping.findForward( "Failure" )
}catch( Throwable ex ){
// Log the exception
// Create and store the action error
ActionError newError = new ActionError( "error.systemfailure" );
ActionErrors errors = new ActionErrors( );
errors.add( ActionErrors.GLOBAL_ERROR, newError );
saveErrors( request, errors );
// Return an ActionForward for the system error resource
return mapping.findForward( IConstants.SYSTEM_FAILURE_PAGE );
}

The problem with this approach is that you end up having the same
redundant code inside almost every Action class.
Eliminating this redundancy is one of the benefits of using the
declarative approach. However, if you don't want to
use the declarative approach, or if you can't
because you're using an earlier version of Struts,
there's an alternate approach that
doesn't involve so much redundancy.

In Chapter 5, you saw how the use of an
abstract base Action like the
StorefrontBaseAction class can reduce the
redundancy inside the Action classes for other
issues. You can also push the programmatic exception-handling
functionality up to the abstract class, so you
don't need to have it in all of your
Action classes. To do this, you will need to
implement the additional executeAction(
)
method. The executeAction() method is
introduced by the StorefrontBaseAction class and
is an implementation of the Template design pattern. Example 10-7 shows the execute() method
of the StorefrontBaseAction class.


Example 10-7. The execute( ) method of the StorefrontBaseAction

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ActionForward forwardPage = null;
try{
UserContainer userContainer = getUserContainer( request );
// Inform the specific action instance to do its thing
forwardPage = executeAction(mapping, form, request, response, userContainer);
}catch (BaseException ex){
// Log the application exception using your logging framework
// Call the generic exception handler routine
forwardPage = processExceptions( request, mapping, ex );
}catch (Throwable ex){
// Log the system exception using your logging framework
// Make the exception available to the system error page
request.setAttribute( Action.EXCEPTION_KEY, ex );
// Treat all other exceptions as system errors
forwardPage = mapping.findForward( IConstants.SYSTEM_FAILURE_KEY );
}
return forwardPage;
}

The execute() method invokes the
executeAction() method, which all
Action subclasses must override, and wraps the
invocation with the appropriate try/catch blocks.

The StorefrontBaseAction class is abstract and
provides an abstract version of the executeAction(
)
method, which is shown here:

abstract public ActionForward executeAction( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response,
UserContainer userContainer )
throws BaseException;

When any application exception occurs, as long as it extends
StorefrontBaseAction, it will be caught in the
try/catch block inside the execute() method. The
subclasses don't have to worry about providing a
catch block unless they plan to provide further specialized behavior
for the exception.

The execute() method passes the exception, along
with the request and mapping objects, to the
processExceptions() method shown in Example 10-8.


Example 10-8. The processExceptions( ) method in the StorefrontBaseAction

protected ActionForward processExceptions( HttpServletRequest request,
ActionMapping mapping,
BaseException ex ){
ActionErrors errors = new ActionErrors( );
ActionForward forward = null;
// Get the locale for the user
Locale locale = getUserContainer( request ).getLocale( );
if (locale == null){
// If it hasn't been configured, get the default for the environment
locale = Locale.getDefault( );
}
processBaseException(errors, (FieldException) ex, locale);
// Either return to the input resource or a configured failure forward
String inputStr = mapping.getInput( );
String failureForward = mapping.findForward(IConstants.FAILURE_KEY);
if ( inputStr != null) {
forward = new ActionForward( inputStr );
}else if (failureForward != null){
forward = failureForward;
}
// See if this exception contains a list of subexceptions
List exceptions = ex.getExceptions( );
if (exceptions != null && !exceptions.isEmpty( ) ){
int size = exceptions.size( );
Iterator iter = exceptions.iterator( );
while( iter.hasNext( ) ){
// All subexceptions must be BaseExceptions
BaseException subException = (BaseException)iter.next( );
processBaseException(errors, subException, locale);
}
}
// Tell the Struts framework to save the errors into the request
saveErrors( request, errors );
// Return the ActionForward
return forward;
}

The processExceptions() method seems quite
complex, but it's really not that bad. Here are the
steps that the method performs:

  1. Obtain the locale for the user.

  2. Call the processBaseException() method to process
    the top-level exception.

  3. If there are any subexceptions, process each one.

  4. Save all of the ActionErrors that were created.

  5. Return control back to either the resource identified in the
    input attribute of the action or a
    "Failure"
    ActionForward that has been configured for the
    action.


The processBaseException() method is where the
ActionError objects are created. This method is
shown in Example 10-9.


Example 10-9. The processBaseException( ) method of the StorefrontBaseAction class

 protected void processBaseException( ActionErrors errors,
BaseException ex,
Locale locale) {
// Holds the reference to the ActionError to be added
ActionError newActionError = null;
// The errorCode is the key to the resource bundle
String errorCode = ex.getMessageKey( );
/**
* If there are extra arguments to be used by the MessageFormat object,
* insert them into the argList. The arguments are context sensitive
* arguments for the exception; there may be 0 or more.
*/
Object[] args = ex.getMessageArgs( );
/**
* In an application that had to support I18N, you might want to
* format each value in the argument array based on its type and the
* user locale. For example, if there is a Date object in the array, it
* would need to be formatted for each locale.
*/
// Now construct an instance of the ActionError class
if ( args != null && args.length > 0 ){
// Use the arguments that were provided in the exception
newActionError = new ActionError( errorCode, args );
}else{
newActionError = new ActionError( errorCode );
}
errors.add( ActionErrors.GLOBAL_ERROR, newActionError );
}

The processBaseException() method is responsible
for creating the ActionError object. It uses the
messageKey field to look up a bundle message, and
if any arguments are included, it includes those in the
ActionError constructor as well.

As you can see, adding programmatic exception handling to your
applications definitely requires more work than using the default
behavior provided by the Struts framework. It also makes maintenance
more difficult if you drastically change your exception hierarchy or
change how you want to handle certain exceptions. However, if you are
using an earlier version of Struts, this may be your only choice. You
may have to extend these examples for your own applications, but they
show a well- designed approach that you can build upon.

Within the EJB and Servlet specifications, programmatic security is
frowned upon because it's too easy to couple your
application to the physical security environment. With exception
handling, it's unlikely that you'll
need to change the exceptions that are thrown based on the target
environment. Therefore, there isn't the same stigma
associated with programmatic exception handling as there is with
programmatic security. It is true, though, that if you can take
advantage of declarative exception handling, your application will be
easier to maintain than if you have the same functionality in your
source code. An application will almost always be modified over time,
and new exceptions will need to be thrown and caught. The more you
can specify declaratively, the easier time you'll
have maintaining it.


    / 181