Thursday, June 25, 2009

Struts ActionMessages and ActionErrors

Tutorials : Handling Messages, Errors and Exceptions in Struts 1.1 :

by Keld H. Hansen

Introduction

This article is about Jakarta Struts 1.1, the most popular framework for building Java servlet applications. One of the new, useful features in 1.1 is the possibility to specify exception handling in the Struts config file. It's called "declarative exception handling", and it's one of the topics for this article.

You use exceptions when the application has encountered a serious error. The error could be an SQL statement that fails, or a connection to another computer that couldn't be established. As a programmer, you should first of all record the situation as precisely as you can. For example, writing information to a log file. You might also have to fix up a few things to keep the program state stable, but eventually you'll also have to inform the user of the situation. So to move forward, one issue in exception handling is how to handle messages, and I'll therefore start by explaining how you generally should handle messages for the end user in Struts. A special class of messages are "validation messages", which are sent out as the result of the validation of a form. If you're interested in how to set up forms validation you may want to read my article "Stepping through the Struts 1.1 Validator".

After having discussed message handling in Struts, we'll see how Struts handles exceptions and start to investigate what the benefits of declarative exception handling could be.

Handling of Messages

Let's set the scene: we're in the Action class and want to send a message to the user at the browser. It could be anything from a kind message like "Your data has been stored successfully in the data base", or a warning about some incorrect data entered in a form, to the real bad news like "Database failure. Please contact support". Since the message is going to be displayed in a browser, it will have to be inserted in some way in a jsp-page.

What if we had to invent a message handling scheme ourselves? A sensible solution could be to put our messages in some kind of Java Collection, maybe a LinkedList:

Listing 1: Using a List to hold messages

List messages = new LinkedList();
messages.add("Data saved successfully");
messages.add("Please continue");
request.setAttribute("messages", messages);

In the jsp-page we could then easily show the messages using Struts tags:

Listing 2: Displaying the messages in a jsp-page

<logic:present name="messages">

<h3>Messages:</h3>

<logic:iterate id="msg" name="messages">
<bean:write name="msg"/><br>
</logic:iterate>

When the jsp-page displays we'll see this:

Messages:

Data saved successfully 
Please continue 

No rocket science in this. However, Struts has a more powerful mechanism for storing and handling messages based upon the ActionMessage and ActionMessages classes. If you're already a Struts user you may know the ActionErrors and ActionError classes. They're almost identical to the Message classes, and most of what I'll explain below for the Message classes works equally well for the Error classes. How we cope with having two different set of classes I'll return to later.

Using ActionMessages

The ActionMessages class resembles the List class we used above in that it is a container for the messages. The messages themselves are held in instances of the ActionMessage class:

Here's a piece of code from an Action class that uses these classes:

Listing 3: Using ActionMessages

ActionMessages messages = new ActionMessages();
ActionMessage msg = new ActionMessage("data.ok");
messages.add("message1", msg);

msg = new ActionMessage("data.continue");
messages.add("message2", msg);

saveMessages(request, messages);

As you can see there are some differences when you compare this to Listing 1:

  1. You don't give the message text directly to ActionMessage. Instead you give a key to the Struts message resource file (also called "the application property file"). I've specified these two lines in this file:
        data.ok=Data saved successfully data.continue=Please
    continue
    It's considered good practice to keep your fixed texts in the message resource file, and it also gives you the possibility to support several languages in your application. 
  2. You give a "label" when you add an ActionMessage. This makes it possible to look up the message and stick it in your HTML wherever you like it. We'll see below how to do this.
    It's possible to have more than one message with a given label.
    If the message is not related to anything special you use the label ActionErrors.GLOBAL_ERROR.

To show all the messages in the browser you add these lines to your jsp-page (note the resemblance with the code from listing 2):

Listing 4: The messages tag in use

<html:messages id="msg" message="true">
<bean:write name="msg"/><br>
</html:messages>

This will give you exactly the same output as above:

Messages:

Data saved successfully 
Please continue 

As you can see the <html:messages> tag loops over all the messages in the ActionMessages class. If you want to pick a single message from the class you specify its label as the property:

<html:messages id="msg2" message="true" property="message1">
<bean:write name="msg2"/><br>
</html:messages>

This makes it possible to place messages, for example, next to input fields in a form.

One more thing about the ActionMessage class: you may specify parameters to the message taken from the message resource file:

msg = new ActionMessage("data.do", "stop");
messages.add("message2", msg);

With "data.do=Please {0}" this will display as "Please stop".

Looking inside the ActionMessage(s) classes

You've seen how to put data inside the ActionMessage and ActionMessages classes, but it's also possible to pull data out again without having to use the Struts tags. You'd want to do this if you had to write your own tags for displaying messages. Here, first, are the methods you'd have to know from the classes:

Class Method Purpose
ActionMessages Iterator properties() Get the "labels" you used when storing the ActionMessage instances
-"- Iterator get(String property) Get the ActionMessage objects 
ActionMessage String getKey() Get the message key for this message
-"- Object[] getValues() Get the replacement values for this message 

You'll also need to know how to find the ActionMessages object. Struts stores it in the request object using the string value "org.apache.struts.action.ACTION_MESSAGE". This value should not be hard coded, but taken from the org.apache.struts.Globals class, which contains most of the keys used for storing various objects. We can also find a pointer to the message resources in the Globals class, and if we want to handle several languages we should also pull out the "locale".

To make it simple here's some code for a jsp-page that peeks into the ActionMessage objects:

Listing 5: The inspectmessages.jsp page

 . .
<%@ page import="java.util.*" %>
<%@ page import="org.apache.struts.*" %>
<%@ page import="org.apache.struts.util.*" %>
<%@ page import="org.apache.struts.action.*" %>

<%
// Print all attributes in the request object
out.println("<p><b>All Attributes in request scope:</b>");
Enumeration paramNames = request.getAttributeNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
Object values = request.getAttribute(name);
out.println("<br> " + name + ":" + values);
}

// Print all attributes in the session object
out.println("<p><b>All Attributes in session scope:</b>");
paramNames = session.getAttributeNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
Object values = session.getAttribute(name);
out.println("<br> " + name + ":" + values);
}

out.println("<p><b>Data in ActionMessages:</b>");

// Get the ActionMessages
Object o = request.getAttribute(Globals.MESSAGE_KEY);
if (o != null) {
ActionMessages ae = (ActionMessages)o;

// Get the locale and message resources bundle
Locale locale =
(Locale)session.getAttribute(Globals.LOCALE_KEY);
MessageResources messages =
(MessageResources)request.getAttribute
(Globals.MESSAGES_KEY);

// Loop thru all the labels in the ActionMessage's
for (Iterator i = ae.properties(); i.hasNext();) {
String property = (String)i.next();
out.println("<br>property " + property + ": ");

// Get all messages for this label
for (Iterator it = ae.get(property); it.hasNext();) {
ActionMessage a = (ActionMessage)it.next();
String key = a.getKey();
Object[] values = a.getValues();
out.println(" [key=" + key +
", message=" +
messages.getMessage(locale,key,values) +
"]");
}
}
}
%>
. . .

As you can see, I also print out the objects in the request and session objects. If we have the same data in the ActionMessages object as in Listing 3 we'll get this output from the jsp-page:

All Attributes in request scope: 
. . . (a few lines left out here) . . .
org.apache.struts.action.MESSAGE:
   org.apache.struts.util.PropertyMessageResources@a336d5 
org.apache.struts.action.ACTION_MESSAGE:
   org.apache.struts.action.ActionMessages@3bedc4 

All Attributes in session scope: 
org.apache.struts.action.LOCALE:en 

Data in ActionMessages: 
property message2: [key=data.continue, message=Please continue] 
property message1: [key=data.ok, message=Data saved successfully] 

The two last lines show the same messages as we got from the code in listing 3. At the top you can also identify the request keys for the ActionMessages object and the message resource bundle.

