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

Beginning Apache Struts - From Novice To Professional (2006)

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

208

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

Separate arrays for each column

A single array of containing objects that holds values for a single row

We’ll tackle these two situations separately.

In the first scenario, our shopping cart has two columns, each represented by arrays itemID and itemCount. We want to check that each non-null itemID element has a corresponding non-null itemCount element. The validator declaration to do this appears in Listing 15-8.

Listing 15-8. Validating Indexed Fields

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

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

((itemID[] == null) or (*this* != null))

</var-value> </var>

</field>

The validator automatically runs the check for each row of itemCount and itemID.

In the second scenario, we use an object called item to hold a single row of data. This object has the properties id and count.

Note Recall that for this to work, item needs to be a JavaBean. That is, it needs to have getXXX and setXXX functions for each property, like id and count.

Listing 15-9 shows how we run the same validation.

Listing 15-9. Validating Properties on Indexed Fields

<field property="count"

indexedListProperty="item" depends="validwhen"> <var>

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

((item.id[] == null) or (*this* != null))

</var-value> </var>

</field>

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

209

Lastly, note that you can explicitly refer to a specific row on an indexed field. Listing 15-10 shows an example.

Listing 15-10. Referring to an Explicit Row on an Indexed Field

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

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

((item.id[1] == null) or (*this* != null))

</var-value> </var>

</field>

In the next section, we’ll look at how to run your own validations alongside the standard ones you’ve used in validation.xml.

Adding Custom Validations

Although the set of validations on the Validator framework are quite comprehensive, you might want to run your own validations alongside those available in the Validator framework. There are two ways of doing this:

Put in a validate() function on your ValidatorForm subclass. You’d take this approach if the custom validation isn’t likely to be reused in other parts of your application.

Extend the Validator framework itself by creating a new validator. You’d take this route if there’s a strong case for reuse.

In most cases, putting in a validate() function should suffice. Use the second alternative only after much thought, since validators should be generic, if only to the problem domain of your web application. Otherwise, you’d run into potential maintenance issues. Can you see why?

We’ll take a look at both approaches in this section.

Implementing validate()

As I mentioned earlier, one way of implementing your own validations in addition to the standard ones is to implement the validate() function.

We’ll take up the RegistrationForm class again (see Listing 15-4), and add custom validations to detect that the given password isn’t in a list of common words like “password”

210

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

or “secret.” Listing 15-11 shows the validate() function that you’d add to RegistrationForm in order to achieve this functionality.

Listing 15-11. Checking for Common Words as Passwords

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){

//run the validations declared in validation.xml first

ActionErrors errors = super.validate(mapping,request);

if(errors != null && !errors.isEmpty()) return errors;

errors = (errors == null)? new ActionErrors() : errors;

//run your custom validations next

for(int i = commonWords.length - 1; i >= 0; i--){ if (_password.toUpper().equals(commonWords[i])){

errors.add("password",

new ActionMessage("reg.error.password.toocommon", commonWords[i]));

return errors;

}

}

return (errors.isEmpty())? null : errors;

}

In Listing 15-11, validate() is called on the base class (ValidatorForm), which runs the validations defined in validation.xml. The end result is an ActionErrors object, to which we can add new error messages of our own. Notice the use of the second constructor of ActionMessage (see Appendix B), in order to customize the error message with the common word that was rejected in the validation.

Extending the Validator Framework

Instead of implementing validate() as in Listing 15-11, we could extend the Validator framework. Do this sparingly. For the Registration webapp example, it would be overkill— implementing validate() would suffice. However, in this subsection, I will extend the Validator framework for the same functionality, simply so you can compare the two approaches.

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

211

So, in what situations would extending the Validator framework be appropriate? Actually, only when you can justify reuse of the custom validator. This usually means that the custom validator checks a variable prevalent in your webapp’s problem domain.

Good examples are identifiers, like product codes, ISBN numbers, IP addresses, or zip codes (or “postal codes” as they are known outside the United States). While these can be validated using the standard mask validator, it may not always be easy to do so, especially if you need to take care of exceptions to the rule (e.g., postal codes), or if the variable can change format over time (e.g., IP addresses).

You’d be much better off extending the Validator framework than implementing the validate() function because you obviously want to reuse the validations. A zip code field, for example, could appear in more than one form, or be present in many different webapps you might create.

Creating a new validator is easy:

1.Implement the Java handler class that actually performs the validation.

2.Optionally create JavaScript for form validation. I won’t cover this topic in this book.

3.Declare the new handler in validator-rules.xml with the <validator> tag.

Implementing the Java Handler

