Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Apache Struts - From Novice To Professional (2006)

.pdf
Скачиваний:
56
Добавлен:
17.08.2013
Размер:
11.68 Mб
Скачать

198

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

The <form-validation> tag is the root tag, and all other tags are contained within it. The important subtags of <form-validation> are

<global>, which in turn contains declarations of “global” constants (we’ll come to this shortly) or declarations for validators.

<formset>, which represents validations for a locale. We’ll discuss this at the end of this chapter.

<form>, which is a subtag of <formset>, which contains your validations of fields on any form beans declared in struts-config.xml. You will declare these in your validation.xml file.

In the validator-rules.xml file, you will only see <validator> declarations, one for each generic validator available on the Validator framework.

Your validation.xml file should contain only <form> or <constant> declarations. This division of tags between validator-rules.xml and validation.xml is purely for maintainability: you put all declared validations in validator-rules.xml, and your use of the validations in validation.xml.

I’ll describe these tags in more detail as I show you how to use and extend the Validator framework.

Using the Validator Framework

Using the Validator framework is easy:

For each form bean needing validation, you add a new <form> tag to your validation.xml file. The name attribute of the <form> should be the same as the name of the form bean.

For each field requiring validation in the form bean, you add a <field> subtag to the <form>. The property attribute of the <field> tag should be the same as the name of the field you want to validate. The property attribute supports nested fields (see Chapter 10 for details on nested fields/properties).

The <field> tag also has a depends attribute, with which you specify the validations you want to run for this field. The names of the validations are specified as a comma-delimited list.

The <field> tag may contain optional subtags for custom error messages or variables used by a validator (e.g., minimum length of a field).

Ensure that your form bean is a subclass of org.apache.struts.validator. ValidatorForm.

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

199

That’s it! To see how this works, let’s explore how you’d use the Validator framework to validate RegistrationForm, the only form bean for the Registration webapp (see Listing 6-1 in Chapter 6).

Example: Validating RegistrationForm

RegistrationForm (refer to Listing 6-1) has three fields that need to be validated. In this section, I’ll create a version of RegistrationForm that uses the Validator framework. The new RegistrationForm.java class is shown in Listing 15-4.

Listing 15-4. RegistrationForm.java for the Validator Framework

package net.thinksquared.registration.struts;

import org.apache.struts.validator.*;

public final class RegistrationForm extends ValidatorForm{

private String _userid = null; private String _pwd = null; private String _pwd2 = null;

/**

*getXXX and setXXX functions

*corresponding to form properties

*/

public String getUserid(){ return _userid; }

public void setUserid(String userid){ _userid = userid; }

public String getPassword(){ return _pwd; }

public void setPassword(String pwd){ _pwd = pwd; }

public String getPassword2(){ return _pwd2; }

public void setPassword2(String pwd2){ _pwd2 = pwd2; }

}

If you compare Listing 15-4 with Listing 6-1, you’ll notice a couple of changes:

RegistrationForm now extends org.apache.struts.validator.ValidatorForm instead of org.apache.struts.action.ActionForm.

The validate() function has disappeared.

200

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

You need to subclass ValidatorForm because this base class will run validations declared in your validation.xml file. This also explains the disappearance of the validate() function— Struts will call validate() on ValidatorForm instead.

Note The form bean declaration for RegistrationForm would be unchanged.

To understand how to declare validations, consider that RegistrationForm has three fields: userid, password, and password2. The checks run in Listing 6-1 are to ensure that the userid is filled in and that password and password2 match.

These validations are hardly complete. We’ll add a max/min check for the userid and password, and a check that userid and password are restricted to alphanumerics. The validation.xml file incorporating all these checks appears in Listing 15-5.

Listing 15-5. Declaring Validations for RegistrationForm

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE form-validation PUBLIC ... // truncated for clarity <form-validation>

<formset>

<form name="RegistrationForm"> <field property="userid"

depends="required,mask,maxLength,minLength">

<msg name="required" key="reg.error.userid.missing" /> <msg name="mask" key="reg.error.userid.alphanum"/> <msg name="minLength" key="reg.error.userid.length" /> <msg name="maxLength" key="reg.error.userid.length" />

<arg name="minLength" key="${var:minlength}" position="0" resource="false"/>

<arg name="minLength" key="${var:maxlength}" position="1" resource="false"/>

<arg name="maxLength" key="${var:minlength}" position="0" resource="false"/>

<arg name="maxLength" key="${var:maxlength}" position="1" resource="false"/>

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

201

<var> <var-name>mask</var-name>

<var-value>^[a-zA-Z0-9]*$</var-value> </var>

<var> <var-name>minlength</var-name> <var-value>5</var-value>

</var>

<var> <var-name>maxlength</var-name> <var-value>10</var-value>

</var>

</field>

<field property="password" depends="required,mask,maxLength,minLength">

//similar to validations for userid,

//omitted for clarity.

</field>

<field property="password2" depends="validwhen">

<msg name="validwhen" key="reg.error.password.mismatch"/> <var>

<var-name>test</var-name> <var-value>(password == *this*)</var-value>

