-
Notifications
You must be signed in to change notification settings - Fork 7
v0.7.3 Documentation (Tentative)
You are viewing documentation for an incomplete and unreleased version. Features and functionality described below are subject to change up until the release of this specific version.
Springjutsu Validation replaces standard JSR-303 validation annotations with robust XML validation definitions offering several possibilities not found in a standard bean validation library:
- Simple creation and definition of custom rules
- Options for form-specific contextual validation within Spring MVC and Spring Web Flow
- Execution of conditional validation rules based on the outcome of another rule
- Encapsulation of common patterns of multiple rules into reusable templates
- Programmatic access via Expression Language to request parameters and other request data
- Full i8n internationalization support by loading error messages and field labels from a spring message source
- Easily apply validation rules and conditional validation to collection members
In Springjutsu Validation all validation is comprised of combinations of rules.
<!-- A rule looks like this. -->
<rule path="address.zipCode" type="exactLength" value="5"/>
As you might guess, the above rule constrains a field named "zipCode" found on a sub-bean field named "address" to be exactly 5 characters long.
When a rule fails error messages are created by looking up message codes from a spring MessageSource using conventions which can be specified within the configuration options. This error message is registered as a field error on the standard Spring Errors object.
Let's assume that the address bean was of type org.mycompany.coolproject.model.PostalAddress in our project. Let's also assume we have the following message properties defined:
# look up error messages - errors.<rule type>
errors.exactLength={0} must be exactly {1} characters long.
# look up field labels - <simple class name>.<field name>
postalAddress.zipCode=Postal Code
Springjutsu Validation fills in the message args, and the generated error message is:
Postal Code must be exactly 5 characters long.
This is usage at its most basic. But, before looking at more advanced usage we'll cover how to acquire Springjutsu Validation and configure it for use within your application.
(Please note that Springjutsu Validation requires a minimum Spring Framework version of 3.1.0.RELEASE)
The first step in adding Springjutsu Validation to a project is to acquire the appropriate artifacts from Maven Central. The dependency is as follows:
<dependency>
<groupId>org.springjutsu</groupId>
<artifactId>validation</artifactId>
<version>0.7.3</version>
</dependency>
If you don't use maven for your dependencies, you can alternatively acquire the current validation jar directly from maven central: http://repo1.maven.org/maven2/org/springjutsu/validation/0.7.3/
Once the validation jar is safely nestled into your project, you can set it up as your default Validator with Spring by updating your Spring configuration thusly:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Note the validation namespace added to spring namespaces -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:validation="http://www.springjutsu.org/schema/validation"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springjutsu.org/schema/validation
http://www.springjutsu.org/schema/validation-0.7.3.xsd">
<!-- Create a springjutsu validation manager -->
<validation:configuration validatorName="springjutsuValidator" />
<!-- Enable Spring @MVC annotation driven controller model referencing our validator -->
<mvc:annotation-driven validator="springjutsuValidator"/>
<!-- Other beans... don't forget your message source! -->
</beans>
As you can see, a bean named "springjutsuValidator" is created by the validation:configuration namespace element, and is set as the default spring validator by name reference. For additonal information on registering MVC validators, consult the spring documentation: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/htmlsingle/#validation-mvc-configuring
This is all the basic configuration that is required to begin using Springjutsu Validation. Configuration options accessible through the validation:configuration element will be shown in later sections.
Rules are defined in XML using the element. The only required attribute for a rule is the "type" attribute, which indicates the type of rule being evaluated. The attributes declarable for a rule are:
Attribute Name | Usage |
---|---|
type | Indicates the rule type being evaluated |
path | Indicates the path to the field being validated. When not specified, the root object under validation is passed to the rule instead of a specified field. Can also be an EL string. |
value | Indicates an argument to a rule. Can be a static string or an EL string. Can be left unspecified for rules not requiring an argument. |
message | Indicates an error message to display instead of the rule's default message. Use to customize the displayed message when the outcome of a rule has special meaning. |
errorPath | Overrides "path" attribute to indicate the field that should receive the error. Use to validate one field, but report the error on a different one. |
collectionStrategy | See Validating Collection Members. |
Several rules come built into Springjutsu Validation and are configured by default. These are:
Rule Type | Usage |
---|---|
alphabetic | Will pass if the model is either all letters, empty, or null |
alphanumeric | Will pass if the model is all letters and numbers, empty, or null |
Will pass if the model is a valid email address, empty, or null | |
maxLength | Will pass if the String value of the model is less than or equal to the number of characters specified by the argument, empty, or null |
minLength | Will pass if the String value of the model is greater than or equal to the number of characters specified by the argument, empty, or null |
numeric | Will pass if the model is all numbers, empty, or null |
required | Will pass if the model is neither null or empty |
notEmpty | Same as required but has better semantics for use in conditional validation |
matches | Will pass if the String value of the model is the same as the String value of the argument |
If you would prefer that the default rules not be registered, they can be disabled through the configuration element:
<validation:configuration validatorName="springjutsuValidator">
<validation:rules-config addDefaultRuleExecutors="false"/>
</validation:configuration>
A rule is evaluated using a class called a RuleExecutor. Springjutsu validation provides a few extension options for creating custom RuleExecutor classes:
Class Name | Purpose |
---|---|
org.springjutsu.validation.executors.RuleExecutor | This is the most basic interface. It provides a simple boolean validate method which accepts a model to be validated, and an optional argument, and should return true if the rule passed. |
org.springjutsu.validation.executors.ValidWhenEmptyRuleExecutor | This is a convenient extension point for creating validation rules that will pass when the model is null or empty. Use it to create rules that shouldn't fail when the model isn't present. The doValidate method has the same semantics as the RuleExecutor's validate method. |
org.springjutsu.validation.executors.RegexMatchRuleExecutor | This is a convenient extension point for creating rules which should simply require that the String value of model being validated should match a regular expression returned by the abstract getRegularExpression method. It is the basis of the alphabetic, numeric, and alphanumeric rules. |
An important note is that all Springjutsu Validation Rule Executors are registered as spring beans. This means they can have autowired dependencies injected from the Spring Container.
Let's say we're writing an issue tracker and have a field that allows the user to specify the username of an assignee. We need to ensure that the assignee for that username exists and is active. Let's create a rule executor to fulfill this requirement.
public class ValidUsernameRuleExecutor extends ValidWhenEmptyRuleExecutor {
@Autowired
private UserService userService; // pretending this is part of our project.
@Override
public boolean doValidate(Object model, Object argument) {
User user = userService.findByUsername((String) model);
return user != null && user.isActive();
}
}
There we go. One rule executor using autowired dependencies to validate that our username corresponds to an existing and active user. Next we need to register the RuleExecutor with Springjutsu validation. We have two options.
Option 1: Use the ConfiguredRuleExecutor annotation at the class level.
// Make our rule available using the rule type "validUsername"
@ConfiguredRuleExecutor(name="validUsername")
public class ValidUsernameRuleExecutor extends ValidWhenEmptyRuleExecutor {
// implementation
}
Option 2: Configure the RuleExecutor manually in XML.
<validation:configuration validatorName="springjutsuValidator">
<validation:rules-config addDefaultRuleExecutors="false">
<!-- Make our rule available using the rule type "validUsername" -->
<validation:rule-executor name="validUsername" class="com.mycompany.project.validation.ValidUsernameRuleExecutor" />
</validation:rules-config>
</validation:configuration>
An XML validation file is actually a spring bean definition file. Validation rules are declared on a per-class basis: our recommendation is to have one file per class. Since this means adding multiple bean definition files to your spring context, you may want to review Spring's documentation on constructing contexts from multiple files. The use of ANT paths is especially simple. http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/htmlsingle/#resources-app-ctx
Each class has its rules defined in an entity element.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springjutsu.org/schema/validation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springjutsu.org/schema/validation
http://www.springjutsu.org/schema/validation-0.7.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- Going to define rules for our Person class -->
<entity class="com.mycompany.project.model.Person">
<!-- and rules can go here -->
<rule path="firstName" type="alphabetic" />
<rule path="firstName" type="maxLength" value="50" />
<!-- etc... -->
</entity>
</beans:beans>
For focus and brevity, the following examples will assume the above namespaces and wrapping beans:beans element are present, and include instead only the entity elements.
Let's say that we want to validate that the fields on a company's name and it's address are present. The classes look like this:
public class Company {
private String name;
private Address address;
// ... getters and setters ...
}
public class Address {
private String street;
private String city;
private String zipCode;
}
Then, we have a couple options. We could put all the validation on the Company object.
<entity class="com.mycompany.project.model.Company">
<rule path="name" type="required" />
<rule path="address.street" type="required" />
<rule path="address.city" type="required" />
<rule path="address.zipCode" type="required" />
</entity>
But, that may not be ideal, because we can't reuse those address rules on any other object that has an address. But if we look at the next section, we can take advantage of recursive sub bean validation and move those address rules out to an address entity.
Let's say we have rules declared for a Company class, and an Address class, and the Company class has a field of type Address.
<entity class="com.mycompany.project.model.Company">
<rule path="name" type="required" />
</entity>
<entity class="com.mycompany.project.model.Address">
<rule path="street" type="required" />
<rule path="city" type="required" />
<rule path="zipCode" type="required" />
</entity>
Now when we validate a Company object, the Address class rules will be applied to the Address-type field on the Company object as well. Springjutsu Validation will automatically validate sub bean fields of a parent object recursively when both the owning object's class and the field type have entity definitions.
There are several ways to delcaratively include or exclude fields from this behavior. For this example we'll have a Company object with an Address field that we want to validate when the company is validated, and a parentCompany field of type Company that we'll want to exclude from validation:
public class Company {
private String name;
private Address address; // we want to validate this
private Company parentCompany; // we don't want to validate this
// ... getters and setters ...
}
Option 1: Exclude parentCompany via XML: since address isn't excluded it will still be validated recursively.
<entity class="com.mycompany.project.model.Company">
<recursion-exclude propertyName="parentCompany" />
<!-- Rules go here as they normally do -->
</entity>
Option 2: Exclude parentCompany via bean annotation: since address isn't excluded it will still be validated recursively.
public class Company {
private String name;
private Address address;
@RecursiveValidationExclude // in package org.springjutsu.validation.rules
private Company parentCompany;
// ... getters and setters ...
}
Option 3: Include address via XML: since parentCompany isn't included it won't be validated recursively.
<entity class="com.mycompany.project.model.Company">
<recursion-include propertyName="address" />
<!-- Rules go here as they normally do -->
</entity>
Option 4: Include address via bean annotation: since parentCompany isn't included it won't be validated recursively.
public class Company {
private String name;
@RecursiveValidationInclude // in package org.springjutsu.validation.rules
private Address address;
private Company parentCompany;
// ... getters and setters ...
}
Option 5: Include / Exclude using your own annotations - Custom Include and Exclude annotations can be defined in the configuration element. This can be useful if you wanted to exclude all @Transient fields, for instance. These are usable in addition to the default annotations and XML include and exclude options.
<validation:configuration validatorName="springjutsuValidator">
<validation:rules-config>
<validation:recursion-exclude-annotation class="javax.persistence.Transient"/>
<validation:recursion-include-annotation class="javax.persistence.Embedded"/>
</validation:rules-config>
</validation:configuration>
public class Company {
private String name;
@Embedded
private Address address;
@Transient
private Company parentCompany;
// ... getters and setters ...
}
Springjutsu Validation allows the execution of certain rules to be tied to a specific Spring MVC form pattern or Web Flow flow & state in order to constrain fields only on the forms on which the constraints are required.
Let's say that we've already got a Customer class set up and that required fields are also set up for validation.
public class Customer {
private String firstName;
private String lastName;
private String userName;
private String emailAddress;
// ... getters and setters ...
}
<entity class="com.mycompany.project.model.Customer">
<!-- Always evaluated no matter what form we're on -->
<rule path="firstName" type="required" />
<rule path="lastName" type="required" />
<rule path="userName" type="required" />
<rule path="emailAddress" type="required" />
</entity>
This works fine for create and edit pages, but if we attempted to add a new search page using a Customer object to back the search form, we'd find that all fields would be required on the search form as well. Probably not what we intended.
To make the fields optional on the search, but required on create and edit, we can apply form constraints to the rules definition.
For Spring MVC it looks like this:
<entity class="com.mycompany.project.model.Customer">
<!-- MVC Path Variable references are treated as wild cards. -->
<!-- Comma delimit multiple form paths -->
<form path="/customers/new, /customers/{id}/edit">
<rule path="firstName" type="required" />
<rule path="lastName" type="required" />
<rule path="userName" type="required" />
<rule path="emailAddress" type="required" />
</form>
</entity>
We can do the same thing for Spring Web Flow:
<entity class="com.mycompany.project.model.Customer">
<!-- colon-separate flow path from flow state -->
<!-- Comma delimit multiple form paths -->
<form path="/customers/customer-creation:customerForm, /customers/customer-edit:customerForm">
<rule path="firstName" type="required" />
<rule path="lastName" type="required" />
<rule path="userName" type="required" />
<rule path="emailAddress" type="required" />
</form>
</entity>
With the above, rules will apply to the create and edit forms, but not on the search form.
Form paths can also include ANT-style path wildcards (/* and /**), Spring MVC path variable references like {id} (which are treated the same as a *), and if all else fails, regular expression syntax.
An important note is that form-constrained rules are not applicable for recursive sub bean validation covered in the previous section. It is recommended instead that all form-specific rules be rooted from the class of the object backing the form. This makes your form-specific rules far easier to manage.
Conditional validation refers to executing one or more rules when a specific rule passes. Accomplishing this is simple: open the tag of the rule that acts as the condition, and nest the conditional rule elements inside. For example:
<entity class="com.mycompany.project.model.Address">
<!-- When country is US, zipCode should be numeric, five digits -->
<rule path="country" type="matches" value="US">
<rule path="zipCode" type="numeric" />
<rule path="zipCode" type="exactLength" value="5" />
</rule>
</entity>
In this example, if the "country matches US" rule passed, the child rules would be executed. If the parent rule failed however, no error message would be generated. Instead, the child rules would be skipped.
With Springjutsu Validation it is easy to apply rules to each member of a collection, or to the collection object itself.
There are two important restrictions to the functionality in this section:
- The collection must be a List or an Array (so that Spring may index individual members on the errors object)
- When using a List, the type of the members must be parameterized on the type of the collection in the field declaration.
For this example, we'll be pretending we have an inventory bulk-update form which allows us to modify the attributes of many inventory items at once. The setup looks like this:
public class Inventory {
private String inventoryName;
private List<InventoryItem> items;
// ... getters and setters ...
}
public class InventoryItem {
private String description;
private Integer value;
private String responsibleParty;
}
<entity class="com.mycompany.project.model.Inventory">
<rule path="items.description" type="required" />
<rule path="items.value" type="required" />
<rule path="items.responsibleParty" type="required" />
</entity>
In the above setup, the description, value and responsible party fields for each InventoryItem in the items collection will be required. When a rule path targets a collection, the rule is applied to all members of the collection. If a member fails validation, that member will receive the error on the spring errors object using a collection index.
We can also apply conditional validation to collection members. Let's assume we wrote a greaterThan RuleExecutor, and we only want to require a responsibleParty for items with a value greater than 500.
<entity class="com.mycompany.project.model.Inventory">
<rule path="items.description" type="required" />
<rule path="items.value" type="required" />
<rule path="items.value" type="greaterThan" value="500">
<rule path="items.responsibleParty" type="required" />
</rule>
</entity>
In this case, items.responsibleParty rule will be applied to the same collection member that passed the value check, iteratively.
It's also worth noting that we can take advantage of recursive sub bean validation here also. The following setup is equivalent to the above:
<entity class="com.mycompany.project.model.Inventory"/>
<entity class="com.mycompany.project.model.InventoryItem">
<rule path="description" type="required" />
<rule path="value" type="required" />
<rule path="value" type="greaterThan" value="500">
<rule path="responsibleParty" type="required" />
</rule>
</entity>
Since Inventory and InventoryItem are both defined in validation, the List on the Inventory object is subject to recursive sub bean validation - except that in this case, each member of the collection will be validated. The standard include / exclude options can be applied to the items field as well.
Finally, we can also pass the collection instance itself to a rule, by overriding the default collectionStrategy rule attribute.
<entity class="com.mycompany.project.model.Inventory">
<rule path="items" type="ruleThatAcceptsACollectionModel" collectionStrategy="validateCollectionObject" />
</entity>
Springjutsu Validation allows you to encapsulate sets of rules into reusable templates which can later be applied to various fields on a bean. This allows you reuse a block of validation in multiple places within your application.
A common use is creating short and full address templates. Here, we'll create validation for a short location address which only requires city and zip, as well as a full address which requires all fields.
public class Address {
private String street;
private String city;
private String state;
private String country;
private String zipCode;
// getters and setters
}
<entity class="com.mycompany.project.model.Address">
<!-- Define a short address req'ing city and zip -->
<template name="shortAddress">
<rule path="city" type="required"/>
<rule path="zipCode" type="required"/>
</template>
<!-- Define a full address requiring all -->
<template name="fullAddress">
<rule path="street" type="required"/>
<rule path="country" type="required"/>
<rule path="country" type="matches" value="US">
<rule path="state" type="required"/>
</rule>
<!-- note that templates can reference other templates -->
<template ref="shortAddress"/>
</template>
</entity>
Note that we were able to simplify the second template by referencing the first one. Templates can include both rules and references to other templates. The only exception is that a template cannot directly or indirectly reference itself. If this occurs, a CircularValidationTemplateReferenceException will be thrown when the loop is detected.
To apply the template, we create a template-ref element. Here's a Commuter object with a full home address and a partial work location address.
public class Commuter {
// other fields
private Address homeAddress;
private Address workLocation;
// getters and setters
}
<entity class="com.mycompany.project.model.Commuter">
<!-- basePath identifies the root of the rules in the template -->
<template ref="fullAddress" basePath="homeAddress"/>
<template ref="shortAddress" basePath="workLocation"/>
</entity>
Note that we select a basePath to identify the field that acts as the root of validation for the template. The object referred to by the path must be or extend the class the template is declared for. If it is not, an IllegalTemplateReferenceException will be thrown.
An object under validation is subject to the rules defined for an entity matching its class, as well as the rules defined for any entity matching any direct superclass up to Object.class. This applies to all features in Springjutsu Validation, including the recursive sub bean validation, collection types, templates, etc.
Springjutsu Validation also allows for the use of Expression Language within the path and value attributes of a rule. The implementation is Spring EL, documented at http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/htmlsingle/#expressions
Note that although SPEL expressions are typically formatted #{expression}, Springjutsu Validation expects the ${expression} EL format.
We expose an EL context which provides for access to several named scopes, depending on the current type of web request, or the absence thereof. If a property is prefixed with one of these scope names, it will be explicitly searched for within that scope. If the prefix is omitted, a property will be searched through these scopes in the order listed below.
Scope Name | Description | Requirements |
---|---|---|
model | The model bean being validated | N/A |
requestScope | The Spring Web Flow Request Scope | Web Flow Request |
flashScope | The Spring Web Flow Flash Scope | Web Flow Request |
viewScope | The Spring Web Flow View Scope | Web Flow Request |
flowScope | The Spring Web Flow Flow Scope | Web Flow Request |
conversationScope | The Spring Web Conversation Scope | Web Flow Request |
requestParameters | The HTTP Request Parameters | Web Flow or MVC Request |
requestAttributes | The HTTP Request Attributes | Web Flow or MVC Request |
session | The HTTP Session | Web Flow or MVC Request |
Example: ensure that a Confirm Password box matches a Password box, when the Confirm Password field does not bind to a bean, and instead exists only in request parameters.
<entity class="com.mycompany.project.model.Applicant">
<rule path="password" type="matches" value="${requestParameters.confirmPassword}" />
</entity>
When a validation rule fails, the error message is loaded from a configured Spring Message source using, by default, the key "errors." where rule type is the configured type name for the RuleExecutor that returned a failure value.
An error message can also include two argument placeholders:
- {0} will be replaced by a label for the field that failed validation.
- {1} will be replaced by the value of the argument passed to the rule. If the argument was an EL string referencing a property on the same bean, then {1} will instead be replaced by the field label of the argument.
Some examples:
# To log the error "You must enter a valid email"
# When the rule <rule path="emailAddress" type="email"/> fails
errors.email=You must enter a valid email
# To log the error "First name is required"
# When the rule <rule path="firstName" type="required"/> fails
errors.required={0} is required
# To log the error "First name must be at least 5 characters long"
# When the rule <rule path="firstName" type="minLength" value="5"/> fails
errors.minLength={0} must be at least {1} characters long.
Field labels are loaded by convention: the simple class name of the class on which the field lies with the first letter changed to lower case, followed by a period, then the name of the field that failed validation. If the field is on a nested path, the prefix is the simple class name of the class on which the last field in the nested path lies. For example if a Company object has a collection of Customer objects, and the Customer object has an Address object, and the error path is "customers[0].mailingAddress.zipCode", then the resultant field path is "address.zipCode".
In the event that you are validating a subclass instance, the super class simple names will also be available as candidates for field label prefixes. For example let's say you extended a Person class to create a Customer class. The "firstName" field appears on Person.class, and validation on this field fails when validating a Customer object. Springjustu Validation will first check for a message using the code "customer.firstName", then using "person.firstName" if it is not found. If this behavior is not desired, it can be disabled in the message configuration element:
<validation:configuration validatorName="testValidationManagerName">
<validation:message-config enableSuperclassFieldLabelLookup="false" />
</validation:configuration>
The default prefix for error messages can be changed in the configuration element. Although field labels don't have a default prefix, one can also be set in the configuration element.
<validation:configuration validatorName="testValidationManagerName">
<validation:message-config
errorMessagePrefix="validation.errors."
fieldLabelPrefix="field.labels." />
</validation:configuration>
A typical MVC controller validation check looks like this (example taken from Spring docs):
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
However, Springjutsu Validation also provides some convenience functions to simplify handling redirection on validation failure and success. Using the @SuccessView and @ValidationFailureView annotations, we can rewrite this method to be a bit more straightforward.
@SuccessView("/appointments")
@ValidationFailureView("appointments/new")
@RequestMapping(method = RequestMethod.POST)
public void add(@Valid AppointmentForm appointment) {
appointmentBook.addAppointment(appointment);
}
To activate support for these annotations, you'll need to configure them within your spring config by adding this line from the validation namespace:
<!-- @SuccessView and @ValidationFailureView support -->
<validation:mvc-annotations />
If you are instead already utilizing a Spring ViewTranslator to perform redirection by conventions, you can also utilize that view translator to re-render your form on validation failure by registering a ViewTranslatorValidationFailureRedirector in your spring context
<bean class="org.springjutsu.validation.mvc.ViewTranslatorValidationFailureRedirector "/>
In this event, the controller logic further boils down to:
@RequestMapping(method = RequestMethod.POST)
public void add(@Valid AppointmentForm appointment) {
appointmentBook.addAppointment(appointment);
}
With the success view handled by the ViewTranslator, and the failure view also handled by the viewTranslator which will be invoked by the ViewTranslatorValidationFailureRedirector.