This is the trickiest part of the whole process of creating a custom validator. Most of the complexity arises because you must handle the possibility of nested and indexed properties and because you have to use helper classes (Resources, GenericValidator, etc.) to read the information you need to validate an input datum or to reuse validations available in the Validator framework.

Your Java handler can be any Java object, as long as the following conditions are met:

It implements java.io.Serializable.

The function performing the custom validation has public access. It must also be a static function that returns a boolean, which is false if validation fails and true otherwise.

You can call this function anything you like. The standard method signature is shown in Listing 15-12.

212

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

Listing 15-12. Standard Signature for the Custom Validation Function

public static boolean

myValidationFunction(Object bean, ValidatorAction va, Field field, ActionMessages errors,

Validator validator, HttpServletRequest request){

The function’s six arguments are the following parameters:

java.lang.Object: This can be a String or a JavaBean-conformant object. It holds the value(s) to be validated. If it’s a JavaBean, the value to be validated resides on one of the bean’s properties. Your validation function will have to handle each of these possibilities.

org.apache.commons.validator.ValidatorAction: This class contains information declared within the <validator>. You’ll probably never call functions on this class, but an initialized instance of it is needed when you want to load pooled error messages saved in Struts’ Resource class. Without such an instance, you’d have to resort to calling a new ActionMessage(...) each time you wanted to create an error message, which isn’t efficient.

org.apache.commons.validator.Field: This contains data pertaining to the field being validated. With this instance, you can find out the name of the property being validated and any other information contained in the <field> tag.

ActionMessages: This is simply a container for you to place your error messages. Struts manages the creation of ActionMessages objects, enabling these objects to be pooled for efficiency.

org.apache.commons.validator.Validator: This is an instance of the Validator framework itself, which you can use to read other fields in the <form>. You might need to use this to perform field-dependent validations (as is the case with validwhen). Like ValidatorAction, an instance is needed to load pooled ActionMessage instances for efficiency.

HttpServletRequest: This is the request object associated with the current request.

Note You can actually use any subset of these six parameters, in any order! The Validator framework will resolve the correct function at runtime, based on your declaration in the <validator> tag for this validation. However, keeping the function signature consistent makes maintenance that much easier.

A typical validation function will run through these steps:

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

213

1.Determine the value to be validated. You will use the helper class org.apache. commons.validator.ValidatorUtils to do this.

2.Collect information about the environment (such as the values of other fields on the form), if necessary. You might do this with the Field, ValidatorAction, and Validator instances sent in the function’s argument list.

3.Run the custom validation. Knowledge of the functions on org.apache.commons.validator.GenericValidator is helpful, since it contains many useful validations you can reuse.

4.Locate a suitable ActionMessage instance (instead of calling new), and place this instance on the given ActionMessages object. Use the helper class, org.apache. struts.validator.Resources, to do this.

Listing 15-13 shows a simple validation—ensuring the given data is alphanumeric.

Listing 15-13. Java Handler to Validate Alphanumeric Strings

package com.mycompany.myapp.struts.validator;

import java.io.Serializable;

import org.apache.commons.validator.ValidatorAction; import org.apache.commons.validator.Field;

import org.apache.commons.validator.Validator; import org.apache.struts.action.ActionMessages; import javax.servlet.http.HttpServletRequest;

import org.apache.commons.validator.util.ValidatorUtils; import org.apache.commons.validator.GenericValidator; import org.apache.struts.validator.Resources;

public class MyValidations implements Serializable{

public static boolean isAlphanumeric(Object bean, ValidatorAction va, Field field, ActionMessages errors, Validator validator,

HttpServletRequest request){

214

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

//step 1: Determine value

String value = null; if(String.class.isInstance(bean)){

value = (String) bean; }else{

value = ValidatorUtils.getValueAsString(bean,

 

 

field.getProperty());

}

 

 

//step 2: Collect info about the environment

/* --------

not needed

--------- */

//step 3: Run the custom validation try{

if(!(GenericValidator.isBlankOrNull(value) || GenericValidator.matchRegexp(value, "^[a-zA-Z0-9]$"))){

errors.add(field.getKey(),

/**

*step 4: Locate a suitable ActionMessage

*instance.

*/

Resources.getActionMessage(validator,

request,

va,

field));

return false;

}

}catch(Exception ignore){}

return true;

}

}

A systematic and detailed exposition of the Apache Commons Validator classes is beyond the scope of this book. In most cases, however, the following information will prove useful:

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

215

field.getProperty() gives the property of the field being validated.

field.getKey() gives the error message associated with this validator.

ValidatorUtils.getValueAsString(bean, property) reads the simple/nested/ indexed property of the given bean.

GenericValidator.isBlankOrNull(value) returns true if the value is blank or null.

GenericValidator has many other functions you might find useful, like matchRegexp(value,regexp), which returns true if the value matches the given regular expression.

The Struts class org.apache.struts.validator.FieldChecks is the Java handler class corresponding to every one of the Validator framework standard validators.

Armed with the information from this section, you should be well equipped to pursue the topic further by reading the Commons Validator source code, as well as FieldChecks from Struts.

Note To compile your code, you will need commons-validator.jar, commons-beanutils.jar, servlet-apis.jar, and struts.jar on your classpath. These files are in the Struts distribution.

The last step is to declare your validator in validator-rules.xml. Listing 15-14 shows how this would look in the case of alphanumeric validation.

Listing 15-14. Declaring the Alphanumeric Validator

<form-validation> <global>

<validator name="alphanumeric" classname="com.mycompany.myapp.struts.validator.MyValidations" method="isAlphanumeric"

methodParams="java.lang.Object,

org.apache.commons.validator.ValidatorAction,

org.apache.commons.validator.Field,

org.apache.struts.action.ActionMessages,

javax.servlet.http.HttpServletRequest"

msg="errors.alphanumeric"/>

// ... other <validator> declarations

216

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 name of the validator is what it will be called in your validation.xml file. The classname attribute corresponds to the class containing the validation function. The method attribute is the actual method used for this validator. The methodParams attribute declares the method signature of the validation function.

The msg parameter defines a default error message key that the user has to implement in the Application.properties file. It is preferable, however, to override this default message key and specify your own, as I’ve done in Listing 15-4.

You may add a depends attribute to your <validator> declaration. This functions just like depends on your <field> tags: the comma-delimited list of validators are fired before your custom validation is called. For example, in Listing 15-15 I’ve amended Listing 15-14 slightly.

Listing 15-15. Using depends in the Alphanumeric Validator

<validator name="alphanumeric" classname="com.mycompany.myapp.struts.validator.MyValidations" method="isAlphanumeric"

methodParams="java.lang.Object,

org.apache.commons.validator.ValidatorAction,

org.apache.commons.validator.Field,

org.apache.struts.action.ActionMessages,

javax.servlet.http.HttpServletRequest"

msg="errors.alphanumeric"

depends="required"/>

// ... other <validator> declarations

The amended validator of Listing 15-15 would fail if the user entered a blank value, since it depends on the required validator.

Migrating Legacy Code

As your webapp grows, creating and maintaining individual validate() functions containing mainly generic validations can be a challenge. As I’ve discussed in the introduction to this chapter, using the Validator framework brings many benefits, so migrating your legacy code to the Validator framework might be something you’d want to do. Fortunately, this is a simple three-step process:

1.Change the base class from ActionForm to ValidatorForm.

2.Comment out the old validate() function on the legacy code. This makes it easy for you to “roll back” to the old validations if you detect a bug in the new validations.

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

217

3.As usual, declare the required validations in validation.xml. If not all validations can be moved to the Validator framework, use the two techniques described earlier to accommodate these special cases.

It’s wisest to do migrate incrementally, and use a unit-testing framework (see “Useful Links”) to test the changes.

Localizing Validations

In some situations, you might need to localize your validations. It’s rare to want to do this, and in most scenarios where you might want to do so (such as checking for zip codes), the validation can be misleading. I’ve discussed the pitfalls of localizing validations in Chapter 12, and given you a couple of useful hacks to help you do this if you need to.

The Validator framework does provide support for localizing validations, but the solution is a poor one. I should hasten to add that this isn’t a great failing since you should think twice (or three times!) before deciding to localize any validation.

Caution Do not confuse localizing validations with localizing output (e.g., error messages). The Validator framework provides good support for localizing error messages through the use of keys/value pairs on

Application.properties.

Recall that the <formset> tag is a containing tag for all your declared validations. In order to create validations specific to a locale, you’d have to put in a new <formset> in the root containing the <form-validation> tag.

This new <formset> tag would contain all necessary validations for the new locale (refer to Chapter 12 for a discussion on locales). Notice the emphasis on “all”—there’s no way for you to selectively override the validations for just a few fields; you’d have to specify the validations for every field again in the new <formset>.

The <formset> tag accepts three optional properties that you can use to specify a locale:

language: This is a ISO-639 identifier for a language. Examples are en for English or zh for Chinese.

country: This is an ISO-3166 identifier for a country. For example, the code US refers to the United States.

variant: This is a variant on a given language/country combination. The user usually can’t set this within the web browser.

If all these attributes are left unspecified, the <formset> element is taken to be the default one.