</var>

</field>

</form>

</formset> </form-validation>

As you can see, there’s just one <formset> tag and this means that we haven’t localized any validations. Don’t get me wrong—the error messages are localized, since Listing 15-5 obviously uses keys from the properties file. But the validations themselves are not localized. To understand the difference, refer to the discussion on “Localizing Validations” in Chapter 12.

Most of the time, you won’t be localizing validations, so your validation.xml file will contain just one <formset> tag. I’ll describe how to use multiple <formset> tags to help you localize validations toward the end of this chapter.

202

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

The nested <form> tag is where the action happens. In Listing 15-5, there’s just one <form> tag, corresponding to the RegistrationForm form bean that needs validating. If you had other form beans that needed validating, then the <formset> would contain more <form> tags, one for each of these form beans.

The validations for each field are done separately. The validations for the password field are very similar to the ones for userid, so I’ve left this field out. From the <field> declaration for the userid field, you can tell I’ve run four validations:

<field property="userid" depends="required,mask,maxLength,minLength">

The required validator checks that the field is filled in by the user. It requires no extra information to run. If the required validator fails, the following <msg> tag

<msg name="required" key="reg.error.userid.missing"/>

ensures that the error message specified by the key reg.error.userid.missing is displayed. It’s possible (but not advisable) to force the display of a static string using the resource=false attribute:

<msg name="required" key="Userid is missing!" resource="false"/>

The mask validator checks that the field’s value matches a given regular expression.

Note A regular expression is a way to compactly express any string. For example, the DOS regular expression *.* matches all filenames with an extension. Regular expression languages vary by provider, so *.* will not match every filename with an extension if used in the Validator framework. The Validator framework uses a much more powerful regular expression language, similar to the one used in the Perl programming language. Refer to the “Useful Links” section at the end of this chapter for a reference.

This regular expression is given in a var tag:

<var> <var-name>mask</var-name>

<var-value>^[a-zA-Z0-9]*$</var-value> </var>

This declares a variable named mask, with the value ^[a-zA-Z0-9]*$. The mask validator requires this variable to be defined in order to work. In this case, the regular expression matches alphanumeric strings (strings containing roman alphabets and Arabic numerals, 0 through 9).

Similarly, the maxLength and minLength validators require variables maxlength and minlength, respectively.

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

203

The error messages for these checks require some explanation. For example, the custom error message for minLength is declared by

<msg name="minLength" key="reg.error.userid.length"/>

The entry in Application.properties for this key is

Passwords should be between {0} and {1} characters long

The {0} and {1} replacement arguments have to be defined using <arg> tags:

<arg name="minLength" key="${var:minlength}" position="0" resource="false"/>

<arg name="minLength" key="${var:maxlength}" position="1" resource="false"/>

The name attribute corresponds to the name of the validator (this is also present in the <msg> tag), and the key in this case refers to the minlength and maxlength variable values. Since the key attribute is to be interpreted as a static value and not as a key in Application. properties, I’ve set resource="false". The position attribute indicates which replacement argument this refers to.

LOCALIZING THE REPLACEMENT ARGUMENTS

In Listing 15-5, the replacement arguments are not localized. So, a user in the Arabic locale would see the error message in Arabic except for the numerals (the modern Arabic numerals are not the same as the so-called “Arabic” numerals 0 to 9—invented by the Hindus of India before being brought to the West).

If you were required to localize a replacement argument you have two options:

Hard-code the variable into an error message. This is best if you do not anticipate reuse of the replacement argument in other error messages.

If reuse of the replacement argument is necessary, declare it as a key/value pair in your Application.properties file. Then use <arg> with the key attribute corresponding to this key and leave out the resource attribute, or set resource="true".

The validation of the userid field only succeeds if all the declared validators for it pass. The validation for the password2 field used the validwhen validator, which I will describe in more detail in a later section (“Using validwhen”).

Validating Nested and Indexed Properties

userid, password, and password2 are all simple properties (see Chapter 10 for a definition). You can also validate nested and indexed properties.

204

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

For nested properties, simply set the field’s property attribute using the usual . notation to access a nested property. For example, setting property="contact.email" validates the email property on the contact property of the given form.

Validating indexed properties is a little different. Suppose you have a function that returns an array or Collection of values, and you want to validate each item on the array. The way to do this is to use the indexedListProperty attribute of <field>. For example:

<field indexedListProperty="items" ...

interprets items as an array or Collection, and will validate each element in the array/Collection.

You can also combine indexed with simple or nested properties. For example:

<field indexedListProperty="items" property="id" ...

will validate the id property of each element on the items array/Collection. And

<field indexedListProperty="contacts" property="address.postcode" ...

will validate the nested property address.postcode on each element in the contacts array/

Collection.

Using Constants

Both userid and password validations check that the values keyed in by the user are composed of alphanumerics. In Listing 15-5, the regular expression for each would be duplicated. Is there a way to “share” these? The answer is “yes,” and the way to do this is to declare the regular expression as a constant. You have two options open to you:

Define a global constant—a constant accessible to every field in every <formset>.

