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 by Phil Selby