Friday, November 13, 2009

Recipe 9.6. Formatting Error Messages










Recipe 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>






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.




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.






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 separatenot commingledata 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.




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.












    No comments: