Sealyu

--- 博客已迁移至: http://www.sealyu.com/blog

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  618 随笔 :: 87 文章 :: 225 评论 :: 0 Trackbacks

1. Commons Validator

[Important] Important

In version 0.4, the commons validator support was moved from the commons module to the validation module. As a consequence, all related classes in the commons module were deprecated and will be removed in version 0.5.

Please note that due to this change, the packages were renamed. Apart from that, the functionality stayed more or less the same with the exception of some bug fixes and small improvements. Also note that all bug fixes and maintenance will be done on the validation module solely.

The Commons Validator is a library that allows you to perform validation based on rules specified in XML configuration files.

TODO: Describe the concepts of Commons Validator in more details.

1.1. Configure an Validator Factory

Firstly you need to configure the Validator Factory which is the factory to get Validator instances. To do so, the support provides the class DefaultValidatorFactory in the package org.springmodules.validation.commons

You need to specify with the property validationConfigLocations the file containing the Commons Validator rules and the file containing the validation rules specific to the application.

The following code shows how to configure this factory.

<bean id="validatorFactory"
class="org.springmodules.validation.commons.DefaultValidatorFactory">
<property name="validationConfigLocations">
<list>
<value>/WEB-INF/validator-rules.xml</value>
<value>/WEB-INF/validation.xml</value>
</list>
</property>
</bean>

1.2. Use a dedicated validation-rules.xml

The file validation-rules.xml must contain Commons Validator elements based on classes provided by the support of this framework in Spring Modules.

For example, the configuration of the entities "required" and "requiredif" must be now in the validation-rules.xml file.

<validator name="required"
classname="org.springmodules.validation.commons.FieldChecks"
method="validateRequired"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.springframework.validation.Errors"
msg="errors.required">

<javascript><![CDATA[
(...)
]]></javascript>
</validator>

<validator name="requiredif"
classname="org.springmodules.validation.commons.FieldChecks"
method="validateRequiredIf"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.springframework.validation.Errors,
org.apache.commons.validator.Validator"
msg="errors.required">
</validator>

The validation sample of the distribution provides a complete validation-rules.xml based on the classes of the support.

You must note that the support of validwhen is not provided at the moment in the support. However, some codes are provides in JIRA. For more informations, see the issues MOD-38 and MOD-49.

1.3. Configure a Commons Validator

Then you need to configure the Validator itself basing the previous Validator Factory. It corresponds to an adapter in order to hide Commons Validator behind a Spring Validator.

The following code shows how to configure this validator.

<bean id="beanValidator" class="org.springmodules.validation.commons.DefaultBeanValidator">
<property name="validatorFactory" ref="validatorFactory"/>
</bean>

1.4. Server side validation

Spring MVC provides the implementation SimpleFormController of the interface Controller in order to process HTML forms. It allows a validation of informations processing by the controller by using the property validator of the controller. In the case of Commons Validator, this property must be set with the bean beanValidator previously configured.

The following code shows how to configure a controller which validates a form on the server side using the support of Commons Validator.

<bean id="myFormController" class="org.springmodules.sample.MyFormController">
(...)
<property name="validator" ref="beanValidator"/>
<property name="commandName" value="myForm"/>
<property name="commandClass" value="org.springmodules.sample.MyForm"/>
(...)
</bean>

The beanValidator bean uses the value of the property commandClass of the controller to select the name of the form tag in the validation.xml file. The configuration is not based on the commandName property. For example, with the class name org.springmodules.sample.MyForm, Commons Validator must contain a form tag with myForm as value of the name property. The following code shows the contents of this file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1.dtd">

<form-validation>
<formset>
<form name="myForm">
<field property="field1" depends="required">
<arg0 key="error.field1" />
</field>
<field property="field2" depends="email">
<arg0 key="error.field2" />
</field>
</form>
</formset>
</form-validation>

1.5. Partial Bean Validation Support

Partial validation support enables partial validation of beans where not all properties are validated but only selected ones.

Commons validator enables partial validation by specifying the page attribute for each field in the form configuration:

<form name="personDataWizard">
<field property="firstName" depends="required" page="0">
<arg0 key="person.first.name" />
</field>
<field property="lastName" depends="required" page="0">
<arg0 key="person.last.name" />
</field>
<field property="email" depends="required,email" page="0">
<arg0 key="person.email" />
</field>
<field property="password" depends="required" page="1">
<arg0 key="person.password" />
</field>
<field property="verifyPassword" depends="validwhen" page="1">
<arg0 key="person.password.not.matching" />
<var>
<var-name>test</var-name>
<var-value>(*this* == password)</var-value>
</var>
</field>
</form>

The org.springmodules.validation.commons.ConfigurablePageBeanValidator and org.springmodules.validation.commons.DefaultPageBeanValidator classes support partial validation by setting their page property. The value of this property will be matched with the page attribute in the form configuration, and only the fields with the appropriate page configured will be validated.

The following is an example of a partial validation support usage within a wizard controller:

<bean id="personWizardController" class="PersonWizardController">
<property name="pages">
<list>
<value>personPage0</value>
<value>personPage1</value>
</list>
</property>
<property name="allowDirtyForward" value="false"/>
<property name="validators">
<list>
<ref bean="pageValidator0"/>
<ref bean="pageValidator1"/>
</list>
</property>
<property name="commandName" value="person"/>
<property name="commandClass" value="PersonData"/>
</bean>

<bean id="pageValidator0" class="ConfigurablePageBeanValidator" parent="pageValidator">
<property name="page" value="0"/>
</bean>

<bean id="pageValidator1" class="ConfigurablePageBeanValidator" parent="pageValidator">
<property name="page" value="1"/>
</bean>

<bean id="pageValidator" abstract="true">
<property name="formName" value="personDataWizard"/>
<property name="validatorFactory" ref="validatorFactory"/>
</bean>

...

The controller will look like this:

public class PersonWizardController extends AbstractWizardFormController {

...

protected void validatePage(Object command, Errors errors, int page) {
Validator[] validators = getValidators();
for (int i=0; i<validators.length; i++) {
Validator validator = validators[i];
if (validator instanceof PageAware) {
if (((PageAware)validator).getPage() == page) {
validator.validate(command, errors);
}
}
}
}
}

1.6. Client side validation

The support of Commons Validator in Spring Modules provides too the possibility to use a client side validation. It provides a dedicated taglib to generate the validation javascript code. To use this taglib, we firstly need to declare it at the beginnig of JSP files as following.

<%@ tglib uri="http://www.springmodules.org/tags/commons-validator" prefix="validator" %>

You need then to include the generated javascript code in the JSP file as following by using the javascript tag.

<validator:javascript formName="account"
staticJavascript="false" xhtml="true" cdata="false"/>

At last, you need to set the onSubmit attribute on the form tag in order to trigger the validation on the submission of the form.

<form method="post" action="(...)" onsubmit="return validateMyForm(this)">

2. Valang

Valang (Va-lidation Lang-uage), provides a simple and intuitive way for creating spring validators. It was initially create with three goals in mind:

  • Enables writing validation rules quickly, without the need of writing classes or even any java code.

  • Ease the use of Spring validation tools.

  • Make validation rules compact, readable and easily maintainable.

Valang is built upon two major constructs - The valang expression language and valang validators. The former is a generic boolean expression language that enables expressing boolean rule in a "natural language"-like fashion. The later is a concrete implementation of the Spring Validator interface that is built around the expression language.

Before going into details, lets first have a look at a small example, just to have an idea of what valang is and how it can be used. For this example, we'll assume a Person class with two properties - firstName and lastName. In addition, there are two main validation rules that need to be applied:

  • The first name of the person must be shorter than 30 characters.

  • The last name of the person must be shorter than 50 characters.

One way of applying these validation rules (and currently the most common one) is to implement the Validator interface specifically for the Person class:

public class PersonValidator implements Validator {

public boolean supports(Class aClass) {
return Person.class.equals(aClass);
}

public void validate(Object person, Errors errors) {
String firstName = ((Person)person).getFirstNam();
String lastName = ((Person)person).getLastName();
if (firstName == null || firstName.length() >= 30) {
errors.reject("first_name_length", new Object[] { new Integer(30) },
"First name must be shorter than 30");
}
if (lastName == null || lastName.length() >= 50) {
errors.reject("last_name_length", new Object[] { new Integer(50) },
"Last name must be shorter than 50");
}
}
}

