Struts Validation

A potential boon for Struts developers is the validator component. Documentation is sparse on this facility so to save anyone else from the frustration I've encountered, what follows are instructions on how to perform a very simple validation of a text string in a form.

Error messages

Error messages are generated automatically by the validator and controlled by the properties file. If you want to display all the errors in a single block then you can put something like this in your properties file:

    errors.header=<h3>The following errors were detected:</h3><ul>
    errors.prefix=<li>
    errors.suffix=<br>
    errors.footer=</ul>
and use <html:errors/> in your JSP. If you want to associate errors with the field which caused them then use something like this:
    errors.prefix=<font size="-1" color="red">
    errors.suffix=</font>
and use <html:errors property="fieldname"/> where fieldname is as defined in the JSP and mapped in the validate.xml file. If you're using a table (common for forms) then you can create table elements either above, below or to the right of the offending fields and populate appropriately with the <html:errors> tag. You can even mix and match! In my login example, I need to confirm that the username/password combination is valid. If not then I can generate an error like this (in the action):
    ActionErrors    errs = new ActionErrors();
    errs.add( ActionErrors.GLOBAL_ERROR, new ActionError( "login.unknown" ) );
    saveErrors( req, errs );
Of course I have to add the login.unknown message to my properties file. I can then pull out the global errors using code like this in the JSP:
<logic:messagesPresent property="org.apache.struts.action.GLOBAL_ERROR">
<h3>The following error(s) occurred:</h3>
<font color="red">
<ul>
<html:messages property="org.apache.struts.action.GLOBAL_ERROR" id="message">
<li><bean:write name="message"/><br>
</html:messages>
</ul>
</font>
</logic:messagesPresent>
Note the use of the messages tag: it permits me to iterate through the messages rather than getting them all at once as with the errors tag. Here's how I could display the message to the right of the field to which it applied:
<table>
<tr>
<th align="right">Username:</th>
<td align="left"><html:text property="username" maxlength="12"/></td>
<td><html:errors property="username"/></td>
</tr>
</table>
Note that I don't have to worry about multiple errors in a field: once validation fails for any reason then no further tests are performed. † Finally, here are the definitions from the properties file:
errors.prefix=<font color="red">
errors.suffix=</font>

Extending Validator Classes

The login example mentioned earlier would require overriding the validate method of the validator. This is not an uncommon situation as you might want to have basic validation performed by Struts and business validation performed as well. You can enjoy the best of both worlds by extending the validation class. Here's some code which does just that:

package com.yourcompany;

import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.validator.ValidatorForm;

public class OptionTestForm extends ValidatorForm {

	private String	ccnum;	// private variable

	/*
	 * get the credit card number
	 */

	public String getCcnum() {
		return( ccnum );
	}

	/*
	 * set the credit card number
	 */

	public void setCcnum( String ccnum ) {
		this.ccnum = ccnum;
	}

	/*
	 * reset form variables
	 */

	public void reset(ActionMapping mapping, HttpServletRequest req ) {
		ccnum = null;
	}

	/*
	 * perform validation
	 */

	public ActionErrors validate( ActionMapping mapping,
	  HttpServletRequest req ) {
		ActionErrors	result = super.validate( mapping, req );
		if( result != null ) {
			Iterator	iter = result.get( "ccnum" );
			if( ( iter != null ) && iter.hasNext() )
				return( result );
		}

		String		issuer = CreditCard.getCardIssuer( ccnum );
		if( issuer == null ) {
			if( result == null )
				result = new ActionErrors();
			result.add( "ccnum", new ActionError( "error.ccinvalid" ) );
		}
		else if( ! issuer.equals( "MC" ) && ! issuer.equals( "VISA" ) ) {
			if( result == null )
				result = new ActionErrors();
			result.add( "ccnum", new ActionError( "error.ccwrongtype" ) );
		}
		return( result );
	}
}
The first thing we do in the validate method is call the superclass method. We then check to see whether any errors were created for the ccnum field. It doesn't make sense to check the credit card number validity if other validations (like minimum length) failed. We add any errors to the ActionErrors (being careful to create it if it doesn't already exist) and return to the caller.

Action Forms

Someone was asking about the difference between the DynaValidatorForm and the DynaValidatorActionForm. A couple of examples should illustrate the differences.

Example of using DynaValidatorForm:

[validator.xml]

<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">
<form-validation>
        <formset>
                <form name="loginForm">
.....
[struts-config.xml]
<?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>
        <form-beans>
                <form-bean name="loginForm"
                  type="org.apache.struts.validator.DynaValidatorForm">
.....

When using DynaValidatorActionForm:

[validator.xml]

<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">
<form-validation>
        <formset>
                <form name="/login">
.....
[struts-config.xml]
<?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>
        <form-beans>
                <form-bean name="loginForm"
                  type="org.apache.struts.validator.DynaValidatorActionForm">
                        <form-property name="username" type="java.lang.String"/>
                </form-bean>    
        </form-beans>
        <action-mappings>
                <action name="loginForm"
                  path="/login"
.....
So in the first case it's looking for a form-bean with a name attribute of loginForm. In the second it's looking for an action with a path of /login. The idea was that multiple actions could conceivably be using the same form and so you'd have more granularity if you could validate based on the action rather than the form.

Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby. All rights reserved.