The old ActionError(s)

If you've been using Struts before version 1.1 you probably know the ActionErrors and ActionError classes and the corresponding <html:errors> tag. The new ActionMessages and ActionMessage classes and <html:messages> tag are meant to replace these. The reason is that the <html:errors> tag forced you to place HTML in the message resource file, which is not nice. This is not necessary with the new <html:messages> tag, since it sets up a loop where you may easily stick in the HTML you want. The name ActionError(s) is also misleading, since they're not only supposed to hold an error message, but, rather, any message.

Struts, however, maintains two separate queues: one for ActionErrors and one for ActionMessages. They're both held in the request object:

Collection Queue name in request object Saved by
ActionErrors Globals.ERROR_KEY =
"org.apache.struts.action.
ERROR"
saveErrors
(HttpServletRequest request, ActionErrors errors)
ActionMessages Globals.MESSAGE_KEY =
"org.apache.struts.action.
ACTION_MESSAGE"
saveMessages
(HttpServletRequest request, ActionMessages messages)

The plan is that in some coming Struts release--maybe 1.2--the ActionError(s) classes will be deprecated. My advice is therefore that you use the new ActionMessage(s) classes. There is one issue however in doing this. The Validator that was introduced in Struts 1.1 still uses the ActionErrors class. The same goes for the validate method in the ActionForm class. 

To display messages from the ActionErrors object you may use this tag:

<html:messages id="msg" message="false">
<bean:write name="msg"/><br>
</html:messages>

It's optional to specify message="false".

Earlier we saw that messages from the ActionMessages object are displayed like this:

<html:messages id="msg" message="true">
<bean:write name="msg"/><br>

Exception handling in Struts

Let me start by defining what I mean by "exception handling". It's how you handle serious errors in your program. So it's not something like forms validation errors, but more like errors that shouldn't occur with a properly set up environment. It could be a database access that fails because you've supplied an incorrect password. In Java such errors are typically handled by throwing an exception, but they could of course be handled in other ways, for example by having a method returning parameters that describe the error situation.

If a method throws an exception it's up to the receiver to determine what to do about it. It's common to re-throw the exception, but if the error can be repaired partly or completely, then the whole situation could end up in a warning message sent to the user. How messages are sent we know from the previous part of my article.

We'll first see how Struts, per default, handles exceptions and then continue by looking at how exception handling can be configured through the struts-config file. As before our point of view is the Action class, and we'll first make a small test set-up to see what throwing an exception will cause.

We'll need a jsp-page like this:

Listing 6: The tryexception.jsp page

<%@ page language="java" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <logic:messagesPresent> <h3>Errors:</h3> <html:messages id="emsg"> <bean:write name="emsg"/><br> </html:messages> </logic:messagesPresent> <html:form action="tryexception.do"> <html:text property="id"/> <br> <html:submit value="Submit"/> </html:form>

In the form field we can type the name of an exception we'd like to throw from the Action class. We've prepared the page to show a list of errors if present. 

In the struts-config file we define this:

Listing 7: The struts-config.xml file

<form-beans> <form-bean name="tryexceptionform" type="org.apache.struts.action.DynaValidatorForm"> <form-property name="id" type="java.lang.String"/> </form-bean> </form-beans> <action-mappings> <action path="/tryexception" name="tryexceptionform" scope="request" input="/tryexception.jsp" type="hansen.playground.TryException"> <forward name="OK" path="/tryexception.jsp"/> <forward name="failure" path="/error.jsp"/> </action> </action-mappings>

The TryException Action class is this:

Listing 8: The TryException class