Define a constant accessible to every field within a single <formset>.

To declare a global constant, you’d place the following XML before the first <formset> tag:

<global>

<constant> <constant-name>alphanumericMask</constant-name> <constant-value>^[a-zA-Z0-9]*$</constant-value>

</constant>

</global>

This declares a constant named alphanumericMask with the value ^[a-zA-Z0-9]*$. Declaring the constant in a <formset> is similar:

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

205

<formset>

<constant> <constant-name>alphanumericMask</constant-name> <constant-value>^[a-zA-Z0-9]*$</constant-value>

</constant>

<form name="RegistrationForm" ...

Note that according to the DTD, <constant>s have to be declared at the start of the

<formset> tag.

To use the declared alphanumericMask constant in either case, you’d employ the ${constant-name} reference:

<var> <var-name>mask</var-name>

<var-value>${alphanumericMask}</var-value> </var>

Client-Side Validations

To create client-side validations, simply place a <html:javascript form="MyFormBean"/> tag anywhere on your JSP page so that Struts can paste JavaScript on the final web page in order to run client-side validations. Bear in mind, however, that there’s no easy way (apart from amending the validator itself, or perhaps writing a plug-in) to prevent a form from being passed through server-side validations.

This might make you wonder whether the client-side validations are of any use! To be fair, the Validator framework is undergoing active development, and I’m sure we’ll see improvements in the area of client-side validations soon.

We next look at the standard validators available on the Validator framework.

The Standard Validators

Table 15-1 lists all standard validators at the time of this writing (Struts 1.2.4 to 1.2.7). However, as I mentioned the Validator framework is undergoing active development, so be sure to check the Struts Validator website (see the “Useful Links” section) for the latest.

The first column of Table 15-1 shows the name of the validator. This is the name you’d use in the depends attribute of the <field> tag. The second column gives the list of variables the validator accepts. All variables listed in the second column are required. The last column gives “triggers” for the validator—conditions that if met will trigger the validator to fail.

Almost every validator is easy to use, with the possible exception of the validwhen validator. We’ll tackle this next.

206 C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

Table 15-1. Standard Validators, Their Arguments, and Their Triggers

Validator

Variables

Trigger

required

-

Field only has whitespace.

mask

mask

Field doesn’t match mask. The mask variable

 

 

is a regular expression.

intRange,floatRange

min,max

Field > max or field < min.

maxLength

maxLength

Field has more than maxLength characters.

minLength

minLength

Field has fewer than minLength characters.

double,float,long,integer,short,byte

 

Field can’t be cast to associated primitive.

date

datePattern or

Field doesn’t match given date pattern.

 

datePatternStrict

Note: datePatternStrict requires any

 

 

leading zeroes present in the pattern to

 

 

be present in the field.

creditCard

 

Field is not a credit card number.

email

 

Field is not a conformant email address.

 

 

(Note: This is actually much more com-

 

 

plicated that testing for a single @ in the

 

 

field!)

url

schemes,

Field is not a URL. Several protocols are

 

allowallschemes,

supported. schemes is a comma-delimited

 

allow2slashes,

list of protocols, for example, http,https,

 

nofragments

ftp,file,telnet,gopher. allowallschemes

 

(all optional)

(which equals false by default) makes

 

 

any scheme acceptable if true.

 

 

allow2slashes (= false) accepts double

 

 

backslashes (//) in a scheme. nofragments

 

 

(= false) disallows a fragment of a URL.

validwhen

test

The test condition fails. See the following

 

 

subsection for more details.

requiredif

Deprecated

Deprecated—use validwhen instead.

range

Deprecated

Deprecated—use intRange or floatRange

 

 

instead.

 

 

 

Using validwhen

The validwhen validator is useful when validation of one field depends on another. With this validator, you can formulate complex dependencies.

I’ve always believed an example beats a thousand descriptions, so Listing 15-6 shows an example of validwhen in action.

Note

C H A P T E R 1 5 T H E V A L I D A T O R F R A M E W O R K

207

Listing 15-6. Using validwhen

<field property="mySecondField" depends="validwhen"> <var>

<var-name>test</var-name> <var-value>

((myFirstField == null) or (*this* != null))

</var-value> </var>

</field>

mySecondField is valid when myFirstField is blank or mySecondField is not blank. The symbol *this* refers to the field being validated. Notice also that the logical operators are or and and, not the usual Java || and &&.

This is an example of how to conditionally validate fields. mySecondField only gets validated (checked for a blank value) if myFirstField is blank. This is how you can use validwhen to replace the older (pre-1.2) validator requiredif.

Clearly validwhen is a powerful solution to a variety of problems. Consider the common problem of validating a twice-entered password (see Listing 15-7).

Listing 15-7. Checking for Identical Fields

<field property="password2" depends="validwhen"> <var>

<var-name>test</var-name> <var-value>

(password == *this*)

</var-value> </var>

</field>

Using validwhen with Indexed Fields

validwhen also makes validating dependent indexed fields and their properties unbelievably easy.

For example, suppose your form had a table of values, such as a list of items in a shopping cart. You could do this by using one of the following: