Open Source Software Technical Articles

Want the Best of the Wazi Blogs Delivered Directly to your Inbox?

Subscribe to Wazi by Email

Your email:

Connect with Us!

Current Articles | RSS Feed RSS Feed

Creating custom exceptions with Struts

  
  
  

Apache Struts, the web application framework, helps developers get Java web apps up and running in next to no time, using a Model-View-Controller framework. One important part of creating a great app is handling errors and exceptions correctly. Struts can handle some types of user entry validation within the app with UI tags such as ActionError. But what about when your app throws an exception? You don't want your users to be faced with nothing but an impenetrable Tomcat stack trace page. Happily, Struts makes it easy to set up custom exception handling, so you can make life easier for your users if something does go wrong.

I'll assume here that you're running Struts on Tomcat. If you're using another server, you may need to deploy your web app slightly differently, but the Struts code itself should work identically.

Default exception handler

Before we start writing our own error-handling code, let's take a quick look at the error page you get if you don't put in any specific handling (which may be different if you're running on a non-Tomcat server). We'll edit MyStruts.java (from this previous tutorial) to throw an exception:

 public class MyStruts extends ActionSupport {
  public String execute() throws Exception {
    throw new NullPointerException();
  }
}

Recompile (cd WEB-INF/;javac -cp lib/xwork-core-2.3.4.1.jar -d classes src/example/MyStruts.java), restart Tomcat, and go to http://localhost:8080/mystruts/MyStruts. You'll see a fairly ugly HTTP 500 page with a big stack trace on it: That might be useful for you as a developer, but it's disconcerting for the average end user.

500 exception

Create a custom error page

The easiest way to deal with this is simply to create a custom error page and direct error results to that. Create error.jsp in the root mystruts directory:

<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
 
<body>
<h1>Struts 2 Error Page</h1>
 
</body>
</html>

You can set this to load if there's an exception in MyStruts. Open up WEB-INF/classes/struts.xml and add a couple of lines in the MyStruts action section:

<struts>
  <package name="default" extends="struts-default">
    <action name="MyStruts" class="example.MyStruts">
      <exception-mapping exception="java.lang.Exception" result="error" />
      <result name="success">/response.jsp</result>
      <result name="input" type="redirect">/input.jsp</result>
      <result name="error">/error.jsp</result>
    </action> 
  </package>
<struts>

The exception-mapping line maps any Exception at all (since all Java Exceptions are descendants of java.lang.Exception) to the "error" result. This result is defined a couple of lines later, and directs to /error.jsp. Restart Tomcat and visit http://localhost:8080/mystruts/MyStruts again. This time you should see your custom error page:

Exception error

Displaying exception info in the browser

The current error page is prettier than the HTTP 500 default one, but it's much less informative. This is good in some ways (users don't in general want to be overwhelmed by stack trace info, and it can be a security risk to show lots of stack trace output), but it makes it trickier to track down problems. Happily, if you do want to show that information, Struts also provides you with a way to display it in a more user-friendly fashion.

Edit error.jsp again and add a couple of properties:

<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
 
<body>
<h1>MyStruts Error Page</h1>

 <h4>Sorry, MyStruts has a problem!</h4>

 <p>Please email the administrator with the information below, and we'll get things working again as soon as we can.</p>
 <hr />

 <p>
 <b>Exception Name:</b> <br />
 <s:property value="exception" /> 
 </p>

 <p>
 <b>Exception Details:</b> </br>
 <s:property value="exceptionStack" />
 </p>
</body>
</html>

As you might expect, the exception property returns the last exception, and the exceptionStack property returns the last exception's stack trace. Restart Tomcat, reload the page, and you'll see your stack trace is back:

Exception error with stack

Again, do be aware that displaying this information may be a security risk. Think carefully about how much information you want to display to an end user; the optimal amount may vary depending on who your end users are.

Other improvements

As the above code stands, you have to specify an exception handler for each action, which can turn into a lot of work. To simplify things a bit, you can declare a global exception handler. Edit struts.xml to look like this:

<struts>
  <package name="default" extends="struts-default">
    <global-exception-mappings>
      <exception-mapping exception="java.lang.exception" result="error" />
    </global-exception-mappings>  

     <action name="mystruts" class="example.mystruts">
      <result name="success">/response.jsp</result>
      <result name="input" type="redirect">/input.jsp</result>
      <result name="error">/error.jsp</result>
    </action> 
  </package>
</struts>

Here, all exceptions in this package will be dealt with by the global exception mapping, which, again, sends them to the "error" result. However, that "error" result is still declared on a per-action basis, which means that you could divide up different sorts of errors on a global basis (so all security exceptions are handled differently from all other exceptions, for example), but personalize each error response according to the specific action within the package.

You might, however, want to use the same error result for all errors within the package. In this case, you'll want to declare a global result in struts.xml, as well as a global exception mapping:

<struts>
  <package name="default" extends="struts-default">
    <global-results>
      <result name="error">/error.jsp</result>
    </global-results>  

    <global-exception-mappings>
      <exception-mapping exception="java.lang.exception" result="error" />
    </global-exception-mappings>  

     <action name="mystruts" class="example.mystruts">
      <result name="success">/response.jsp</result>
      <result name="input" type="redirect">/input.jsp</result>
    </action> 
  </package>
</struts>

Now "error" is declared as a global result, and any exceptions from any action within this package are sent to the error.jsp page.

Note! In this XML file, the order of the elements matters. If you have the global-exception-mapping section before the global-results section, you'll get an error that will generate a line like this in the Tomcat logs:

Caused by: org.xml.sax.SAXParseException: The content of element type "package" must match "(result-types?,interceptors?,default-interceptor-ref?,default-action-ref?,default-class-ref?,global-results?,global-exception-mappings?,action*)".

This specifies the order in which the parser expects to see the various elements.

You should also carefully consider how you handle exceptions in your code. For example, it may be more appropriate to catch than to throw an exception, in which case, depending on how you deal with the exception, you may never get as far as the exception handler. You can also choose the extent to which you handle different types of exceptions differently, or even choose to create your own exception types (for which you can then create a specific Struts result tag) to improve your user experience. All this is part of designing and building a robust, good-quality app.

Enabling the logger

Finally, the Struts2 ExceptionMappingInterceptor also offers logging options, which you can set by editing struts.xml:

<struts>
  <package name="default" extends="struts-default">
    <interceptors>
      <interceptor-stack name="myStrutsDefaultStack">
        <interceptor-ref name="defaultStack">
          <param name="exception.logEnabled">true</param>
          <param name="exception.logLevel">ERROR</param>
        </interceptor-ref>
      </interceptor-stack>
    </interceptors>
  .....
  </package>
</struts>

Remember that this needs to be in the correct place in the file; see above for the order in which Struts expects to see the package elements.

Here we are creating a new stack of interceptors, called myStrutsDefaultStack, based on the defaultStack of interceptors (identified by the interceptor-ref element). These are the interceptors that run automatically whenever an Action class method is called. One of these is the ExceptionMappingInterceptor, which is named exception in the definition of the defaultStack. Finally, we set the logEnabled and logLevel parameters on this interceptor.

This means that when an uncaught exception is thrown, Struts will not only handle it (firing one of your error results, as we've already set up), but will also log it to your web server's console. (For Tomcat, this should be in your default logging directory, usually $CATALINA_HOME/logs.) You can set the level to the standard log levels (WARN, INFO, DEBUG, ERROR, etc). For more information on debugging Struts, see the Struts 2 debugging docs.

Handling errors properly is an important part of building a robust and user-friendly app. Once again, Struts steps up to the plate to help you manage your errors well with the minimum of fuss – and provides a little debugging help along the way.




This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.

Comments

Currently, there are no comments. Be the first to post one!
Post Comment
Name
 *
Email
 *
Website (optional)
Comment
 *

Allowed tags: <a> link, <b> bold, <i> italics