package hansen.playground; import java.io.IOException; import javax.servlet.http.*; import org.apache.struts.action.*; public final class TryException extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { DynaValidatorForm f = (DynaValidatorForm) form; String id = (String) f.get("id"); if (id.equals("Exception")) throw new Exception("my Exception"); if (id.equals("RuntimeException")) throw new RuntimeException("my RuntimeException"); // Back to same page return (mapping.findForward("OK")); } }

It retrieves the id of the exception to be thrown and if it's one of the ones listed it will be thrown. If not, we simply return to the jsp-page. As you can see both checked and unchecked exceptions are in the list. If we enter "Exception" in the form and submit it, we'll see something like this in the browser (I've used Tomcat 4.1.12 for the examples):

HTTP Status 500 -


type Exception report
message
 
description
The server encountered an internal error () that prevented it from fulfilling this request.
exception

javax.servlet.ServletException: my Exception
        at org.apache.struts.action.RequestProcessor.processException
                   (RequestProcessor.java:541)
. . . (traceback continues) . . .

It's the same picture if you try the other exceptions. Not very nice, is it?

Improving exception handling

Let's set up a more realistic situation to show how you can handle an exception in a better way, first without using Struts 1.1 features.

In the TryException class we'll call two methods tryChecked1 and tryChecked2, which throws two checked exceptions called MyException1 and MyException2. I don't show them here because they're simple extensions of the base Exception class. For simplicity we put the two methods in the TryException class. The first exception we convert to a message and the other one we redirect to an error page:

Listing 9: The TryException class - extended (1)

. . .
if (id.equals("tryChecked1"))
try {
tryChecked1("mydata");
} catch (MyException1 e) {
// Insert some logging code here...
ActionErrors errors = new ActionErrors();
errors.add("error", new ActionError("errors.exception"));
saveErrors(request, errors);
// Return to same page
return (new ActionForward(mapping.getInput()));
}

if (id.equals("tryChecked2"))
try {
tryChecked2("mydata");
} catch (MyException2 e) {
// Insert some logging code here...
// Save the exception for the error page
request.setAttribute("MYEXCEPTION",e);
// Return to error page
return (mapping.findForward("failure"));
}

// Back to same page
return (mapping.findForward("OK"));
}

private void tryChecked1(String s) throws MyException1 {
throw new MyException1("my Exception 1");
}

private void tryChecked2(String s) throws MyException2 {
throw new MyException2("my Exception 2");
}
. . .

First we enter "tryChecked1" in the tryexception.jsp page. The result is this:

As you can see we have defined this message in the message resource file: "errors.exception=A problem was repaired". If the exception isn't really a serious error, and we can do something sensible about it, then converting the exception to a message like this is a perfectly valid solution.

Before we try the "tryChecked2" case we'll create an error.jsp page. The first version of it simply lists all objects in request and session scope, takes the stored exception and prints it:

Listing 10: An error jsp page

<%@ page language="java" %>

<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.struts.*" %>

<h3>In error.jsp</h3>

<%

out.println("<p><b>All Attributes in request scope:</b>");

Enumeration paramNames = request.getAttributeNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
Object values = request.getAttribute(name);
out.println("<br> " + name + ":" + values);
}

out.println("<p><b>All Attributes in session scope:</b>");

paramNames = session.getAttributeNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
Object values = session.getAttribute(name);
out.println("<br> " + name + ":" + values);
}

Object o = request.getAttribute("MYEXCEPTION");
if (o != null) {
Throwable t = (Throwable)o;
out.println("<h3>An Exception was thrown:</h3>" + t);

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.flush();
String trace = sw.toString();
out.println("<pre>"+trace+"</pre>");
}

Declarative Exception Handling

Struts 1.1 offers a new way to handle exceptions: Declarative Exception Handling. Instead of coding the exception handling in the Action class, we can now specify in struts-config how a specific exception should be treated. In its simplest form we only have to enter these lines in struts-config:

Listing 12: Declaring an exception in struts config

<global-exceptions> <exception type="hansen.playground.MyException2" key ="errors.exception2" path="/error.jsp"/> </global-exceptions>

The interpretation of this is that if MyException2 is caught by Struts' ActionServlet then it should redirect to error.jsp. The key is as usual a pointer to the message resource file. This makes coding in the Action class very simple--see for yourself when we add yet another test:

if (id.equals("tryDEH")) tryChecked2("mydata");

Since the execute method declares throws Exception we don't need a try-catch block. If we enter "tryDEH" in the jsp-page we'll receive this answer:

In error.jsp

All Attributes in request scope: 
. . .  (a few lines left out here) . . .
org.apache.struts.action.EXCEPTION:hansen.playground.MyException2: my Exception 2

All Attributes in session scope: 
org.apache.struts.action.LOCALE:en

We can see that Struts has saved the exception using one of its global constants. You may use the field Globals.EXCEPTION_KEY to retrieve it from the request object, so we make a small modification to error.jsp to handle our own and now also Struts' exception:

. . . Object o = request.getAttribute("MYEXCEPTION"); if (o == null) o = request.getAttribute(Globals.EXCEPTION_KEY); if (o != null) { Throwable t = (Throwable)o; . . .

This gives us the same traceback as in Listing 11.

The exception element in the struts-config file has these attributes:

Table 1: The attributes of the exception element

Attribute Meaning
bundle Servlet context attribute for the message resources bundle
associated with this handler. The default attribute is the
value specified by the string constant declared at
Globals.MESSAGES_KEY.
[org.apache.struts.Globals.MESSAGES_KEY]
className The configuration bean for this ExceptionHandler object.
If specified, className must be a subclass of the default
configuration bean
["org.apache.struts.config.ExceptionConfig"]
handler Fully qualified Java class name for this exception handler.
["org.apache.struts.action.ExceptionHandler"]
key The key to use with this handler's message resource bundle
that will retrieve the error message template for this
exception.
path The module-relative URI to the resource that will complete
the request/response if this exception occurs.
scope The context ("request" or "session") that is used to access
the ActionError object [org.apache.struts.action.ActionError]
for this exception.
type Fully qualified Java class name of the exception type to
register with this handler.

The most interesting attribute, besides the ones we've already seen, is the handler attribute. It gives us the possibility to write a class to handle the exception. This is necessary when we, for example, will write the exception to a log file or another persistent media. It's also necessary since we want to move Java code away from error.jsp. The exception handler must extend the org.apache.struts.action.ExceptionHandler class--see the JavaDoc here. When you extend it you should override the execute method, which has this signature:

Listing 13: The signature of the execute method in an Exception Handler

public ActionForward execute(java.lang.Exception ex, ExceptionConfig ae, ActionMapping mapping, ActionForm formInstance, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException

You get the exception as the first parameter, the data (as a bean) from the exception-element in struts-config as the second parameter, and finally the parameters you already know from the Action classes.

Here's a simple ExceptionHandler that writes the exception to the console (which is only useful during development), and forwards to the jsp-page given in struts- config.

Listing 14: An extension of an ExceptionHandler

package hansen.playground; import javax.servlet.*; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.config.*; public class MyHandler extends ExceptionHandler { public ActionForward execute( Exception ex, ExceptionConfig ae, ActionMapping mapping, ActionForm formInstance, HttpServletRequest request, HttpServletResponse response) throws ServletException { ex.printStackTrace(); return new ActionForward(ae.getPath()); } }

The entry in struts-config is this:

<exception type="hansen.playground.MyException1" key ="errors.exception1" handler="hansen.playground.MyHandler" path="/error.jsp"/>

To test it we add these lines to the Action class:

if (id.equals("tryDEH1")) tryChecked1("mydata");

If you want to code your own general ExceptionHandler you should consider adding these features:

  1. write the exception to a log (e.g. implemented through Log4J or the JDK1.4 Logging API)
  2. write as much useful data as possible to the log. If you have the userid or other user information then it could be relevant to log it so you can match the error to the user who encountered the error.
    You might have other useful information stored in session or request scope that could be written to the log.
  3. you might want to inform of the error through other channels, e.g. e-mail. Just be sure to make your set-up robust, so you don't provoke a new exception!
  4. finish by going to e.g. the error.jsp page, where you can inform the user of the error, and how he or she can proceed.

Finally I'll mention that it's also possible to add an exception handler to a specific action--e.g.:

Listing 15: Declaring a local exception handler

<action path="/tryexception" . . . <exception type="hansen.playground.MyException1" key ="errors.exception1" handler="hansen.playground.MyHandler" path="/error.jsp"/> </action>

Recap (2)

Declaring exceptions in struts-config makes coding in the Action class much simpler. Instead of having almost the same code replicated in every Action class you can now move this code to the ExceptionHandler.

Using chained exceptions

An exception typically occurs in a method in a "calling chain" initiated by the Action class. To get a realistic snapshot of the error it's very valuable if each method in this chain can add data for the logging mechanism. By using chained exceptions this is easily accomplished. Here's an example where the Action class calls a method in ClassA which calls a method in ClassB, where the exception is thrown:

Listing 16: Using chained exceptions

In the Action class: 
. . .
if (id.equals("tryDEH2"))
new ClassA().methodA("mydata");
. . .

In ClassA:
. . .
public void methodA(String s) throws MyException1 {
try {
new ClassB().methodB(s);
} catch (MyException1 e) {
throw new MyException1
("ClassA.methodA: important data . . .",e);
}
}
. . .
In Class B:
. . .
public void methodB(String s) throws MyException1 {
throw new MyException1
("ClassB.methodB: important data . . .");
}
. . .

If we simply print the Java traceback we get something like this:

hansen.playground.MyException2: ClassA.methodA: 
important data . . .
at hansen.playground.ClassA.methodA(ClassA.java:9)
at hansen.playground.TryException.execute(TryException.java:63)
. . .(several lines omitted) . . .
at java.lang.Thread.run(Thread.java:536)
Caused by: hansen.playground.MyException2: ClassB.methodB:
important data . . .
at hansen.playground.ClassB.methodB(ClassB.java:6)
at hansen.playground.ClassA.methodA(ClassA.java:7)

... 36 more

The formatting is not very nice, but the important thing is that we get the application data written out. If you're interested in more information on how to best use chained exception, and how you produce a nicer traceback, I recommend reading the article "Using Chained Exceptions in JDK 1.4".

Conclusion

Before you start coding a new Struts application it's important to decide how messages, errors and exception handling are implemented. In a large development team it's very important to have these decisions in place and documented before coding starts.  The examples and ideas presented in this article will hopefully help you simplify the decision process.

Resources




%>

When we enter "tryChecked2" we get this page back:

Listing 11: Traceback in error.jsp

In error.jsp

All Attributes in request scope: 
. . .  (a few lines left out here) . . .
MYEXCEPTION:hansen.playground.MyException2: my Exception 2 

All Attributes in session scope: 
org.apache.struts.action.LOCALE:en

An Exception was thrown:

hansen.playground.MyException2: my Exception 2 

hansen.playground.MyException2: my Exception 2
at hansen.playground.TryException.tryChecked2(TryException.java:71)
at hansen.playground.TryException.execute(TryException.java:50)
. . . (traceback continues) . . .

This page is of course only good for experimentation and testing. In a real production system you'd have to give a friendly message, format it nicely using your style sheets, and tell the user how to proceed. It's a matter of personal choice if you'll also include the raw traceback in the page. The traceback and all other useful information will have to be written to a log file or some other media, to be used by the support people. But including the traceback in the page has at least two advantages:

  1. the user may phone right away to the help desk and pass (by voice or mail) the technical information. Trained support people might recognize the error right away, and thus give immediate help to the user.
  2. if the user has seen the error before and have had some advice of what to do in the situation, she might be able to work around the problem once more.


</html:messages>

My best advice--until version 1.2 comes out and clarifies things--is this: use ActionErrors for validations, and ActionMessages for all other messages. For showing all the messages you'll unfortunately have to use both of the tags above.

Recap (1)

To have a message displayed in the browser, you first use the ActionMessage(s) classes for storing message data. You typically do this in the Action class. In the jsp-page you use the <html:message> tag to display one or more of the messages.

No comments:

Google+