Validation Reference

Skip to end of metadata
Go to start of metadata

This document describes how valiation works in Stripes, how you specify validations, and how ActionBeans interact with validation. It includes the following sections:

Overview

Validation is conceptually broken into three areas in Stripes:

  • Annotation Driven Validation
  • Type Converstion
  • Custom Validation

Annotation Driven Validation is named thusly because the way the validation rules are specified is through annotations in the ActionBean. These Validations apply to single fields in the form/ActionBean, encapsulate common and often used validations. Unfortunately due to the lack of inheritance in annotations, it is not possible to extend annotation driven validations.

Type Conversion is the process of converting a single form field input from the String value in the HttpServletRequest into a Java object (of any Class, including String). Type conversion is a core part of the validation process, and is easy to extend to new of custom types.

Custom Validation is the name given to the validation performed by implementing the Validatable interface in an ActionBean. The interface contains a single method, validate() which gives the ActionBean a chance to execute arbitrary validation code and provide error messages to be displayed.

Validation Processing Flow

Given the different kinds of validation on offer, one might ask how they are applied, and what happens when errors are produced at any stage. The following is the general flow:

  1. Required field validation is run first
  1. For fields that have no errors after required field validation
    1. Perform minimum/maximum length and mask validations
    1. If the field still has no errors, convert the type and bind the property to the ActionBean
  1. If there are still no errors, run custom validation (if it exists)

This strategy is designed to catch the largest possible number of validation errors possible in a single pass. Thus if the user forgets to enter one value, and mis-types another, they will be presented with both errors the first time they submit the form - but they will not be presented with any additional (e.g. minimum length violation) errors for the field that was required by not supplied. Lastly it should be noted that the reason custom validation is run only when other validations succeed is so that it can rely on the ActionBean being in a well defined. This is, however, configurable (see the Configuration Reference).

As we will see in the sections [Selectively Disabling Validation] and [Changing Validation Outcomes] this is not the whole story, but it's good enough for now.

Validation Error Classes

Before we go much further, a quick tour of the different validation error classes is in order. We'll be seeing a lot about validation errors in the next few sections, so it is worthwhile to understand the different types available, and how they are used.