While this is a perfectly valid approach, it has its downsides. First, it is quite verbose and time consuming - quite a lot of code to write just for two very simple validation rules. Second, it required an additional class which clutters the code (in case it is an inner-class) or the design - just imagine having a validator class for each of the domain model objects in the application.

The following code snippet shows how to create a valang validator to apply the same rules as above:

<bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
<property name="valang">
<value>
<![CDATA[
{ firstName : length(?) < 30 : 'First name too long' : 'first_name_length' : 30}
{ lastName : length(?) < 50 : 'Last name too long' : 'last_name_length' : 50 }
]]>
</value>
</property>
</bean>

There are a few things to notice here. First, no new class is created - with valang, one can reuse a predefined validator class (as shown here). Second, This validator is not part of the java code, but put in the application context instead - In the above case, the ValangValidator is instantiated and can be injected to other objects in the system. Last but not least, The validation rules are defined using the valang expression language which is very simple and quick to define.

The following two sections will elaborate on the expression language and the use of the Valang validator in greater details.

2.1. Valang Syntax

The valang syntax is based on the valang expression language and the valang validation rule configuration. As mentioned above, the former is a boolean expression language by which the validation rules predicates (conditions) are expressed. The later binds the rule predicates to a key (usually a bean property), error message, and optionally error code and arguments.

2.1.1. Rule Configuration

Here is the basic structure of the valang rule configuration:

{ <key> : <predicate_expression> : <message> [: <error_code> [: <args> ]] }
  • <key> - The key to which the validation error will be bound to. (mandatory)

  • <predicate_expression> - A valang expression that defines the predicate (condition) of the validation rule. (mandatory)

  • <message> - The error message of the validation rule. The message is mandatory but can be an empty string if not used. This message is also used as the default message in case the error code could not be resolved. (mandatory)

  • <error_code> - An error code that represents the validation error. Used to support i18n. (optional)

  • <args> - A comma separated list of arguments to associate with the error code. When error codes are resolved, this arguments may be used in the resolved message. (optional)

2.1.2. Expression Language

As mentioned, the valang expression language is used to define the predicate to be associated with the validation rule. The expression is always evaluated against a context bean. The expression can be defined as follows:

<expression> ::= <expression> ( ( "AND" | "OR" ) <expression> )+ | <predicate>

The <predicate> in an evaluation that is composed of operators, literals, bean properties, functions, and mathematical expressions.

Operators

The following are the supported operators:

  • Binary Operators:

    • String, boolean, date and number operators:

      • = | == | IS | EQUALS

      • != | <> | >< | IS NOT | NOT EQUALS

    • Number and date operators:

      • > | GREATER THAN | IS GREATER THAN

      • < | LESS THAN | IS LESS THAN

      • >= | => | GREATER THAN OR EQUALS | IS GREATER THAN OR EQUALS

      • <= | =< | LESS THAN OR EQUALS | IS LESS THAN OR EQUALS

  • Unary Operators:

    • Object operators:

      • NULL | IS NULL

      • NOT NULL | IS NOT NULL

    • String operators:

      • HAS TEXT

      • HAS NO TEXT

      • HAS LENGTH

      • HAS NO LENGTH

      • IS BLANK

      • IS NOT BLANK

      • IS UPPERCASE | IS UPPER CASE | IS UPPER

      • IS NOT UPPERCASE | IS NOT UPPER CASE | IS NOT UPPER

      • IS LOWERCASE | IS LOWER CASE | IS LOWER

      • IS NOT LOWERCASE | IS NOT LOWER CASE | IS NOT LOWER

      • IS WORD

      • IS NOT WORD

  • Special Operators:

    • BETWEEN

    • NOT BETWEEN

    • IN

    • NOT IN

    • NOT

These operators are case insensitive. Binary operators have a left and a right side. Unary operators only have a left side.

Value types on both sides of the binary operators must always match. The following expressions will throw an exception:

name > 0
age == 'some string'
BETWEEN / NOT BETWEEN Operators

The BETWEEN and NOT BETWEEN operators have the following special syntax:

<between_operator> ::= <left_side> BETWEEN <value> AND <value>
<not_between_operator> ::= <left_side> NOT BETWEEN <value> AND <value>

Both the left side and the values can be any valid combination of literals, bean properties, functions and mathematical operations.

Examples:

width between 10 and 90
length(name) between minLength and maxLength
IN / NOT IN Operators

The IN and NOT IN operators have the following special syntax:

<in_operator> ::= <left_side> IN <value> ( "," <value> )*
<not_in_operator> ::= <left_side> NOT IN <value> ( "," <value> )*

Both the left side and the values can be any valid combination of literals, bean properties, functions and mathematical operations.

There's another special syntax where a java.util.Collection, java.util.Enumeration, java.util.Iterator or object array instance can be retrieved from a bean property. These values are then used as right side of the operator. This feature enables to create dynamic sets of values based on other properties of the bean.

<special_in_operator> ::= <left_side> IN "@"<bean_property>
<special_not_in_operator> ::= <left_side> NOT IN "@"<bean_property>

Examples:

size in 'S', 'M', 'L', 'XL'
size in @sizes
NOT Operator

The not operator has the following special syntax:

<not_operator> ::= "NOT" <expression>

This operator inverses the result of one or a set of predicates.

Literals

Four type of literals are supported by valang: string, number, date, and boolean.

Strings are quoted with single quotes:

'Bill', 'George', 'Junior'

Number literals are unquoted and are parsed by java.math.BigDecimal:

0.70, 1, 2000, -3.14

Date literals are delimited with square brackets and are parsed upon each evaluation by a special date parser. [TODO: write documentation for date parser]

[T<d], [2005-05-28]

Boolean literals are not quoted and have the following form:

<boolean> ::= ( "TRUE" | "YES" | "FALSE" | "NO" )
Bean Properties

As mentioned above, the valang always evaluates the expressions against a context bean. Once can access this bean's properties directly within the expression. To better understand how this works lets assume a Person class with the following properties:

  • name (String)

  • address (Address)

  • specialFriends (Map<String, Object>)

  • friends (Person[])

  • enemies (List<Person>)

The Address class has the following properties:

  • street (String)

  • city (String)

  • Country (String)

The context bean properties can be accessed directly by using their names:

name, address, attributes

Accessing nested properties is also supported by using a dot-separated expression. For example, accessing the street of the person can be done as follows:

address.street

List and/or array elements can be access by their index number as follows:

friends[1].name
enemies[0].address.city

Map entries can also be accessed by their keys:

specialFriends[bestFriend].name
Functions

Valang expressions can contain functions. A function is basically an operation which accepts arguments and returns a result. Functions can accept one or more arguments where each may be either a literal, bean property, or a function as described in the following definition:

function ::= <function_name> "(" <arg> [ "," <arg> ]* ")"
<arg> ::= <literal> | <bean_property> | <function>

Valang ships with the following predefined functions:

Table 11.1. Functions

NameDescription
length Returns the size of the passed in collection or array. If the passed in argument is neither, the length of the string returned from the toString() call on the passed in argument.
len See length above
size See length above
count See length above
match Matches the given regular expression (first argument) to the string returned from the toString() call on the passed in value (second argument).
matches See match above.
email Returns true if the string returned from the toString() call on the passed in argument represents a valid email
upper Converts the string returned from the toString() call on the argument to upper case.
lower Converts the string returned from the toString() call on the argument to lower case.
! Not operation on a boolean value.
resolve Wrap string in org.springframework.context.support.DefaultMessageSourceResolvable.
inRole Accepts a role name as an argument and returns true if the current user has this role. This function uses Acegi to fetch the current user.

Examples:

length(?)
size(upper('test'))
upper(address.city)

One of the more powerful features in Valang expression language is that it is extensible with custom functions. To add a custom function one first needs to implement the org.springmodules.validation.valang.functions.Function interface or extend the org.springmodules.validation.valang.functions.AbstractFunction. Then, when using the ValangValidatorFactoryBean or ValangValidator, register the new function with the customFunctions property using the function name as the key. [TODO: show an example of a custom function]

Mathematical Expressions

