Download (SourceForge Hosted)




Writing an iScreen Custom Validator

There's a good chance that on any given project, the included iScreen validators won't be sufficient for all the project's needs. In this case, writing a custom validator is necessary. Unfortunately, it means writing code that is specific to the iScreen API. This document will describe how to use that API to write your own validators. The approach provided is to show a very trivial example and then show a more complicated version. If all else fails, JavaDoc is available.

The Trivial Case: The Search For 'foo'

In this trivial case, the validator is expecting a String and is looking for the substring of 'foo'. If it can't find it, then there's a failure.

All iScreen validators must implement the org.iscreen.Validator interface. In this trivial case, we'll use the org.iscreen.BaseValidator base class, as it provides some helpers to make things a little easier. The Validator interface has only two methods: the validate() method, and the constructBeanToValidate() method. The constructBeanToValidate() method is handled by the BaseValidator class, so our example only needs to implement the validate() method.

The method signature looks like this:

public void validate( ValidatorContext context, Object beanToValidate );

The ValidatorContext is used to report failures, and the second parameter is a bean that contains the properties to validate. Since we're using the BaseValidator, the type of the second parameter is org.iscreen.SimpleBean. This class has a getValue() method to give the object that is to be validated. However, the BaseValidator provides a helper method to get the ultimate String that needs to be validated: getStringValue().

Since our example needs a String and is checking for the substring of 'foo', the partial implementation would look like this:

public void validate( ValidatorContext context, Object beanToValidate ) { String str; str = getStringValue( beanToValidate ); if ( str == null || str.indexOf( 'foo' ) == -1 ) { //Report a failure. No 'foo' found! } }

Failures are really messages that get propagated to the user. Since these messages are configured and not hard-coded, they are set on the validator as properties. For the BaseValidator, there exists a single failure message with a property of 'defaultFailure.' This means there's a method on BaseValidator called setDefaultFailure(). This property is set via configuration. It's accessed by the validator via the getDefaultFailure() method. The full implementation of the validate() method is as follows:

public void validate( ValidatorContext context, Object beanToValidate ) { String str; str = getStringValue( beanToValidate ); if ( str == null || str.indexOf( 'foo' ) == -1 ) { //The getDefaultFailure() method is defined in the BaseValidator class. context.reportFailure( getDefaultFailure() ); } }

Notice that the validator doesn't need to know what the failure message looks like.

The configuration for this validator would look like this:

<validator id="FooValidator" class="my.FooValidator"> <failure property="defaultFailure">The failure message</failure> </validator>

Note that the value of the failure message can be stored in a properties file and accessed as a resource bundle this way:

<validator id="FooValidator" class="my.FooValidator"> <failure property="defaultFailure" resource-id="MyResources" key="fooFailure" /> </validator> <resource id="MyResources"> <resource-file file="messages" /> </resource>

Resource file messages.properties:

#default failure for foo validator fooFailure=The failure message

Advanced Usage: Looking For Something in Something

The BaseValidator is there to provide a default approach to a simple validator. However, there are a number of features that it ignores, and hence, when doing more complex validators, the BaseValidator should be ignored and the interface should be directly implemented.

Validators should be designed to be stateless. That means that they should be thread-safe and should not store data between calls to the validate() method. However, prior to the validate() method being called, a validator is configured. It's guaranteed to be fully configured prior to the validate() method (and the constructBeanToValidate() method) being called.

There are two aspects of the validator that are configured: constraints and failure messages. All of these are set via OGNL expressions (typically, just as JavaBean properties).


Constraints are configuration options that define how the validator is to act. For example, the StringValidator has a constraint called minLength that defines the minimum acceptable length of a String. In general, validators should provide default values for constraints, just in case they are not configured.

In addition, constraints are used to provide externally configured services to Validators (such as a DataSource). In this case, the "service" that is required is made available via the factory (see the user manual on how to configure the validation factory). The service is configured for the validator so that the validator has access to the service while validating.

Failure Messages

Failure messages are objects that represent failures (or warnings). They are configured so that the validator need not know anything specific about them, other than the particular one needed for a particular failure. These objects should only be stored and used when reporting failures (via the ValidatorContext passed in as part of the validate() method).

All configuration items are set once for the life of the validator. Though technically not considered a singleton, a validator is expected to be thread-safe and stateless (the configuration via constraints and failure messages are not considered state, as they are set once for the life of the validator).


Since validators are stateless, and since they have to be passed the information that they're validating, a mapping mechanism is provided to map the information from the originating object that's being validated to another object that the validator will actually reference. Another reason for this is a validator may validate any number of object types but must be able to know the type prior to validating. For example, the same validator the checks the length of a user's first name in the User object may be used to check the length of a product name in a Product object. By mapping the fields being validated to a type known to the validator, the validator can take advantage of type-safety and concentrate on what it's validating, and not dealing with a lot of reflection.

Mapping is done by both configuration and by the validation engine. The validator actually provides the object that it ultimately validates. It does this through it's constructBeanToValidate() method (though, technically, it doesn't need to be a JavaBean). The validator acts as a factory for the objects that it validates.

Prior to calling the validate() method on the validator, the validation framework calls the validator's constructBeanToValidate(), which provides it an instance of an object. Given the configuration, the framework then maps the incoming object's fields to the fields in the object provided by the validator via its constructBeanToValidate() method. The framework then passes the object the validator provided it back to the validator via its validate() method (but, the object now has the appropriate fields set with the values from the original object being validated). The validator never touches the original object being validated.

The BaseValidator implements the constructBeanToValidate() method by constructing a SimpleBean, which has a getValue() and setValue() method. For validators that only validate a single field whose types are the primitive wrappers, the SimpleBean is sufficient. However, if multiple fields need to be mapped, then the validator will have to provide its own 'beanToValidate'.

The Example

In the example for the more complex validator, the validator will be looking for a certain substring within a particular String. The substring being looked for is defined as another field.

The full code for the example follows:

import org.iscreen.Validator; import org.iscreen.ValidatorContext; public class SubstringValidator implements Validator { //There are two different failure messages because there are two possible "failures" private FailureMessage notFoundFailure; private FailureMessage tooManyFailure; //Always a default, public constructor. public SubstringValidator() {} //Using a special object that the validator knows about. public Object constructBeanToValidate() { //This should always return a new instance...remember that the validator must be thread-safe. return new SubstringObject(); } public void validate( ValidatorContext context, Object objectToValidate ) { String strToSearch; String strToFind; //The passed in object is guaranteed to be the same type returned by the //constructBeanToValidate() method. strToSearch = ( ( SubstringObject ) objectToValidate ).getValue(); strToFind = ( ( SubstringObject ) objectToValidate ).getSubStr(); //Check for the first failure. if ( strToSearch == null || strToSearch.indexOf( strToFind ) == -1 ) { //Note that the context allows us to report a failure with what's called a //'failure object' which is an object that can be referenced by the failure message. context.reportFailure( notFoundFailure, strToFind ); return; } if ( strToSearch.indexOf( strToFind ) != strToSearch.lastIndexOf( strToFind ) ) { context.reportFailure( tooManyFailure, strToFind ); return; } } //Need setters for the different failure messages public void setNotFoundFailure( FailureMessage failure ) { notFoundFailure = failure; } public void setTooManyFailure( FailureMessage failure ) { tooManyFailure = failure; } //Here's the 'validate bean' that the validator will use to get access to the fields to validate. private class SubstringObject() { private String subStr; private String value; public String getSubStr() { return subStr; } public void setSubStr( String str ) { subStr = str; } public String getValue() { return value; } public void setValue( String theValue ) { value = theValue; } } }

The configuration for this validator may look something like this:

<validator id="SubstringValidator" class="my.SubstringValidator"> <mapping from="someProperty" to="subStr" /> <mapping from="someOtherProperty" to="value" /> <label>My Field</label> <failure property="notFoundFailure">Could not find ${failure} in ${label}.</failure> <failure property="tooManyFailure">Too many of ${failure} found in ${label}.</failure> </validator>

See the DTD for the configuration file for alternatives to this configuration. The important pieces to notice are the multiple failure configurations and the multiple mappings to handle each field being mapped. In addition, notice to the reference to the failure object in the message text. This allows the failure object to be referenced in the message itself (as well as the label and other objects).

One final note is that though the context passed in is primarily for reporting validation failures, it also has access to other context-specific information, such as the specific locale that the current validation is using. This allows locale-specific validations by giving the Validator access to the current locale during its validation.