Chapter 8. Input Validation

Introduction

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.

Warning

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.

Tip

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.

Example 8-1. Custom action form with validation
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 .

Table 8-1. Bundled pluggable validators

required

Checks that a field value is non-null and not an empty string.

requiredif

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 validwhen, in releases after Struts 1.1.

validwhen

Designed to replace requiredif, this validator is available in releases after Struts 1.1. This validator relies on a user-specified test expression that can include references to other fields, field values, and logical relationships.

minlength

Checks that the number of characters in the field value is greater than or equal to a specified minimum.

maxlength

Checks that the number of characters in the field value is less than or equal to a specified maximum.

mask

Validates the field value using a regular expression to determine a match. If the value matches the regular expression, the field is valid.

byte

Checks that the field value is a valid byte value.

short

Checks that the field value is a valid short integer value.

integer

Checks that the field value is a valid integer value.

long

Checks that the field value is a valid long value.

float

Checks that the field value is a valid floating-point value.

double

Checks that the field value is a valid double value.

date

Checks that the field value matches a specified date format pattern (e.g., MM/dd/yyyy). The match can be strict or lenient; “strict” would require May 10, 1963 to be formatted (using the MM/dd/yyyy pattern) as 05/10/1963; “lenient” would allow 5/10/1963.

range

Checks that the field value is within a specified numeric range. This valdiator has been deprecated in favor of the type-specific range checks (intRange, floatRange, etc.).

intRange

Checks that the field value is within a range bounded by two int values.

floatRange

Checks that the field value is within a range bounded by two float values.

doubleRange

Checks that the field value is within a range bounded by two double values.

creditCard

Verifies that the format of the field value is valid for a credit card number. This validator is convenient to use instead of using mask.

email

Verifies that the format of the field value is valid for an electronic mail address (e.g., [email protected]). This validator is convenient to use instead of working with mask.

url

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.

Configuring the Validator

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.

Tip

You can specify more than these two files if needed.

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.

Example 8.2. Validation for a login form
<?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>

Tip

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>

Tip

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.

Warning

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.

See Also

For more of the basics on using the Validator, check out Programming Jakarta Struts by Chuck Cavaness (O’Reilly).

8.1. Reusing Validator Attribute Values

Problem

You want to define, in one place, a common value you can reference wherever needed in a Validator form.

Solution

Define the value as a global or form-set constant in one of your validation documents, as shown in Example 8-3.

Example 8-3. Defining global and form-set validator constants
<?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>

Discussion

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.

Example 8-4. Using global and formset constants
<?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.

See Also

The mask validator is discussed in Recipe 8.2.

8.2. Validating Using Regular Expressions

Problem

You want to validate data using a regular expression.

Solution

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>

Discussion

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>

Warning

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.

See Also

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.

8.3. Validating Dependent Fields in Struts 1.1

Problem

You are using Struts 1.1 and you want to validate a field based on the value of another related field.

Solution

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>

Discussion

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.

Warning

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.

Tip

If fieldJoin isn’t specified, then AND will be assumed.

See Also

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.

8.4. Validating Dependent Fields in Struts 1.2

Problem

You are using Struts 1.2 and you want to validate a field based on the value of another related field.

Solution

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

Discussion

The validwhen validator, available with Struts 1.2, replaces requiredif for performing cross-field validations.

Warning

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.

Warning

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.

Warning

The expression syntax is strict and unforgiving.

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.

See Also

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.

8.5. Validating an Indexed Property

Problem

You want to validate a field that is a nested property of an object in an array or Collection.

Solution

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.”

Example 8-5. Validations for an indexed list property (partial)
<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>

Discussion

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.

Example 8-6. JSP for rendering indexed properties
<%@ 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 />&nbsp;
                    <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 />&nbsp;
                    <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.

