A library that provides support for input forms also has to deal with the data entered by the user:
java.util.Date
object.
In the JGUIraffe library this topic is covered by so-called
Validator
and Transformer
classes, which are
located in the net.sf.jguiraffe.transform
package. In the
following sub sections we discuss their usage.
The fundamental interfaces in the tranform
packages are
Validator
and Transformer
.
Validator
is responsible for validation of data entered by the user. The interface
defines a single method with the following signature:
ValidationResult isValid(Object o, TransformerContext ctx);
Validator
is passed
an object and has to decide whether this object is valid or not.
(We will explain the helper classes ValidationResult
and
TransformerContext
in a moment.)
A Transformer
deals with data conversions from one data type into another one. Focusing on
use cases related to forms such conversions are necessary twice in the
life-cycle of a typical form:
Validator
is involved, too,
which checks the validity of the data entered before the transformation is
actually performed.Transformer
interface also consists only of a single method
that looks as follows:
Object transform(Object o, TransformerContext ctx) throws Exception;
Transformer
) goes out.
In both method signatures an argument of type
TransformerContext
appeared. An object implementing this interface
provides useful information for the Transformer
or
Validator
that may be needed for performing the transformation
or validation. The major part of the TransformerContext
interface is related to internationalization, which is an important aspect
of the tasks to be performed by Transformer
and
Validator
implementations. For instance, different countries
use different formats for dates or numbers, so transformers dealing with
such data types have to be aware of the current locale.
The TransformerContext
interface can be used for obtaining the
following information:
Locale
.ValidationMessageHandler
object (this is discussed later).
Transformer
or Validator
implementations.
The isValid()
method of the Validator
interface
returns an object implementing the
ValidationResult
. Through this interface the status of the
validation can be obtained. There are two methods:
isValid()
returns a boolean flag that determines whether
the validation was successful. If isValid()
returns true,
the user has entered valid data.getErrorMessages()
returns one or more error messages
related to the validation. Error messages are only available if
isValid()
returns false. In this case it is important to
tell the user what exactly was wrong. This information is contained in the
ValidationMessage
objects returned by getErrorMessages()
.
A ValidationMessage
consists of a key and a text to be displayed
to the user. More details about ValidationMessage
and how
instances can be created will be discussed later.
The library ships with a number of default transformers and validators that
support the most primitive data types (including date and time) and can be
used out of the box. These implementations are pretty powerful and provide
enhanced validation capabilities, e.g. comparing an input object with a
reference value. The basic pattern is that there are abstract base classes
like
DateTransformerBase
or
NumberTransformerBase
that implement both the Transformer
and the Validator
interface in a generic way. They provide the
major part of the functionality required. Then there are a bunch of
concrete classes derived from these base classes that are specialized for
concrete data types (e.g. Date, Integer, Long, Float, ...). The Javadocs of
these classes list the various configuration options of these classes and
also the validation error messages they can produce.
In addition to the implementations dealing with specific data types there are some more fully functional classes that can be used for special purposes:
ToStringTransformer
implements a generic way for transforming
objects to a text representation. Some special types of objects are directly
recognized and formatted accordingly to the current locale, e.g. date types
and numbers. On unknown objects the toString()
method is
invoked.
RequiredValidator
is a fully functional Validator
implementation that can be assigned to input fields that are mandatory. It
can deal with different types of objects like strings, collections, or arrays.
and can check whether they are empty. In this case validation fails. A value
of null is of course always considered invalid.
RegexValidator
uses a regular expression to validate user input.
An instance can be initialized with a regular expression string that must be
compatible with the regular expression syntax defined for the
java.util.regex.Pattern
class. The user input to be validated
is transformed to a string and matched against the defined pattern. If this
is not possible, a validation error is produced. This is a very powerful
means to enforce certain input patterns.
ChainValidator
allows combining multiple validators so that an
input value is considered valid only if all of these child validators return
a positive result. This is useful if multiple conditions have to be checked
that are implemented by different Validator
implementations. A
good use case are required input fields of a specific data type: The
existing validators for primitive types (e.g. dates or numbers) all accept
null input. In order to declare mandatory input fields these validators
can be combined with a RequiredValidator
using
ChainValidator
.
Implementing a custom Transformer
is a pretty straight forward
task. A Transformer
class requires the transform()
method. Here a custom implementation is free to do whatever it wants. It
can even throw an arbitrary exception if the transformation fails for some
reason. The
TransformerContext
object being passed to the
transform()
method can be queried for specific properties set
in the context or for information about the current locale.
Transformers are typically specific for a given data type. So there are no
general rules that can be stated here. However, one important point is that
a transformer is typically combined with a validator: the validator first
checks the input of the user, and if this validations succeeds, the
transformation should be possible without throwing an exception. Because of
this narrow relationship between validation and transformation many of the
functional classes in the transform
package implement both the
Transformer
and the Validator
interfaces.
Implementing a custom Validator
is usually more involved than a
new Transformer
implementation because the validator has to
provide concrete information to the user if something goes wrong. This
includes the definition of meaningful error messages that are returned as
part of the
ValidationResult
object returned by the validator. As an example
we are going to develop a validator that checks whether a password entered
by the user is strong enough. The password must have at least a configurable
number of characters and include characters from all the three groups
letters, digits, and special characters.
We can start by thinking about the possible error messages our new validator
can produce. There are two error conditions: the password may be too short,
and it may be too simple (i.e. it does not contain characters from all three
character groups defined above). To define the exact error texts we define
a new resource group, say validationerrors. (The framework has
already a resource group validators, which contains the error
messages of the default Validator
classes.) So we create a
file validationerrors.properties
with the following content:
ERR_PWD_TOOSHORT = The passowrd must have at least {0} characters. ERR_PWD_TOOSIMPLE = The password must contain letters, digits, and special characters.
Important are the keys used for the resource texts. They are later used by the implementation class to refer to the error messages. Notice that the first error message contains a place holder for the number of characters a password must have; this value can be configured, so we cannot specify a fix number.
Error messages are obtained from an object implementing the
ValidationMessageHandler
interface that lives in the
TransformerContext
. The default implementation of this
interface is provided by the
DefaultValidationMessageHandler
class.
The ValidationMessageHandler
interface defines a
getValidationMessage()
method, which is passed the key of an
error message and returns a ValidationMessage
object with the
corresponding message. DefaultValidationMessageHandler
implements this method by first checking whether an object with the passed
in key can be found in the properties of the current
TransformerContext
. If this is the case, this object is used
as error message. Otherwise, the key is interpreted as a resource identifier,
and a resource lookup is performed. Per default the class searches in the
reserved validators
resource group. But it can be configured
to look in other resource groups, too. To do this we have to add a bean
declaration like the following one in a bean definition file of our
application:
<di:bean name="jguiraffe.validationMessageHandler" beanClass="net.sf.jguiraffe.transform.DefaultValidationMessageHandler"> <di:setProperty property="alternativeResourceGroups" value="validationerrors"/> </di:bean>
validationMessageHandler
bean. In the new declaration we set
the alternativeResourceGroups property. Here a comma-separated
list of resource group names can be specified that are searched for error
texts. This basically means that DefaultValidationMessageHandler
searches for an error key in these resource groups first. If search is
successful, the found resource text is used as error message. Otherwise it
looks up the key in the default resource group.
With the error messages in place we can start with the actual coding of the
new PasswordValidator
class. The isValid()
method of this class has to check the length of the user input and whether
the password is sufficiently complex. If this is true, a
ValidationResultr
object indicating a successful validation has
to be returned. Otherwise a result object has to be created with the
appropriate error messages:
public class PasswordValidator implements Validator { /** Error key for a password that is too short. */ public static final String ERR_PWD_TOOSHORT = "ERR_PWD_TOOSHORT"; /** Error key for a password that is too simple. */ public static final String ERR_PWD_TOOSIMPLE = "ERR_PWD_TOOSIMPLE"; /** Constant for the default minimum length of a password. */ private static final int DEFAULT_MIN_LENGTH = 6; /** The minimum number of characters for a valid password. */ private int minimumLength = DEFAULT_MIN_LENGTH; /** * Returns the minimum length a password must have. * @return the minimum length of a valid password */ public int getMinimumLength() { return minimumLength; } /** * Sets the minimum length a password must have. * @param len the minimum length of a valid password */ public void setMinimumLength(int len) { minimumLength = len; } /** * Performs the validation. */ public ValidationResult isValid(Object o, TransformerContext ctx) { String pwd = (o == null) ? "" : String.valueOf(o); // long enough? boolean tooShort = pwd.length() < getMinimumLength(); // complexity? int letters = 0; int digits = 0; int special = 0; for (int i = 0; i < pwd.length(); i++) { char c = pwd.charAt(i); if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { letters++; } else if (c >= '0' && c <= '9') { digits++; } else { special++; } } boolean tooSimple = letters < 1 || digits < 1 || special < 1; // Everything okay? if (!tooShort && !tooSimple) { return DefaultValidationResult.VALID; } // Otherwise create correct error messages ValidationMessageHandler msgHandler = ctx.getValidationMessageHandler(); DefaultValidationResult.Builder builder = new DefaultValidationResult.Builder(); if (tooShort) { builder.addErrorMessage(msgHandler.getValidationMessage(ctx, ERR_PWD_TOOSHORT, getMinimumLength())); } if (tooSimple) { builder.addErrorMessage(msgHandler.getValidationMessage(ctx, ERR_PWD_TOOSIMPLE)); } return builder.build(); } }
At the beginning of the class we declare some constants for the possible error messages. The values of these constants must match the keys we used for the resources of the error texts. The minimum length of valid passwords should be configurable, so we created a property with get and set methods for it.
The isValid()
method contains the actual logic. First we
convert the object passed in to a string (because it can be null a
corresponding check is required). Then we implement our checks. If they
succeed, the VALID
constant of the class
DefaultValidationResultr
is returned, which represents a valid
result.
The remaining part of the code deals with the construction of a
ValidationResult
object that contains the appropriate error
messages. In order to obtain the error messages we need a
ValidationMessageHandler
object, which can be queried from the
TransformerContext
. Objects of the class
DefaultValidationResult
cannot be created directly, rather the
class defines a nested Builder
class (an application of the
builder pattern). Such a Builder
object is created,
then the error messages for the failed checks are added to it. Notice that
for the error ERR_PWD_TOOSHORT
the required minimum length is
passed as parameter to the getValidationMessage()
method. It
will become part of the error message to be displayed to the user.
This concludes our excourse in writing custom validators. The validator and transformer classes shipped with the library are also good examples for more or less complex validation tasks; so having a look at them may be useful.