Having spent many hours banging my head against the wall, I've finally figured out how to use the validator component. To save anyone else the frustration I've encountered, what follows is how to perform a very simple (hah!) validation of a text string in a form.
<html:form action="/login.do" focus="username"> <table> <tr> <th align="right">Username:</th> <td align="left"><html:text property="username" maxlength="12"/></td> </tr> </table> <html:submit/> </html:form> |
<?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">
<form-property name="username" type="java.lang.String"/>
</form-bean>
</form-beans>
<global-forwards>
<forward name="Login" path="/login.jsp"/>
</global-forwards>
<action-mappings>
<action name="loginForm"
path="/login"
type="com.yourcompany.LoginAction"
scope="request"
validate="true"
input="/login.jsp"/>
</action-mappings>
<message-resources parameter="ApplicationResources" null="false"/>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validator.xml"/>
</plug-in>
</struts-config>
|
<!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">
<field property="username" depends="required,minlength">
<arg0 key="username" resource="false"/>
<arg1 key="${var:minlength}" resource="false"/>
<var>
<var-name>minlength</var-name>
<var-value>2</var-value>
</var>
</field>
</form>
</formset>
</form-validation>
|
I've used a couple of shortcuts here. You would normally include the validator name in the argument definitions, i.e. <arg0 name="minlength" .... Since I'm using the same text for the fieldname in both the required and minlength validators, it's not necessary to qualify arg0. I further don't need to qualify arg1 since it's not used by the required validator, only the minlength one. Note the key specification for arg1: it uses the variable value assigned to minlength. This construct precludes duplicating the value and eliminates the potential for duplicated values to get out of sync.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app id="TRS">
<servlet>
<servlet-name>TRS</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TRS</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<taglib>
<taglib-uri>bean</taglib-uri>
<taglib-location>/WEB-INF/lib/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>html</taglib-uri>
<taglib-location>/WEB-INF/lib/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>logic</taglib-uri>
<taglib-location>/WEB-INF/lib/struts-logic.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>tiles</taglib-uri>
<taglib-location>/WEB-INF/lib/struts-tiles.tld</taglib-location>
</taglib>
</web-app>
|
It's important to recognize that in a development situation you are probably going to be making numerous changes over time. If you recall that the validator is loaded at startup then you can see why you need to restart Tomcat every time you make a change to validation.xml. That's why I recommend using a separate Tomcat instance (running stand-alone) while developing your application. It also hides what you're doing until you're ready to release it.
There's another area where I believe that the book failed to take advantage of the capabilities of Tomcat. The taglib examples always looked like this:
<taglib>
<taglib-uri>/WEB-INF/lib/struts-tiles.tld</taglib-uri>
<taglib-location>/WEB-INF/lib/struts-tiles.tld</taglib-location>
</taglib>
|
That requires the following in the JSP:
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> |
<%@ taglib uri="tiles" prefix="tiles" %> |
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> |
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.
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. As I read it, 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