Example 8-7. Generated HTML for indexed properties
<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 />&nbsp;
                </td>
                <td>
                    <input type="text" name="orders[0].quantity" value="">
                    <br />&nbsp;
                </td>
            </tr>
            <tr>
                <td>
                    <input type="text" name="orders[1].partNumber" value="">
                    <br />&nbsp;
                </td>
                <td>
                    <input type="text" name="orders[1].quantity" value="">
                    <br />&nbsp;
                </td>
            </tr>
            <tr>
                <td>
                    <input type="text" name="orders[2].partNumber" value="">
                    <br />&nbsp;
                </td>
                <td>
                    <input type="text" name="orders[2].quantity" value="">
                    <br />&nbsp;
                </td>
            </tr>
            <tr>
                <td>
                    <input type="text" name="orders[3].partNumber" value="">
                    <br />&nbsp;
                </td>
                <td>
                    <input type="text" name="orders[3].quantity" value="">
                    <br />&nbsp;
                </td>
            </tr>
            <tr>
                <td>
                    <input type="text" name="orders[4].partNumber" value="">
                    <br />&nbsp;
                </td>
                <td>
                    <input type="text" name="orders[4].quantity" value="">
                    <br />&nbsp;
                </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.

Indexed list JSP with errors
Figure 8-1. Indexed list JSP with errors

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.

See Also

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.”

8.6. Validating Dates

Problem

You want to validate a calendar date and time field by specifying a specific format pattern.

Solution

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.

Example 8-8. Using the Validator’s date 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>

Discussion

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.

See Also

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.

8.7. Validating Field Equality with a Custom Validator

Problem

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.

Solution

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.

Example 8-9. TwoFields validation rule class
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.

Example 8-10. TwoFields validator rule
<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.

Example 8-11. Applying the TwoFields rule
<?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>

Discussion

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.

Table 8-2. Validator rule arguments

Name

Type

Purpose

bean

Object

The ActionForm being validated. You retrieve the input values from this object’s properties.

Va

ValidatorAction

Object representation of the pluggable validator element from the validator-rules.xml file for the particular rule. Used to generate the error message for the rule.

field

Field

Object representation of the field element from the validation.xml file. Provides access to the nested variables (var) and arguments (arg).

errors

ActionErrors

An instance of ActionErrors you populate with ActionError objects. For Struts 1.2, this variable is an ActionMessages instance that you populate it ActionMessage objects.

request

HttpServletRequest

The HTTP servlet request. Used by the Resources utility class to retrieve the MessageResources for creating an ActionError.

The implementation of the TwoFields rule does the following:

  1. Gets the value for the field from the ActionForm (bean).

  2. Gets the value for the field variable named secondProperty that represents a bean property name.

  3. Using the bean property name acquired in step 2, retrieves the form value for the property from the ActionForm.

  4. Checks if the field value acquired in step 1 is blank or null. If it is either, true is returned; otherwise, processing continues.

  5. Tests if the two values are equal using the equals( ) method.

  6. If the values are unequal, adds a new ActionError to the errors and returns false.

  7. If an Exception is thrown in step 5 or 6, the rule adds a new ActionError to the errors and return false.

  8. 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).

Tip

For custom rules that don’t use client-side validation, you can omit the javascript element.

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.

See Also

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.

8.8. Validating Field Equality in Struts 1.2

Problem

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.

Solution

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.

Example 8-12. Validating field equality with validwhen
<?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>

Discussion

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.

See Also

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.

8.9. Validating Two or More Choices

Problem

You need to check that two or more choices have been picked from a set of checkboxes or a list of options.

Solution

Use my minchoices pluggable Validator rule. The rule implementation is shown in Example 8-13.

Example 8-13. Validating minimum choices
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>

Discussion

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.

Warning

If the methodParams specified in the validator element doesn’t match the method signature of the rule’s Java method, the Validator will be unable to find the method to invoke.

See Also

Recipe 8.7 provides an additional example of creating custom pluggable validators.

8.10. Adding a Custom Validation to a Validator Form

Problem

You need to add a custom ad hoc validation check to a Validator ActionForm.

Solution

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.)

Example 8-14. Extending the ValidatorForm
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.

Example 8-15. Extending the DynaValidatorForm
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>

Discussion

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 ActionForms 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.

See Also

Recipe 5.1 discusses how to create and use dynamic action forms.

8.11. Validating a Wizard Form

Problem

You need to perform validations on a form shared across pages as part of a wizard-style interface.

Solution

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.

Example 8-16. Use of the Validator page attribute
<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 ActionForms 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.)

Example 8-17. Action Form for wizard interface
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;
}

Discussion

The Validator supports validation of session-scoped ActionForms 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.

See Also

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.

8.12. Localizing Validation Rules

Problem

You need to specify different validation rules for a specific language or country for certain fields on a form.

Solution

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>

Discussion

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.

Warning

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}$

Warning

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 formsets, 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.

See Also

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.79.65