ValidationError is an interface that specifies the methods that all validation error types must support. This makes it easy to have multiple different error classes that offer different functionality. It also means that you can write your own error classes easily if you are not satisfied with the ones that Stripes supplies (hopefully that won't be the case).

SimpleError is, as it says, a simple implementation or ValidationError. It allows a hard-coded message (or one supplied at construction time) to be used, and provides the ability to insert the name and value of the field (and other arbitrary bits of information) into the message.

LocalizableError extends SimpleError with the capability to look up the error message in an external resource bundle. The name of the bundle can be configured - to do this take a look at the Configuration Reference.

ScopedLocalizableError extends LocalizableError and provides the ability to perform "scoped searches" for an error message. In reality this just means looking for error messages in the resource bundle with names corresponding to:

  • actionPath.fieldName.errorName
  • actionPath.errorName
  • defaultScope.errorName

In the descriptions below you will see reference to "default scope" and "error name", which refer to how a ScopedLocalizableError is found. All the built in validations in Stripes use ScopedLocalizableError, so that you can specify fairly generic messages at a high level and override them as you see fit.

All of the error messages in Stripes use java.text.MessageFormat which allows you to provide parameters to be merged in to the message. The first two parameters (0 and 1 respectively) are always the field name and field value respectively. An example follows, but check the individual error class javadocs, and the javadoc for MessageFormat for more details:

Example error message specification
myCustomError={0} must be formatted mm/dd/yy. {1} is not a valid date.

Annotation Driven Validation

There are two annotations for adding validations to fields. The @Validate annotation is used to specify validations for scalar or simple properties within an ActionBean. The @ValidateNestedProperties annotation is used to aggregate one or more @Validate annotations in order to specify validations for nested properties of complex types. The annotations can be attached to either the getter or setter methods on the ActionBean. They can also be attached directly to the properties (even private ones).

Each @Validate can specify multiple validations for a single property. The following example demonstrates how to use the annotations:

Using Validation Annotations
@Validate(required=true, minlength=5, maxlength=25)

private int age; // annotated right on the field



@ValidateNestedProperties({

    @Validate(field="line1", required=true, minlength=5, maxlength=50),

    @Validate(field="line2", minlength=5, maxlength=50),

    @Validate(field="zip", required=true, mask="\\d{5}(-\\d{4})?")

})

public Address getAddress() { ... }

The following sections walk through the syntax of each validation that can be specified with the @Validate annotation.

@Validate(field="...")

The field property is used only when the @Validate annotation is nested within a @ValidateNestedProperties annotation. It is used to specify the property of the complex type to which the validation applies.

@Validate(required=true/false)

Specifies whether or not the annotated field is required when the form is submitted. Defaults to false (so it's never necessary to write required=false even though you can). If a required field is not supplied a single error is generated. The "default scope" is specified as validation.required and the error name is valueNotPresent. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueNotPresent
  • actionPath.valueNotPresent
  • validation.required.valueNotPresent

@Validate(minlength=##)

Species the minimum length that the pre-conversion String must be. For example you might specify that a password field must have a minimum length of 6. Doing so would require the password to be six characters in length or greater. If a field fails the minimum length test a single validation error is generated. The default scope is validation.minlength and the error name is valueTooShort. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueTooShort
  • actionPath.valueTooShort
  • validation.minlength.valueTooShort

The error message is supplied with an additional parameter (which can be used as {2} in the message text) which is the minimum length constraint that was violated - e.g. 6 in the example above.

@Validate(maxlength=##)

Species the maximum length that the pre-conversion String may be. For example you might specify that a username field may have a maximum length of 25. Doing so would require the username to be twenty-five characters in length or less. If a field fails the maximum length test a single validation error is generated. The default scope is validation.maxlength and the error name is valueTooLong. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueTooLong
  • actionPath.valueTooLong
  • validation.maxlength.valueTooLong

The error message is supplied with an additional parameter (which can be used as {2} in the message text) which is the maximum length constraint that was violated - e.g. 25 in the example above.

@Validate(minvalue=##)

Species the minimum value for a numeric type (after it has been converted to a number). For example you might specify an age field has a minvalue of 0 to enforce non-negative ages! If a field fails the minimum value test a single validation error is generated. The default scope is validation.minvalue and the error name is valueBelowMinimum. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueBelowMinimum
  • actionPath.valueBelowMinimum
  • validation.minvalue.valueBelowMinimum

The error message is supplied with an additional parameter (which can be used as {2} in the message text) which is the minimum value constraint that was violated - e.g. 0 in the example above.

@Validate(maxvalue=##)

Species the maximum value for a numeric type (after it has been converted to a number). If a field fails the maximum value test a single validation error is generated. The default scope is validation.maxvalue and the error name is valueAboveMaximum. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueAboveMaximum
  • actionPath.valueAboveMaximum
  • validation.maxvalue.valueAboveMaximum

The error message is supplied with an additional parameter (which can be used as {2} in the message text) which is the maximum value constraint that was violated.

@Validate(mask=".*")

Specifies a regular expression that the user's input must match. The mask is compiled using java.util.regex.Pattern - if you are not familiar with regular expressions, the documentation for the Pattern class provides a good introduction to the syntax. The entire String submitted must match the mask/pattern supplied, as specified by Pattern.matcher(input).matches().

If the input does not match the mask, a single validation error is output with the default scope validation.mask and the error name valueDoesNotMatch. This results in an attempt to find error messages in the resource bundle with the following keys:

  • actionPath.fieldName.valueDoesNotMatch
  • actionPath.valueDoesNotMatch
  • validation.mask.valueDoesNotMatch

@Validate(converter=PercentageTypeConverter.class)

Specifies the TypeConverter that should be used to convert this field. This is not usually necessary as in most cases Stripes will use the right converter for the target type. Two common cases where this is useful are:

  1. When you are using formats that modify the meaning of the data. E.g. formatting numbers as percentages causes 0.95 to be written as 95%. Using the default Double/Float type converter in this case would result in the value being multipled by 100 every time the user edited the value. In this case you could tell Stripes to use the PercentageTypeConverter which does the right thing.
  1. When you are converting to custom types, and have decided not to write your own (very simple) TypeConverterFactory. More on this in the next section.

Since this is not strictly a validation, but a way of modifying the type conversion, it does not produce any error messages.

Type Conversion

Type conversion is quite simply the process of converting the String parameters that are supplied in the HttpServletRequest into the types of the corresponding ActionBean properties. The TypeConverter interface specifies how Stripes will interact with classes that perform type conversion. There are type converters in Stripes for most common types in Java, and adding support for additional types is as easy as coding up a new TypeConverter.

Stripes gets to know about TypeConverters in one of two ways. If an @Validate annotation specifes a converter then that converter will be used, no questions asked. Otherwise Stripes will ask the configured TypeConverterFactory to supply the appropriate TypeConverter for the type being converted to. Extending the DefaultTypeConverterFactory is fairly straight-forward. See the Configuration Reference for details on how to configure Stripes to use your custom TypeConverterFactory implementation.

The following sections document the errors produced by the individual type converters used in Stripes.

BooleanTypeConverter

The BooleanTypeConverter determines that a value is "true" if it matches (ignoring case) any of the values "true", "t", "yes", "y" or "on" or if the value can be parsed as a number and does not equal zero.

Please note that Stripes does not convert and bind parameters submitted in the request with the value "" (empty string). So while you might think that "" would convert to false, in fact conversion is never run, so the value on your ActionBean will be whatever default value is assigned to the property.

Since any String can be converted to a boolean (either false or true), no validation errors are produced.

DateTypeConverter

The DateTypeConverter employs a number of techniques to get the input to parse as a java.util.Date. If the input cannot be parsed into a Date then a single validation error is produced. The default scope is converter.date and the error name is invalidDate.

EmailTypeConverter

The EmailTypeConverter, strictly speaking is not a type converter. It uses JavaMail to parse the String and return a well formatted email address as a String.

A single validation error is produced if the address cannot be parsed. The default scope is converter.email and the error name is invalidEmail.

EnumeratedTypeConverter

The EnumeratedTypeConverter converts Strings representing the names of enumerated type values, back into an instance of the enumerated type. If the value supplied does not match any of the values of the target enumerated type, a single validation error is produced. The default scope is converter.enum and the error name is notAnEnumeratedValue.

Number TypeConverters

Stripes supplied a set of converters for converting to the Java numeric types: byte, short, int, long, float, double and their corresponding wrapper types as well as BigDecimal and BigInteger. They all operate similarly (using one or more java.text.NumberFormats). The converters make a best effort to parse a String as a number. They can handle the following without any additional effort:

  • additional white space
  • currency and non-currency numbers
  • use of the minus sign and parentheses to indicate negation
  • use of grouping characters and decimals (obviously)

The number TypeConverters produce two types of error. The first error is common to all the number TypeConverters; it is generated when the String is not parsable as a number. The default scope is converter.number and the error name is invalidNumber.

The second error is named differently for each number TypeConverter, but means the same; it is generated when the supplied number is out of the range for the target type. The default scope is converter.[byte|short|integer|float] and the error name is outOfRange. For all outOfRange errors two additional parameters ({2} and {3}) are supplied to the error message. The parameters are the minimum and maximum allowable values for the type being converted to, e.g. Integer.MIN_VALUE and Integer.MAX_VALUE.

PercentageTypeConverter

The PercentageTypeConverter converts numbers displayed as percentages (e.g. 95%) into decimal numbers (e.g. 0.95). It requires that the target type is either float, double (or the wrapper types) or BigDecimal. It produces the same error as other number classes, with a default scope of converter.number and the error name invalidNumber.

Custom Validation

Custom validation refers to the execution of arbitrary, custom, validation logic within the ActionBean. ActionBean classes may optionally implement Validatable. The interface specifies a single method, validate(ValidationErrors errors). If the ActionBean implements Validatable then the validate() method will be invoked after all other validations have been run and all values have been converted and bound to the ActionBean.

By default validate() will not be invoked if preceeding validations resulted in errors. Since the ActionBean will be fully populated when validate() is called it may refer to any property of the ActionBean, or through the ActionBeanContext access values in the HttpServletRequest (though hopefully this will not be common since it makes the ActionBean less testable).

It is possible to configure Stripes to always invoke the validate() method if it exists, regardless of whether or not validation errors exist. To do this refer to the Configuration Reference. Note that by doing this you are no longer guaranteed that all required fields have been supplied or that type conversions and binding succeeded before validate() is called.

The validate() method is passed an instance of ValidationErrors to which errors should be added. Any error class that implements ValidationError may be added. If the ValidationErrors object is not empty when validate() returns, the user will be sent back to the originating page and presented with the errors.

Performing Validation in Handler Methods

Sometimes it's just not possible (or reasonable) to validate absolutely everything up front in the validate() method. Maybe you don't have all the data you need, maybe you won't know if something really is valid until you try and do it! For example, you might not be able to validate that a withdrawal from a bank account is valid until you do it due to concurrency issues (what if two threads check a $100 balance then allow $75 withdrawals?).

In these situations you can create validation errors in your handler method, and send the user back to the page they came from (or elsewhere if you choose). The following is an example:

{code:title=

@HandlesEvent("Withdrawal")

public Resolution withdrawFunds() {

try

Unknown macro: { getAccount().withdraw( getAmount() ); return new RedirectResolution("/account/summary.jsp"); }

catch (InsufficientBalanceException ibe)

Unknown macro: { ValidationErrors errors = new ValidationErrors(); errors.add( "amount", new LocalizableError("/account/transaction.action.insufficientFunds") ); getContext().setValidationErrors(errors); return getContext().getSourcePageResolution(); }

}

Unknown macro: {null}

Selectively Disabling Validation

Often is it desirable to have validation run for one or more events in an ActionBean, but not for others. For example the "save" method might require validation, as might the "add" method. But the "prepopulate" method might not.

Stripes uses the @DontValidate annotation to achieve this. Any handler method that is annotated with @DontValidate will be invoked directly skipping validation. Type conversion and binding are still executed, and may produce validation errors. However, it is expected that in most (all?) cases the @DontValidate annotation will be used with events whose only input is not user-input (e.g. hidden fields, select lists etc.).

Changing Validation Outcomes

There are times when even the most flexible system cannot give you everything, and you want to manage the validation process at a more detailed level. You can do this by implementing the ValidationErrorHandler interface in your ActionBean. The interface defines a single method handleValidationErrors(ValidationErrors errors) that is invoked when validation fails, but before determining what to do next.

The ActionBean can manipulate the ValidationErrors in any way it sees fit and the changes will be taken into account before proceeding. If all errors are cleared out, it will be as if they never existed! The ActionBean can also use this opportunity to undo any side-affects that might be caused by the binding process if necessary, or to substitute a different Resolution. Check out the ValidationErrorHandler javadoc for more information.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.