Ensuring that users enter data correctly can make the difference between a production-ready application and one relegated to remain in the Quality Assurance department. Web applications need to verify the data entered is valid. Data validation takes two forms: syntactic and semantic. Syntactic validity indicates the data is in the correct format; for example, making sure that a numeric field contains only numeric digits. Semantic validity indicates the data means what it should in the functional context of the application. For example, if a user enters his or her birthdate, that date can’t be in the future.
The recipes in this chapter cover some advanced uses of the Struts
Validator. Some of the recipes use new features of the Validator such
as the validwhen
validator. A
validator is
a reusable type of validation. Several recipes show you how easy it
is to create your own validators. Providing locale-specific
validators to support internationalization is covered. If
you’re familiar with the Validator, skip ahead to
the particular recipes that interest you.
Struts was designed from the beginning to support validation. Every
Struts ActionForm
has a
validate()
method.
If a validation check requires access to a back-end business service,
like an EJB or the database, place the call to the business service
in the Action
that processes the form and not the
ActionForm
itself.
You can add your own data validation checks for form data in this
method. The method returns an
ActionErrors
object, a
collection of ActionError
objects.
In Struts 1.2, the
ActionError
class has
been deprecated and replaced by the
ActionMessage
class. The
ActionErrors
class, however,
isn’t deprecated. In Struts 1.2,
ActionErrors
contain a collection of
ActionMessage
objects. See Recipe 9.5 for more details.
If your
validate( )
method
returns an ActionErrors
object with one or more
errors, then the validation check
fails. Struts routes control back to the page
set as the value for the input
attribute of the
action
element in the
struts-config.xml. Example 8-1
shows a simple ActionForm
that validates that the
username
and password
fields
each have a non-blank value; in other words, they are required.
package com.oreilly.strutsckbk.ch08; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; public class MyLoginForm extends ActionForm { public String getPassword( ) { return password; } public void setPassword(String password) { this.password = password; } public String getUsername( ) { return username; } public void setUsername(String username) { this.username = username; } public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors( ); if (username == null || "".equals(username)) { errors.add("username", new ActionError("errors.required","The username")); } if (password == null || "".equals(password)) { errors.add("password", new ActionError("errors.required","The password")); } return errors; } private String username; private String password; }
Basic syntax checks, such as those shown in Example 8-1, comprise the vast majority of validation needs. Coding each one of these checks in Java can be tedious and repetitive. Thankfully, the Struts Validator, available to Struts applications since Struts 0.5, has been developed under the Jakarta Commons project.
Struts provides an integration layer with the Validator, which allows you to define validations for form fields in an XML file. You use the Validator for JavaScript-based client-side checks as well as server-side ones. You can write your own validators (see Recipe 8.7); however, the Validator bundles a full-featured set of validators, as shown in Table 8-1 .
|
Checks that a field value is non- |
|
Only available for Struts 1.1. This validator allows one field to be
specified as required if another field is null, not null, or equal to
a specified value. This validator is deprecated, in favor of
|
|
Designed to replace |
|
Checks that the number of characters in the field value is greater than or equal to a specified minimum. |
|
Checks that the number of characters in the field value is less than or equal to a specified maximum. |
|
Validates the field value using a regular expression to determine a match. If the value matches the regular expression, the field is valid. |
|
Checks that the field value is a valid byte value. |
|
Checks that the field value is a valid short integer value. |
|
Checks that the field value is a valid integer value. |
|
Checks that the field value is a valid long value. |
|
Checks that the field value is a valid floating-point value. |
|
Checks that the field value is a valid double value. |
|
Checks that the field value matches a specified date format pattern
(e.g., |
|
Checks that the field value is within a specified numeric range. This
valdiator has been deprecated in favor of the type-specific range
checks ( |
|
Checks that the field value is within a range bounded by two
|
|
Checks that the field value is within a range bounded by two
|
|
Checks that the field value is within a range bounded by two
|
|
Verifies that the format of the field value is valid for a credit
card number. This validator is convenient to use instead of using
|
|
Verifies that the format of the field value is valid for an
electronic mail address (e.g., |
|
Verifies that the value entered is a valid uniform resource locator. Use this validator when you want to validate an input web location or hyperlink value. |
The most up-to-date listing of the bundled validators can be found at http://struts.apache.org/userGuide/dev_validator.html#builtin.
To use the Validator, first specify a plug-in
element in your struts-config.xml file:
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in>
The pathnames
property value specifies the
Validator configuration files you’re using.
Typically, you specify one or more files here. The first file,
validator-rules.xml, defines and declares the
pluggable validators, listed in Table 8-1, which
are bundled with the Struts distribution. The second file,
validation.xml, declares how the validators are
applied to your application.
A good approach is to have separate validation XML files for each major functional area of your application. For a large application, you might have a dozen of these files. The Validator supports multiple validation documents in the same manner that Struts supports struts-config documents (see Recipe 2.4).
To use the Validator with a
hand-coded
ActionForm
, the ActionForm
must
extend org.apache.struts.action.ValidatorForm
(or
one of its subclasses). For a dynamic action form, the type must be
org.apache.struts.action.DynaValidatorForm
(or one
of its subclasses). You specify how the validators apply to the form
in the validation.xml file. Example 8-2 shows a portion of a
validation.xml file that validates that the
username
and password
are
required on the form named MyLoginForm
. This
Validator form establishes minimum and maximum lengths for the
password
field.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <formset> <form name="MyLoginForm"> <field property="username" depends="required"> <arg key="prompt.username"/> </field> <field property="password" depends="required, minlength,maxlength"> <arg key="prompt.password"/> <arg key="${var:minlength}" name="minlength" resource="false"/> <arg key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>16</var-value> </var> <var> <var-name>minlength</var-name> <var-value>3</var-value> </var> </field> </form> <formset> <form-validation>
The value of the name
attribute specified in the
form
element is the name of the form from the
form-bean
element in the
struts-config.xml file and not the type (class
name) of the form.
You might think of a validation set as applying to all the fields of
an
ActionForm
, and you can
tie the validation set to an action path instead
of the form name. This feature is referred to as
action-based
or path-based validation. The Validator applies
this feature if the form subclasses
ValidatorActionForm
or
DynaValidatorActionForm
. When such a form is
submitted to an Action
, only the Validator
form
element, in which the name
matches the path, applies, and only those fields
specified in that validation set are checked. In this snippet from a
validation.xml file, the
username
will be validated when the form is
submitted to the action path /ProcessStep1:
<form name="/ProcessStep1"> <field property="username" depends="required"> <arg key="prompt.username"/> </field> </form> <form name="/ProcessStep2> <field property="password" depends="required"> <arg key="prompt.password"/> </field> </form>
Validation is enabled for an
action mapping by setting the
action
element’s
validate
attribute to true
. If
validation fails, Struts forwards to the resource identified by the
input
attribute. The value of the
input
attribute represents the path to the
resource, typically a JSP page, to forward control so the user can
reinput data.
<action path="/Login" type="com.oreilly.strutsckbk.ch08.LoginAction" scope="request" name="MyLoginForm" validate="true" input="/login.jsp"> <forward name="success" path="/login_success.jsp"/> </action>
You can set the inputForward
attribute to
true
on the controller
element
in a Struts configuration file so Struts will treat the value of the
input
attribute as the name of a local or global
forward (see Recipe 7.4).
You’re ready to apply validation across your application! Once you get in the groove of configuring validations and verifying that it all works as desired, you will be well on your way to creating a web application that will be robust enough to support the demands of those pesky creatures known as users.
Like most application features driven by XML configuration files,
typographical mistakes may cause silent failures in your application
that can be hard to diagnose and debug. If you find your application
misbehaving after setting up the validations, you can turn off the
validation by setting validate="false
" in the
action
mapping.
If everything works correctly down
this “happy path,” then you know
that something is amiss with your validation setup.
For more of the basics on using the Validator, check out Programming Jakarta Struts by Chuck Cavaness (O’Reilly).
You want to define, in one place, a common value you can reference wherever needed in a Validator form.
Define the value as a global or form-set constant in one of your validation documents, as shown in Example 8-3.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <global> <constant> <constant-name>globalVarName
</constant-name> <constant-value>globalVarValue
</constant-value> </constant> </global> <formset> <constant> <constant-name>formsetVarName
</constant-name> <constant-value>formsetVarValue
</constant-value> </constant> <form name="MyForm"> <field property="myfield" depends="someRule,anotherRule"> <var> <var-name>someRule</var-name> <var-value>${globalVarName
}</var-value> </var> <var> <var-name>anotherRule</var-name> <var-value>${formsetVarName
}</var-value> </var> </field> </form> <formset> <form-validation>
Good developers understand the
Don’t Repeat Yourself
(DRY) principle of software engineering. But if they fail to follow
this principle, they could end up all wet. The Validator embraces
this guideline, making it an excellent choice for input validation.
If you have an attribute value shared by multiple
field
elements, you can define the value as a
named constant. You can reference the constant value by name wherever
an attribute value can be used.
If the value applies across the entire application, define it as a
global constant. If the value only applies to a
specific form and it’s used multiple times on that
form, define it as a formset constant. In Example 8-4, the minimum length for the username is
defined as a global constant. For the
RegistrationForm
, the maximum length for the first
and last name is defined as a formset constant.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <global> <constant> <constant-name>usernameMinLength</constant-name> <constant-value>7</constant-value> </constant> </global> <formset> <constant> <constant-name>nameMaxLength</constant-name> <constant-value>40</constant-value> </constant> <form name="RegistrationForm"> <field property="username" depends="required,minlength"> <arg key="prompt.username"/> <arg key="${var:minlength}" name="minlength" resource="false"/> <var> <var-name>minlength</var-name> <var-value>${usernameMinLength}</var-value> </var> </field> <field property="firstName" depends="required,maxlength"> <arg key="prompt.firstName"/> <arg key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>maxlength</var-name> <var-value>${nameMaxLength}</var-value> </var> </field> <field property="lastName" depends="required,minlength,maxlength"> <arg key="prompt.lastName"/> <arg key="${var:minlength}" name="minlength" resource="false"/> <arg key="${var:maxlength}" name="maxlength" resource="false"/> <var> <var-name>minlength</var-name> <var-value>2</var-value> </var> <var> <var-name>maxlength</var-name> <var-value>${nameMaxLength}</var-value> </var> </field> </form> <formset> <formset-validation>
Validator constants are handy for those fields you use throughout
your application. If you use the mask
validator to
check fields such as Social Security Numbers (SSNs) and phone
numbers, using a Validator constant means you only have to maintain
the regular expression in one place.
The mask
validator is discussed in Recipe 8.2.
You want to validate data using a regular expression.
Use the
mask
validation
type provided by the Validator:
<form name="ValidationTestForm"> <!-- Validate Social Security Number --> <field property="ssn" depends="required,mask"> <arg key="prompt.ssn"/> <var> <var-name>mask</var-name> <var-value>^[0-9]{3}-[0-9]{2}-[0-9]{4}$</var-value> </var> </field> </form>
A regular expression uses a general pattern notation that can be used to describe and parse text. Regular expressions have been around in one form or another since the 1960s. Using regular expressions, you can validate that a user’s input matches a specific pattern. The pattern, simple or complex, is specified as a mask value that uses the regular expression pattern language. Without regular expressions, you would have to write a significant amount of custom code. Despite the power of regular expressions, many developers aren’t comfortable using them. If you don’t know regular expressions, learn them now because it could change your life. A good place to start is Mastering Regular Expressions by Jeffrey E. F. Friedl (O’Reilly).
The Validator supports the use of regular expressions through the
mask
bundled
validator. A validation passes if the field value matches a given
regular expression. You specify the regular expression through a
Validator variable, defined in a var
element. The
name of the variable must be mask
, and the value
is the regular expression to use. If the field value matches the
pattern, the validation passes; otherwise, the validation fails.
<var> <var-name>mask</var-name> <var-value>^[0-9]{3}-[0-9]{2}-[0-9]{4}$</var-value> </var>
If you are using
Struts 1.1 or later, the regular
expression must start with a caret (^
) and end
with the dollar sign ($
). This restriction
wasn’t in place prior to Struts 1.1. Its inclusion,
however, makes complete sense in the context of regular expressions.
The caret notates the start of the string, and the dollar sign the
end of the string. Supplying these values indicates you are
validating the entire input string.
Regular expressions can look downright cryptic, but they are not that
hard to understand once you get the basic notation. The expression
shown in the Solution describes a pattern for validating an SSN. An
SSN contains three digits, a hyphen, two digits, another hyphen, and
four more digits. Valid values would be
123-45-6789
,
000-00-0000
, but not
123-84-Ab45
or
12-12-1234
.
The best way to understand a regular expression is to put it into words. Here’s how this would be done for the SSN expression. First, here’s the expression:
^[0-9]{3}-[0-9]{2}-[0-9]{4}$
This expression would be interpreted as the following:
The beginning of the text (
^
), followed by three characters ({3}
) each between 0 and 9 ([0-9]
), followed by a hyphen (-
), followed by two characters ({2}
) each between 0 and 9 ([0-9]
), followed by a hyphen (-
), followed by four characters ({4}
) each between 0 and 9 ([0-9]
), followed by the end of the text ($
).
The [0-9]
is known as a character
class. There are character classes for lowercase and
uppercase alphabetic characters, numbers, and other character types.
The {3}
syntax is known as a
quantifier. It specifies the quantity of the
preceding character (or parenthesized character group). Additional
quantifiers include *
(0 or more),
+
(1 or more), and ?
(0 or 1,
i.e. optional).
Several shorthand notations exist for certain predefined character
classes. For example, d
represents a digit and
equates to using [0-9]
. Knowing this, the SSN
expression can be rewritten as:
^d{3}-d{2}-d{4}$
When used by the Validator, a regular expression specified by the
mask
validator does replace the basic
required
validator. If you were to remove required
from the
Solution and no data was input, the form would pass validation though
the mask
validator was in place. At first glance
this seems wrong; however, it works well in practice. If a field
needs to be required, use required
. Then, if the
data needs to follow a certain text pattern, you can apply a regular
expression validation using mask
. This allows you
to use mask
on optional fields, and the mask only
gets applied when the field has a non-empty value.
Regular expressions are powerful but important; it would be a disservice to cover the syntax and nuances of these expressions here. A better option is to consult a print or online reference.
Mastering Regular Expressions remains an essential tutorial and reference for regular expressions. This book has been recently updated to include, among other things, information relating to Java-based use of regular expressions. If you are new to regular expressions, this book provides a great start.
If you are familiar with regular expressions, Regular Expression Pocket Reference by Tony Stubblebine (O’Reilly) is a handy reference for the advanced programmer.
Steve Ramsay has put together a concise explanation of regular expressions at http://etext.lib.virginia.edu/helpsheets/regex.html.
The Validator uses the regular expression engine of the Jakarta ORO project (http://jakarta.apache.org/commons/oro). The Jakarta ORO JavaDocs provide an overview of the supported Perl 5-compliant regular expression syntax and can be found at http://jakarta.apache.org/oro/api/org/apache/oro/text/regex/package-summary.html.
You are using Struts 1.1 and you want to validate a field based on the value of another related field.
Use the
requiredif
validator. The field
element, in this snippet from
a validation.xml file, indicates that the
zipCode
field is required if the
city
or state
field is
null
:
<!-- zipCode is required if city is null or state is null --> <field property="zipCode" depends="requiredif"> <arg key="prompt.zipCode"/> <var> <var-name>field[0]</var-name> <var-value>city</var-value> </var> <var> <var-name>fieldTest[0]</var-name> <var-value>NULL</var-value> </var> <var> <var-name>field[1]</var-name> <var-value>state</var-value> </var> <var> <var-name>fieldTest[1]</var-name> <var-value>NULL</var-value> </var> <var> <var-name>fieldJoin</var-name> <var-value>OR</var-value> </var> </field>
The Struts Validator has always worked well for single-field
validations. Cross-field validation, that is validating two or more
dependent fields, wasn’t supported by the Validator
until Struts 1.1. The requiredif
validation rule
was introduced at that time to address the problem. Interestingly
enough, the requiredif
rule lifespan will be
short. It is being deprecated in Struts 1.2 and is being replaced by
the validwhen
rule, discussed in Recipe 8.4.
The validation shown in the Solution would be used on a form where you were retrieving a user’s address. If users specify a zip code, then they can omit the city and state; these values would be looked up based on the zip code. On the other hand, if the city or state is not specified, then the zip code is required.
The requiredif
validator
can’t be used for client-side validation, only
server-side validation.
Before Struts 1.1,
cross-field
validations like this had to be hand coded in the
ActionForm
or Action
. With
Struts 1.1, you can use the
requiredif
rule
to indicate that a field is required if the value of other fields
meet certain criteria. The criteria are defined by specifying
variable names and values for the rule using the
var
element.
Each criterion for a dependent field is identified by an index. In
the Solution, the indices are used to create two criterion sets for
the dependent fields; city
and
state
. The index is used to group together a field
name, test type, and value. These values combine to represent an
expression. The corresponding var-value
for the
field[
i
]
variable refers to a form property:
<var>
<var-name>field[0]</var-name>
<var-value>property name
</var-value>
</var>
The
fieldTest[
i
]
variable defines the type of test:
<var> <var-name>fieldTest[i
]</var-name> <var-value>test type
</var-value> </var>
The following test types are accepted:
NULL
The field must be null or an empty string.
NOTNULL
The field must not be null or an empty string.
EQUAL
The field value must be equal to a specific value.
If the field test is NULL
or
NOTNULL
, then the criterion for that index is
complete. If the field test is EQUAL
, the
fieldValue[
i
]
variable contains the literal value to compare against:
<var>
<var-name>fieldValue[i]</var-name>
<var-value>literal value
</var-value>
</var>
If the property is numeric, the literal value will be converted to a number; otherwise, it is treated as literal text for a String comparison.
If more than one field is specified for a
requiredif
validation—there is more than one
criterion—you can logically connect the criterion using the
fieldJoin
variable:
<var>
<var-name>fieldJoin</var-name>
<var-value>logical operator
</var-value>
</var>
Valid values for the logical operator are AND
and
OR
. Using a logical AND
indicates that the validation passes if all the
requiredif
field criteria are true. A value of
OR
indicates that the validation passes if
any one of the field criteria is true.
If you are using Struts 1.2, the requiredif
validator is deprecated. Instead, use the
validwhen
validator described in Recipe 8.4.
The latest
documentation on
requiredif
can be found in the Struts Validator
Guide, available online at http://struts.apache.org/userGuide/dev_validator.html.
You are using Struts 1.2 and you want to validate a field based on the value of another related field.
Use the
validwhen
validator. The field
element in the following
snippet from a validation document indicates
that the zipCode
is valid when the following
occurs:
The city
and state
properties
are not null
(regardless of the
zipCode
value).
The zipCode
is not null
:
<form name="AddressForm"> <field property="zipCode" depends="validwhen"> <arg key="prompt.zipCode"/> <var> <var-name> test </var-name> <var-value> (((city != null) and (state != null)) or (*this* != null)) </var-value> </var> </field> <form name="AddressForm">
The
validwhen
validator, available with Struts 1.2, replaces
requiredif
for performing cross-field validations.
The validwhen
validator, like
requiredif
, can’t be used for
client-side validation, only server-side validation.
As in Recipe 8.3, the Solution shows how you would set up the validation on a form where you were retrieving a user’s address. If users specify a zip code, then they can omit the city and state; otherwise, if the city or state is not specified, the zip code is required.
With validwhen
, you can code a single expression
that takes the place of multiple XML elements needed for
requiredif
. The validwhen
validator is more powerful than requiredif
, though
it can be trickier to get the logic correct. With
requiredif
, your validation makes the assertion
“this field is required if....” The
validwhen
validator is different in that it
asserts the statement “this field is valid
when...” followed by a boolean expression. The
criteria that make up the expression are open-ended.
When you create the test expression for validwhen
,
ensure that the expression has a way to evaluate to true; otherwise,
the page will never pass validation.
In the Solution, the validation ensures that a zip code must have a
value if the city or state is null
. Likewise, if
the city and state aren’t null
,
then the zip code doesn’t have to be specified; it
can be null
. The following expression from the
Solution enforces this logic:
(((city != null) and (state != null)) or (*this* != null))
The *this*
notation in the expression represents
the value of the field being validated, the zip code. If the zip code
isn’t null
the expression will be
true regardless of the city and state values. If the city or state is
null
and the zip code is null
,
the expression evaluates to false; the zip code
isn’t valid. Finally, if the city and state have
values, the zip code can be any value, including
null
.
The kinds of
values allowed in
a validwhen
expression are the following:
Single- or double-quoted string literals
Integer literals in decimal, hex, or octal format
The value null, which will match against null
or an empty string
Other fields in the form referenced by field name, such as
customerAge
Indexed fields in the form referenced by an explicit integer, such as
childLastName[2]
Indexed fields in the form referenced by an implicit integer, such as
childLastName[]
, which will use the same index
into the array as the index of the field being tested
Properties of an indexed fields in the form referenced by an explicit
or implicit integer, such as child[].lastName
,
which will use the same index into the array as the index of the
field being tested
The literal *this*
, which contains the value of
the field currently being tested
The Validator parses the test expression using the ANTLR parser.
The entire expression must be enclosed in parentheses. Each logical
statement, such as (foo
==
bar)
, contained in the expression must be enclosed
in parentheses; using an editor that matches parentheses can help.
Here are some practical guidelines for crafting
validwhen
expressions:
Think completely through the logic of the test expression. Ensure the expression can evaluate to true under some set of circumstances.
Be precise with the parentheses and whitespace in the expression.
In testing, if the validation unexpectedly fails, check the
container’s log file or console output before you
starting questioning your logic. You have made a typo in the
validation.xml file or the
validwhen
expression can’t be
parsed for some reason.
If you are using Struts 1.1, validwhen
is not
available. You may be able to use the requiredif
rule, discussed in Recipe 8.3.
The latest documentation on the built-in pluggable validators can be found in the Struts Validator Guide available at http://struts.apache.org/userGuide/dev_validator.html.
You want to validate a field that is a nested property of an object
in an array or Collection
.
Set the indexedListProperty
attribute for the
field
element to the name of the property that
returns the array or Collection
. The value of the
property
attribute will be interpreted as the name
of a nested property of an element within the
Collection
. If you’re using
Struts 1.2, you can reference the indexed property in a
test
for a
validwhen
rule.
Example 8-5 shows a complete form
element from validation.xml for a form
containing the array property
“orders.”
<form name="IndexedListForm"> <field property="partNumber" indexedListProperty="orders" depends="minlength"> <arg position="0" key="prompt.partNumber"/> <arg position="1" key="${var:minlength}" resource="false"/> <var> <var-name>minlength</var-name> <var-value>5</var-value> </var> </field> <field property="quantity" indexedListProperty="orders" depends="intRange,validwhen"> <arg position="0" key="prompt.quantity"/> <arg position="1" key="${var:min}" resource="false"/> <arg position="2" key="${var:max}" resource="false"/> <msg name="validwhen" key="error.quantity.invalid"/> <var> <var-name>min</var-name> <var-value>5</var-value> </var> <var> <var-name>max</var-name> <var-value>20</var-value> </var> <var> <var-name>test</var-name> <var-value> (((orders[].partNumber != null) and (*this* != null)) or ((orders[].partNumber == null) and (*this* == null))) </var-value> </var> </field> </form>
The
indexedListProperty
permits you to define one set of validation rules applied to repeated
fields on a form. Say you have a JSP page like the one shown in Example 8-6 (indexed_list_test2.jsp)
that allows a user to place orders for specific part numbers and
quantities.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <html> <head> <title>Struts Cookbook Chapter 8 : Indexed List Validation</title> </head> <body bgcolor="white"> <h2>Struts Cookbook Chapter 8 : Indexed List Validation</h2> <html:form action="/ProcessIndexedListTest2"> <table> <tr> <th><bean:message key="prompt.partNumber"/></th> <th><bean:message key="prompt.quantity"/></th> </tr> <logic:iterate name="IndexedListForm" property="orders" id="orders" indexId="ndx"> <tr> <td> <html:text name="orders" property="partNumber" indexed="true"/><br /> <html:messages id="error" property='<%="orders["+ndx+"].partNumber"%>'> <font color="red"><bean:write name="error"/></font> </html:messages> </td> <td> <html:text name="orders" property="quantity" indexed="true"/><br /> <html:messages id="error" property='<%="orders["+ndx+"].quantity"%>'> <font color="red"><bean:write name="error"/></font> </html:messages> </td> </tr> </logic:iterate> </table> <html:submit/> </html:form> </body> </html>
The rendered HTML form for the JSP in Example 8-6 is shown in Example 8-7.
<html> <head> <title>Struts Cookbook Chapter 8 : Indexed List Validation</title> </head> <body bgcolor="white"> <h2>Struts Cookbook Chapter 8 : Indexed List Validation</h2> <form name="IndexedListForm" method="post" action="/jsc-ch08/ProcessIndexedListTest2.do"> <table> <tr> <th>Part number</th> <th>Quantity</th> </tr> <tr> <td> <input type="text" name="orders[0].partNumber" value=""> <br /> </td> <td> <input type="text" name="orders[0].quantity" value=""> <br /> </td> </tr> <tr> <td> <input type="text" name="orders[1].partNumber" value=""> <br /> </td> <td> <input type="text" name="orders[1].quantity" value=""> <br /> </td> </tr> <tr> <td> <input type="text" name="orders[2].partNumber" value=""> <br /> </td> <td> <input type="text" name="orders[2].quantity" value=""> <br /> </td> </tr> <tr> <td> <input type="text" name="orders[3].partNumber" value=""> <br /> </td> <td> <input type="text" name="orders[3].quantity" value=""> <br /> </td> </tr> <tr> <td> <input type="text" name="orders[4].partNumber" value=""> <br /> </td> <td> <input type="text" name="orders[4].quantity" value=""> <br /> </td> </tr> </table> <input type="submit" value="Submit"> </form> </body> </html>
By setting property="partNumber
" and
indexedListProperty="orders
“, you define a
validation rule that will be evaluated for each indexed property
submitted. In the Solution, each part number is checked using the
minlength
rule.
<field property="partNumber" indexedListProperty="orders" depends="minlength"> <arg position="0" key="prompt.partNumber"/> <arg position="1" key="${var:minlength}" resource="false"/> <var> <var-name>minlength</var-name> <var-value>7</var-value> </var> </field>
The rule only applies if the property being validated has a non-blank
value. Users don’t have to enter five part numbers
if they don’t want to. If you wanted to force the
users to enter a value for every part number field, you would specify
the
required
rule as well.
In the Solution, the quantity is validated. The
intRange
rule validates that an entered quantity is between 5 and 20. This
field uses the
validwhen
rule with a
rather verbose test
.
<var> <var-name>test</var-name> <var-value> (((orders[].partNumber != null) and (*this* != null)) or ((orders[].partNumber == null) and (*this* == null))) </var-value> </var>
The test ensures that if a quantity is entered, a part number must
also be entered. Likewise, if a part number isn’t
entered, the quantity should be blank. The operand,
orders[].partNumber
, allows you to refer to a
nested property of an indexed object. The []
notation tells the Validator that the expression applies to each
object in the array. You can refer to a specific index in the
property
attribute value or a
validwhen
expression. If you wanted to require
that a part number and quantity were entered for the first row, use
the following:
<field property="orders[0].partNumber" depends="required"> <arg position="0" key="prompt.partNumber"/> </field>
A common problem when validating multiple rows is that one error message is generated for each rule type for a field. In the Solution, for example, the quantity range validation is generated for the first failing property. You can see the practical results of this behavior in Figure 8-1. Though both quantity fields are invalid, the error is generated for the first quantity field.
Unfortunately, this is a quirk of the Validator when using indexed
properties. When the Validator encounters the first error for a
particular field, it
won’t continue to check additional indices of the
same field. If this presents a problem, you may want to go back to
displaying the errors at the top of the page or take the tried and
true method and hand code the
validate( )
method
to do what you want.
For additional details on using indexed properties in forms see
Recipe 3.4. The validwhen
rule is discussed in Recipe 8.4.
For custom rendering of error messages, look at Recipe 9.6.
You’ll find a number of interesting discussions on the struts-user mailing list; search on “indexedListProperty” and “array validation.”
You want to validate a calendar date and time field by specifying a specific format pattern.
Use the Validator’s
date
rule to specify the expected pattern that the date must match. Example 8-8 shows some different ways of using this rule.
<field property="date1" depends="date"> <arg key="Date1" resource="false"/> <var> <var-name>datePattern</var-name> <var-value>MM/dd/yyyy</var-value> </var> </field> <field property="date2" depends="date"> <arg key="Date2" resource="false"/> <var> <var-name>datePatternStrict</var-name> <var-value>MM/dd/yyyy</var-value> </var> </field> <field property="dateTime" depends="date"> <arg key="DateTime" resource="false"/> <var> <var-name>datePattern</var-name> <var-value>MM/dd/yy HH:mm</var-value> </var> </field>
Calendar dates have got to be one of the hardest field types to work with in a web application. The developer wants to ensure that only valid data gets into the system, but the users want to be able to enter the date in various formats. The Validator helps mitigate these conflicting desires by providing a robust rule for validating dates and times.
The
date
rule uses a
Validator variable (var
) to specify the pattern
that a property value must match. The datePattern
variable specifies the pattern for formatting the value using the
java.text.SimpleDateFormat
class. If
SimpleDateFormat
can format the value into a date,
then the validation passes; otherwise, the validation fails.
If datePatternStrict
is used instead of
datePattern
, the length of the value must match
the length of the pattern. Suppose you are validating against the
pattern of MM/dd/yyyy
. The user inputs
5/10/1963
to represent May 10, 1963. If
datePattern
is used, the value will pass
validation. If datePatternStrict
is used, however,
the validation won’t pass because the month portion
has one digit, not two, as specified by the pattern.
The date
rule allows the
SimpleDateFormat
to interpret the input value
leniently. For example, the value of 07/32/2004
passes validation! It’s interpreted as August 1,
2004. Though you can’t change the date rule to use
strict parsing, you can add the
mask
rule to
enforce stricter formats.
The JavaDocs for the
SimpleDateFormat
class have all the information you need to come up with a suitable
formatting patterns. This documentation can be found at http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html.
Use of the mask
rule is described in Recipe 8.2. Other considerations when working with dates
are discussed in Recipe 3.13.
You want to create a reusable validator in Struts 1.1 that can validate that the value of one field is equal to the value of another.
Use Matt Raible’s TwoFields custom validator. Start by creating a class with a static method that implements the rule, as shown in Example 8-9.
package com.oreilly.strutsckbk.ch08; import javax.servlet.http.HttpServletRequest; import org.apache.commons.validator.Field; import org.apache.commons.validator.GenericValidator; import org.apache.commons.validator.ValidatorAction; import org.apache.commons.validator.util.ValidatorUtils; import org.apache.struts.action.ActionErrors; import org.apache.struts.validator.Resources; public class CustomValidatorRules { public static boolean validateTwoFields( Object bean, ValidatorAction va, Field field, ActionErrors errors, HttpServletRequest request ) { String value = ValidatorUtils.getValueAsString(bean, field. getProperty( )); String sProperty2 = field.getVarValue("secondProperty"); String value2 = ValidatorUtils.getValueAsString(bean, sProperty2); if (!GenericValidator.isBlankOrNull(value)) { try { if (!value.equals(value2)) { errors.add( field.getKey( ), Resources.getActionError(request, va, field)); return false; } } catch (Exception e) { errors.add( field.getKey( ), Resources.getActionError(request, va, field)); return false; } } return true; } }
Next, add the following validator
element, shown
in Example 8-10, as a sub-element of the
global
element in the
validator-rules.xml file.
<validator name="twofields" classname="com.oreilly.strutsckbk.ch08.CustomValidatorRules" method="validateTwoFields" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" depends="required" msg="errors.twofields"> <javascript><![CDATA[ function validateTwoFields(form) { var bValid = true; var focusField = null; var i = 0; var fields = new Array( ); oTwoFields = new twofields( ); for (x in oTwoFields) { var field = form[oTwoFields[x][0]]; var secondField = form[oTwoFields[x][2]("secondProperty")]; if (field.type == 'text' || field.type == 'textarea' || field.type == 'select-one' || field.type == 'radio' || field.type == 'password') { var value; var secondValue; // get field's value if (field.type == "select-one") { var si = field.selectedIndex; value = field.options[si].value; secondValue = secondField.options[si].value; } else { value = field.value; secondValue = secondField.value; } if (value != secondValue) { if (i == 0) { focusField = field; } fields[i++] = oTwoFields[x][1]; bValid = false; } } } if (fields.length > 0) { focusField.focus( ); alert(fields.join(' ')); } return bValid; }]]></javascript> </validator>
Next, add an error message with the key of
errors.twofields
to your applications
MessageResources
properties file:
errors.twofields={0} must be equal to {1}.
To use the TwoFields validator, in your
validation.xml file change the field
element for one of the related fields to be dependent on
the twofields
rule. In Example 8-11, the password2
field must
be equal to the password
field for the validation
of password2
to succeed.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <formset> <form name="RegistrationForm"> <field property="emailAddress" depends="required,email"> <arg0 key="prompt.emailAddress"/> </field> <field property="password" depends="required"> <arg0 key="prompt.password"/> </field> <field property="password2" depends="required,twofields"> <arg0 key="prompt.password2"/> <arg1 key="prompt.password"/> <var> <var-name>secondProperty</var-name> <var-value>password</var-value> </var> </field> </form> </formset> </form-validation>
This recipe provides a solution for checking field equality and shows you how to create a custom validator. It’s easier than you might suppose.
You implement a custom validator as a static method of a class of
your choosing. By convention, the method should be named
validator
RuleName
. The
precise method signature when developing under Struts 1.1 is the
following:
public static boolean validateRuleName
( Object bean,
ValidatorAction va,
Field field,
ActionErrors errors,
HttpServletRequest request)
This method returns a boolean
, which indicates if
the validation passes (true
) or fails
(false
). Table 8-1 describes
the method arguments and how they are used.
Name |
Type |
Purpose |
|
|
The |
|
|
Object representation of the pluggable |
|
|
Object representation of the |
|
|
An instance of |
|
|
The HTTP servlet request. Used by the |
The implementation of the TwoFields rule does the following:
Gets the value for the field from the ActionForm
(bean
).
Gets the value for the field variable named
secondProperty
that represents a bean property
name.
Using the bean property name acquired in step 2, retrieves the form
value for the property from the ActionForm
.
Checks if the field value acquired in step 1 is blank or
null
. If it is either, true is
returned
; otherwise, processing continues.
Tests if the two values are equal using the equals(
)
method.
If the values are unequal, adds a new ActionError
to the errors
and returns
false
.
If an Exception
is thrown in step 5 or 6, the rule
adds a new ActionError
to the
errors
and return false
.
If no Exception
is thrown, the validation passes
and returns true
.
Once you have the Java coded for the validator, you need to link the
implementation to the Validator by creating a
validator
element in the
validator-rules.xml file.
Here’s the validator
element used
for TwoFields:
<validator name="twofields" classname="com.oreilly.strutsckbk.ch08.CustomValidatorRules" method="validateTwoFields" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" depends="required" msg="errors.twofields">
The name
attribute specifies the name that
you’ll use to refer to the validator wherever
it’s applied. The classname
,
method
, and methodParams
attributes are used by the Validator to determine the class and
method to call. If you are using Struts 1.2, be sure to change
ActionErrors
to ActionMessages
.
The depends
attribute specifies other rules
triggered before this rule is fired. For the
twofields
rule, you need to validate the field if
there’s a value for it. You can enforce this by
setting depends
to required
.
The last attribute, msg
, identifies the
MessageResources
key of the default error message
used by this rule. This value can be overridden when the rule is
applied.
The TwoFields rule shown in Example 8-10 includes JavaScript for client-side
validation. For a Validator rule, you enclose the JavaScript code in
a CDATA section of the javascript
element nested
in the validator
element:
<validator name="twofields" ...
<javascript>
<![CDATA[
function validateTwoFields(form) {
var bValid = true;
// body of the JavaScript function ...
return bValid;
}]]>
</javascript>
</validator>
The JavaScript function, like the Java method name, should be named
using the
validate
RuleName
convention. The function must return a boolean
value that indicates if the validation rule passes
(true
) or fails (false
).
The last piece of the puzzle (whew!) is to apply the rule to a specific form field:
<field property="password2" depends="twofields
"> <arg0 key="prompt.password2"/> <arg1 key="prompt.password"/> <var> <var-name>secondProperty
</var-name> <var-value>password</var-value> </var> </field>
The arg0
and arg1
values
specify the message resource keys for the labels of the two fields.
These values are substituted into the error message if the validation
fails. The var
element specifies the name of the
second field. This value doesn’t necessarily have to
be a field on the form (though in this case it probably would be); it
can be any JavaBean property on the ActionForm
.
If you are using Struts 1.2, an easier solution is discussed in Recipe 8.8.
The TwoFields validator was originally presented on Matt Raible’s weblog. It can be found on his site at http://www.raibledesigns.com/page/rd/20030226. The Validator User’s Guide discusses custom Validators in the section found at http://struts.apache.org/userGuide/dev_validator.html#plugs.
The Recipe 8.9 presents a custom Validator for Struts 1.2 that you may want to read up on as well.
You want to validate that two fields on a form have the same value, taking advantage of the new features provided by the Validator in Struts 1.2.
Use the
validwhen
rule
with a test
expression that checks if the
validated field is equals the other field. In Example 8-12, the test expression specifies the
password2
field (*this*
) must
equal the password
field for the validation to
pass.
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <formset> <form name="RegistrationForm"> <field property="emailAddress" depends="required,email"> <arg key="prompt.emailAddress"/> </field> <field property="password" depends="required"> <arg key="prompt.password"/> </field> <field property="password2" depends="required,validwhen"> <arg position="0" key="prompt.password2"/> <arg position="1" key="prompt.password"/> <msg name="validwhen" key="error.password.match"/> <var> <var-name>test</var-name> <var-value>(*this* == password)</var-value> </var> </field> </form> </formset> </form-validation>
If you compare this Solution with Recipe 8.7, you can see the same business rule is
implemented without requiring a custom Validator rule. The
validwhen
rule used here accepts a test
expression, which it evaluates. If the expression returns
true
, the validation passes; otherwise, the
validation fails.
With validwhen
, you can create expressions that
reference the value of the field being validated and the value of any
other property on the ActionForm
. With this power
in hand, creating an expression to test the equality of two fields is
easy:
(*this* == password)
The left-hand operand, *this*
, represents the
value of the field under validation. The right-hand operand,
password
, represents the value of the password
property from the RegistrationForm
.
The validwhen
rule is discussed in more detail in
Recipe 8.4.
If you can’t use validwhen
because you are using Struts 1.1, Recipe 8.7
provides an alternate solution using a custom rule.
You need to check that two or more choices have been picked from a set of checkboxes or a list of options.
Use my
minchoices
pluggable Validator rule. The rule implementation is shown in Example 8-13.
package com.oreilly.strutsckbk.ch08; import java.util.Collection; import javax.servlet.http.HttpServletRequest; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.validator.Field; import org.apache.commons.validator.ValidatorAction; import org.apache.struts.action.ActionMessages; import org.apache.struts.validator.Resources; public class ValidatorRules { public static boolean validateMinChoices(Object bean, ValidatorAction va, Field field, ActionMessages errors, HttpServletRequest request) { try { Object values = PropertyUtils.getProperty(bean, field. getProperty( )); int minChoices = Integer.parseInt(field.getVarValue ("minChoices")); if (!(values == null)) { int numChoices = 0; if (values instanceof Object[]) { numChoices = ((Object[])values).length; } else if (values instanceof Collection) { numChoices = ((Collection) values).size( ); } else { errors.add(field.getKey( ), Resources.getActionMessage(request, va, field)); return false; } if (numChoices < minChoices) { errors.add(field.getKey( ), Resources.getActionMessage(request, va, field)); return false; } } } catch (Exception e) { errors.add(field.getKey( ), Resources.getActionMessage(request, va, field)); return false; } return true; } }
Then add the corresponding validator
element to
the validator-rules.xml file:
<validator name="minchoices" classname="com.oreilly.strutsckbk.ch08.ValidatorRules" method="validateMinChoices" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionMessages, javax.servlet.http.HttpServletRequest" depends="required" msg="errors.minChoices"/>
Finally, you can apply the rule where needed:
<field property="" depends="minchoices"> <arg position="0" key="prompt.language"/> <arg position="1" key="${var:minChoices}" resource="false"/> <var> <var-name>minChoices</var-name> <var-value>3</var-value> </var> </field>
You often need to verify that users have checked at least one choice
from a set of checkboxes or selected one or more options from a
select list. Both of these validations can be performed using the
Validator’s predefined required
rule. However, validating users who have chosen
more than one item requires a custom rule like
the one shown in the Solution.
You create the minchoices
rule using the same
basic steps outlined in Recipe 8.7. The rule
requires that the validated field must be an array or a
Collection
. If it’s not, then the
validation fails and an error is returned. Otherwise, the value of
the minChoices
variable is retrieved. If the size
of the array or Collection is less than
minChoices
, the validation fails; otherwise, the
validation passes.
This pluggable validator was developed using the Struts 1.2 API. It
uses the ActionMessages
and
ActionMessage
classes instead of
ActionErrors
and ActionError
.
The signature of the validateMinChoices( )
method
and the methodParams
attribute of the
validator
element specify
ActionMessages
.
Recipe 8.7 provides an additional example of creating custom pluggable validators.
You need to add a custom ad hoc validation check to a Validator
ActionForm
.
Extend the ValidatorForm
or
ValidatorActionForm
and override the
validate( )
method, ensuring you call
super.validate( )
to
perform the Validator’s validation. (See Example 8-14.)
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 final class MyForm extends ValidatorForm { private String foo; private String bar; public String getFoo( ) { return foo; } public void setFoo(String s) { foo = s; } public String getBar( ) { return bar; } public void setBar(String s) { bar = s; } public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { // Perform validator framework validations ActionErrors errors = super.validate(mapping, request); // Add crossfield and business validations here if (!checkFooBarValid(foo, bar)) { errors.add("foo", new ActionError("errors.invalidFooBar")); } // additional validations ... return errors; } private boolean checkFooBarValid(Foo foo, Bar bar) { boolean valid = false; // perform custom validation valid = FooBarUtil.checkFooBar(foo, bar); return valid; } }
If you’re using a dynamic form defined in the
struts-config.xml, extend the type being used
(e.g., DynaValidatorForm
), and override the
validate( )
method as the class does in Example 8-15.
package com.mycompany.myapp; 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.DynaValidatorForm; public final class MyDynaForm extends DynaValidatorForm { public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { // Perform validator framework validations ActionErrors errors = super.validate(mapping, request); // get the needed property values String foo = (String) get("foo"); String bar = (String) get("bar"); // Add crossfield and business validations here if (!checkFooBarValid(foo, bar)) { errors.add("foo", new ActionError("errors.invalidFooBar")); } // additional validations ... return errors; } private boolean checkFooBarValid(Foo foo, Bar bar) { boolean valid = false; // perform custom validation valid = FooBarUtil.checkFooBar(foo, bar); return valid; } }
Then change the form-bean
element to use this new
class:
<form-bean name="SomeForm" type="com.mycompany.myapp.MyDynaForm"> <form-property name="foo" type="java.lang.String"/> <form-property name="bar" type="java.lang.String"/> </form-bean>
Though the Validator offloads much of your hand-written code to a configuration file, more complex validations may be required. Two general types of validation exist: syntactic and semantic. Syntactic validation verifies the syntax of the data, and semantic validation verifies that the data is meaningful from a business sense. The Validator does syntactic checks well but isn’t purposed for semantic validation. Usually, this validation needs to be hand-written.
Fortunately, the Validator doesn’t prohibit you from
providing additional custom validations. For conventional hand-coded
ActionForm
s that extend
ValidatorForm
or
ValidatorActionForm
, override the
validate( )
method to add the additional
validation checks. In your validate( )
method,
call super.validate( )
to allow the Validator to
perform its verifications. Then perform additional validations in
your own method as required. If an error occurs, add it to the
ActionErrors
object returned from
super.validate( )
.
If you are using the Validator with dynamic action forms, then your
form bean typically uses a type of
DynaValidatorForm
or
DynaValidatorActionForm
. Apply the same technique
for extending these classes as with conventional nondynamic form
classes. You extend the form class and override the
validate
method. To access properties of a
DynaValidatorForm
, you’ll need to
use the
get(
property
)
methods.
Recipe 5.1 discusses how to create and use dynamic action forms.
You need to perform validations on a form shared across pages as part of a wizard-style interface.
Use the
Validator’s built-in
support for wizards via the page
attribute. Example 8-16 configures the Validator use of this
attribute. Validations will be performed on fields where the
page
attribute value is less than or equal to the
page
property of the
ActionForm
.
<form name="WizardForm"> <field property="username" page="1" depends="required"> <arg key="prompt.username"/> </field> <field property="password" page="2" depends="required"> <arg key="prompt.password"/> </field> <field property="ssn" page="3" depends="required,mask"> <arg key="prompt.ssn"/> <var> <var-name>mask</var-name> <var-value>^d{3}-d{2}-d{4}$</var-value> </var> </field> </form>
If the form used for the wizard uses the
DynaValidatorForm
(or one of its subclasses), add a form-property
with the name of page
. The property must be of
type java.lang.Integer
:
<form-bean name="WizardForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> <form-property name="ssn" type="java.lang.String"/> <form-property name="page" type="java.lang.Integer"/> </form-bean>
For conventional ActionForm
s that subclass
ValidatorForm
(or one of its subclasses), no changes are necessary. The
ValidatorForm
already provides a
page
property. Because the page
instance variable is protected, it can be accessed by subclasses.
(See Example 8-17.)
package com.oreilly.strutsckbk.ch08; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.validator.ValidatorForm; public class WizardForm extends ValidatorForm { public String getUsername( ) { return username; } public void setUsername(String username) { this.username = username; } public String getPassword( ) { return password; } public void setPassword(String password) { this.password = password; } public String getSsn( ) { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } private String username; private String password; private String ssn; }
The Validator supports validation of session-scoped
ActionForm
s used
in wizard-style interfaces. You can specify a page
attribute for each field
element in your
validation.xml file. When you submit the form
from a step in the wizard, you set the page
property to a number representing the current step. This property can
be set in the Action
preceding the page, or it can
be hardcoded on the JSP itself; either way, it’s
usually rendered as a hidden field.
When the Validator validates the form, it checks those fields whose
page
attribute value is less than or equal to the
current page. The Solution, for example, is based on a wizard
interface with three pages; the first page handles the username, the
second page handles the password, and the last page handles the SSN.
The Validator validates prior pages and the current page primarily for thoroughness. Because of programming errors, a step in the flow could be accidentally skipped. On a more ominous note, a hacker could circumvent the wizard’s flow by submitting directly to an out-of-sequence URL. By validating prior pages in addition to the current page, no assumptions are made about how the current page was accessed; all prior fields are validated.
If you override the
ValidatorForm.validate(
)
method in your own subclass, you can account for the
page
property as the Validator does. This code
snippet shows how you could override the
validate( )
of the
ActionForm
in Example 8-17:
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = super.validate(mapping, request); if (page >= 2) { if (username.equals(password)) { errors.add( "password", new ActionMessage("password", "errors.password.sameAsUsername")); } } return errors; }
This method calls
super.validate()
to perform the
Validator validations.
It then performs a business logic validation, verifying that the
username and password are different. If the validation fails, an
ActionMessage
(or ActionError
in Struts 1.1) is added to the set of errors. Just like the
Validator, this validation accounts for the current page. In this
case, the validation is only performed for page 2 and beyond.
Recipe 8.10 shows Solutions for adding custom validations by extending Validator forms. Recipe 7.6 considers additional details about setting up a wizard-style interface.
You need to specify different validation rules for a specific language or country for certain fields on a form.
First, define the form validation rules for all form fields within
the global formset
element in the
validation.xml file:
<formset> <form name="LocalizedForm"> <field property="employeeId" depends="required"> <arg key="prompt.employeeId"/> </field> <field property="hourlyRate" depends="required,mask"> <arg key="prompt.hourlyRate"/> <var> <var-name>mask</var-name> <var-value>^d+.d{2}$</var-value> </var> </field> </form> </formset>
Then create a new formset
element using
localization attributes—language
,
country
, and variant
—for
the specific locale. Within this formset
, you can
configure locale-specific validation rules for those fields that
require special treatment:
<formset language="fr"> <form name="LocalizedForm"> <field property="hourlyRate" depends="required,mask"> <arg key="prompt.hourlyRate"/> <var> <var-name>mask</var-name> <var-value>^d+,d{2}$</var-value> </var> </field> </form> </formset>
You can tell the Validator to use specific
field validation rules for a particular
locale. The formset
supports the standard locale
properties with these attributes:
language
country
variant
At runtime, the Validator searches the formsets
,
based on locale, for a matching form
definition.
It evaluates the locale using the standard search algorithm used for
resource bundles; that is, if it can’t find its
match for the language and country, then it searches just for the
language, etc. Validations specified for a specific language
and country override the same validation
configured for the language.
Ensure that the formset
for the default locale
contains an element for every field you may
validate. If the Validator doesn’t have a fallback
rule for a field, it won’t be able to apply the
locale-specific rule.
Suppose you have an application that allows the user to enter a currency amount. For English-speaking users, you want to validate that the value contains one or more digits to the left of the decimal, a decimal point, and then two digits to the right of the decimal. You can state using a regular expression:
^d+.d{2}$
The period, or full-stop, character (.) is a
special character in a regular expression that matches any character.
To match an actual decimal point, you need to escape the period by
preceding it with a backslash (.).
French users, however, use a comma (,) as the decimal separator instead of a period (.). Here’s the regular expression for this rule:
^d+,d{2}$
In the Solution, the employeeId
field is required
regardless of locale. For the hourlyRate
field,
however, you want to allow French-speaking users to enter the value
using their natural format for currency.
For each
field that you configure in a
locale-specific format, you must specify all the
rules that apply in the depends
attribute. At
runtime, the Validator merges the fields between
formset
s, but it doesn’t merge
individual rules a field depends on. In the Solution, for example,
hourlyRate
depends on the
required
and mask
rules.
Therefore, all of these dependencies are listed within the French
formset
.
The Validator User’s Guide at http://struts.apache.org/userGuide/dev_validator.html has a section on localizing validations.
A good thread from the struts-user mailing list discussed how the Validator and the order that it processes fields and form sets. The thread can be traced from http://marc.theaimsgroup.com/?l=struts-user&m=104793541428623&w=2.
3.137.167.195