Struts Tip #15 - Use chained exceptions

Ted Husted

 

 

Many Java mavens recommend that business objects throw their own exceptions. Internally, a component may catching a SQL or IO exception, but what we really need to tell the user is that a data access error occurred. Of course, at the same time, you do not want to sacrifice any detail from the original exception. Retaining detail from the exception can especially important in a layered, multi-tiered, or multi-platform application.. The business component may not have direct access to the log, and the exception is its only way of telling us what went wrong.

In short, we need a way to keep the detail of the original message but at the same time also add a user-friendly business exception. The former may complain that it could not process a SQL query. The latter may simply report that a "data access error" occurred and suggest that you contact the database administration. We would also want to sure to log both exceptions, to sure all the detail is maintain some actually contact a DBA.

As the exception class was originally designed, doing something like this is a problem. The Java exception mechanism allows you to throw only throw one exception, not two or three. It's easy to "wrap" exceptions, using one to initialize the next, but that results in loss of detail over multiple layers. What we really need to do is "stack" or "chain" the exceptions, so that each layer can add its own viewpoint to the incident. Then, at the end, display them all, with the originating exception at the bottom of the list.

This approach works surprisingly well in a layered architecture. The "topmost" layer is "closest" to the user, and so throws the most "user-friendly" exceptions. The "lowest" layer throws the "geek-friendly" errors that we need to solve the problem. When we chain exceptions by linking them together, the user-friendly message comes first, followed by the more detailed messages. The user is told what they need to know first, and can leave the rest to the system administrators.

The best part is that chaining exceptions is very easy to implement in Struts!

Java 1.4 provides new functionality for chaining exceptions, but it is not difficult to write your own ChainedException class for using with another JVM. The Scaffold package, currently bundled with Artimus, includes a ChainedException class that works with older JVMs.

Here's a try/catch block from a base Action that uses the Scaffold ChainedException class. It calls a member method to perform the business operation and then analyzes the result.

// (1) 
ActionErrors errors = new ActionErrors(); 
try {
 executeLogic(mapping,form,request,response);
} 
catch (Exception exception) {
// (2) 
 servlet.log("*** ACTION EXCEPTION: ", exception);
 exception.printStackTrace();
// (3) 
 errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.general"));
// (4) 
 StringBuffer sb = new StringBuffer(); 
 if (exception instanceof ChainedException) {
  ChainedException e = (ChainedException) exception;
  e.getMessage(sb); 
 } 
 else { 
  sb.append(exception.getMessage()); 
 } 

// (5) 
 errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.detail",sb.toString()));

} // end catch
// (6) ... branch to error page if errors are not empty

(1) We setup an empty ActionErrors collection for later use, call the business operation, and catch any and all exceptions it might throw.

(2) If an exception is thrown, we start by logging the message and including a marker so that it is easier to find later. We also print the stack trace, to sure that is recorded for future reference.

(3) We start by adding our own general error message to the errors collection. This just says something like "The process did not complete. Details should follow."

(4) If the business operation passes back a subclass of ChainedException, we trundle through the chain, and collect all the messages.

(5) To make the messages easy to display a presentation page, we wrap them in a generic message template. The message template is a single substitution code,

error.detail={0}

so that it just prints everything verbatim.

The end result is a series of error messages like

* A required resource is not available.
* The process did not complete. Details should follow.
* Cannot connect to MySQL server localhost:3307. Is there a MySQL server running the machine/port you are trying to connect to? (java.net.ConnectException)

being

1. The business object message.
2. The general error message.
3. The original message from the JDBC driver.

An advantage to this approach is that it provides a high-level message for the user ("A required resource is not available.") and a low-level message for technical support ("Cannot connect to MySQL server ..."). So, everybody's happy! The user gets a reasonable message. Support gets the detail needed to solve the problem.

-----

Struts Tips  are based on excerpts from the book Struts in Action. The tips released weekly on the MVC-Programmers List. To subscribe, visit BaseBean Engineering.

About Ted. Ted Husted is an active Struts Committer and co-author of Struts in Action and Professional JSP Site Design. Ted also manages the JGuru Struts FAQ and moderates the Struts mailing list.

Copyright Ted Husted 2002. All rights reserved.

 

0 Comments  (click to add your comment)
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

About | Sitemap | Contact