The following mathematical operators are supported:

  • +

  • -

  • *

  • / | div

  • % | mod

Parentheses are supported and expression are parsed left to right so that

2 - 3 + 5 = 4

Values in the mathematical expression can be literals, bean properties, and functions.

Examples:

(2 * (15 - 3) + ( 20 / 5 ) ) * -1
(22 / 7) - (22 div 7)
10 % 3
length(?) mod 4

2.2. Valang Validator Support

As we saw in the previous chapter, Valang offers quite a reach and powerful expression language to represent the validation rules. Language that for most cases relieves the user from creating custom Validator classes.

The only missing piece of the puzzle now is to see how this expression language and the validation rule configuration integrate with Spring validation support.

The 2 most important constructs of Spring validation are the org.springframework.validation.Validator and org.springframework.validation.Errors classes. The Errors class serves as a registry for validation errors that are associated with an object (a.k.a the target object). The Validator interface provides a mechanism to validate objects and register the various validation error within the passed in Errors.

Valang ships with some support classes that leverage the power of the Valang expression language and validation rule configuration, and integrates nicely with Spring validation. The most important of them all is the org.springmodules.validation.valang.ValangValidator class.

2.2.1. ValangValidator

The org.springmodules.validation.valang.ValangValidator class is a concrete implementation of Spring's Validator interface. The most important property of this validator is the valang property.

The valang property is of type java.lang.String and holds a textual representation of the validation rules that are applied by the validator. We saw in the previous section that a single validation rule is represented in valang using the following format:

{ <key> : <predicate_expression> : <message> [: <error_code> [: <args> ]] }

Since, a validator may apply more then just one rule, the valang property accepts a set of such rule definitions.

Example:

{ firstName : length(?) < 30 : 'First name too long' : 'first_name_length' : 30}
{ lastName : length(?) < 50 : 'Last name too long' : 'last_name_length' : 50 }

There are two ways to use the valang validator. It can be explicitly instantiated and initialized with the rule definitions by calling the setValang(String) method on it. But the recommended way is actually to let the Spring IoC container do this job for you. The valang validator was design as a POJO specifically for that reason - to easily define it within Spring application context and inject it to all other dependent objects in the application.

Here is an example of how to define a simple valang validator within the application context:

<bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
<property name="valang">
<value>
<![CDATA[
{ firstName : length(?) < 30 : 'First name too long' : 'first_name_length' : 30}
{ lastName : length(?) < 50 : 'Last name too long' : 'last_name_length' : 50 }
]]>
</value>
</property>
</bean>

This validator defines two validation rules - one for the maximum size of the first name of the person and the other for the maximum size of the last name of the person.

Also notice that the above validator is unaware of the object type it validates. The valag validator is not restricted to a specific class to be validated. It will always apply the defined validation rules as long as the validated object has the validated properties (firstName and lastName in this case).

This configuration should be enough for most cases. But there are some cases in which you need to apply extra configuration. With ValangValidator it is possible to register custom function (thus, extend the valang expression language). This can be done by registering the functions within the customFunctions property, where the function name serves as the registration key.

Here is an example of a valang validator configuration with a custom function:

<bean id="personValidator" class="org.springmodules.validation.valang.ValangValidator">
<property name="customFunctions">
<map>
<entry key="doIt">
<value>org.springmodules.validation.valang.functions.DoItFunction</value>
</entry>
</map>
</property>
<property name="valang">
<value>
<![CDATA[
{ firstName : doIt(?) and length(?) < 30 : 'First name too long' : 'first_name_length' : 30}
{ lastName : length(?) < 50 : 'Last name too long' : 'last_name_length' : 50 }
]]>
</value>
</property>
</bean>

It is also possible to register extra property editors and custom date parsers for valang to use. For more details about valang validator configuration options, please refer to the class javadoc.

NOTE: Until version 0.3 the org.springmodules.validation.valang.ValangValidatorFactoryBean class served the same purpose as the ValangValidator. In version 0.4, this class was deprecated and is planned to be removed in version 0.5.

posted on 2009-04-20 09:21 seal 阅读(2608) 评论(0)  编辑  收藏 所属分类: Spring

只有注册用户登录后才能发表评论。


网站导航: