To add validators you make use of the Annotation Factory pattern, a technique used throughout Strecks. This enables you to add validators and other customizations without changing any existing configuration or Java files. There are three steps required to add a new validator.
Write your validator, which needs to implement the interface Validator. The interface defines a single method:
public boolean validate(T value);
where T
is a Java 5 generic type extending Object.
In other words, validation is supported not only on Strings but any object.
Bear in mind that, Strecks supports two types of validators:
validators which use the raw or existing value
of an ActionForm
property, and validators which use a converted value.
IntegerRangeValidator
is very straightforward, because
it does not need to do any type conversion:
public class IntegerRangeValidator extends IntegerValidator implements Validator<Integer> { private int min = Integer.MIN_VALUE; private int max = Integer.MAX_VALUE; public IntegerRangeValidator() { super(); } public boolean validate(Integer value) { boolean ok = super.validate(value); if (!ok) return false; return GenericValidator.isInRange(value.intValue(), min, max); } //getters and setters omitted }
Create an annotation for the validator. The purpose of the annotation is to configure the validator, so any
configuration parameters will need to be specified in the annotation definition.
For example @ValidateIntegerRange
annotation
has the following form:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ValidatorFactoryClass(ValidateIntegerRangeFactory.class) public @interface ValidateIntegerRange { String key(); int order() default 999; int min() default Integer.MIN_VALUE; int max() default Integer.MAX_VALUE; }
An example usage is shown below, with the key being the String
used to look up validation error messages from the MessageResources
database:
@ValidateIntegerRange(key = "not.in.range", min = 0, max = 1) public void setIntegerRangeValue(String integerRangeValue) { this.integerRangeValue = integerRangeValue; }
Notice the @ValidateIntegerRange
definition uses the
@ValidatorFactoryClass
annotation.
This annotation determines which factory class, or ValidatorFactory
implementation,
is needed to create the Validator
instance from the annotation.
ValidateIntegerRangeFactory
is shown below:
public class ValidateIntegerRangeFactory extends BaseFactory { public ValidatorWrapper create(Annotation annot, Method method) { ValidateIntegerRange annotation = (ValidateIntegerRange) annot; IntegerRangeValidator validator = new IntegerRangeValidator(); validator.setMin(annotation.min()); validator.setMax(annotation.max()); List<Object> parameters = new ArrayList<Object>(); parameters.add(annotation.min()); parameters.add(annotation.max()); return createWrapper(validator, annotation.key(), annotation.order(), parameters, method); } }
As well as instantiating the Validator
, the ValidatorFactory
creates a ValidatorWrapper
with additional information required at runtime for validation. Most of the latter
task is performed by the BaseFactory
implementation.
The convention is that for any new validator to use a String key for looking up the appropriate validation message from a message resource bundle. When creating these messages, it is common to parameterize these with values. For example, a message could be:
days.outside.range=The number of days specified {0} is outside the range of {1} to {2}
By default, the first value available for parameterizing the message is the inputted value. Any other
values available depend on what parameters have been added in the ValidatorFactory
implementation.
In our ValidateIntegerRangeFactory
implementation above, the annotation's min and max attributes
have been added.
If you want to depart from this scheme in your own implementation, you can override
BaseFactory
's newMessageParameterProvider()
method,
and provide an alternative MessageParameterProvider
implementation.
This could be useful, for example, in validating a FormFile object in a file upload. Here, the
MessageParameterProvider
may need to separately
expose parts of the uploaded FormFile
as
individual message parameters.