Transformers and Validators

A library that provides support for input forms also has to deal with the data entered by the user:

  • The input fields of the form have to be validated to ensure that the user has entered correct data.
  • Data type conversion may also be required. Input fields used within forms (e.g. plain text input fields) usually store their context as strings only and are not aware of any specific data type. For instance, if an input field is used for entering a date, it contains only a text representation of a valid date. To provide the application with meaningful data this text representation has to be converted to a 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.

Fundamental interfaces

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);
    
This method signature basically means that a 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:

  • When the form is opened its input fields have to be initialized with data from the application's data model. This typically involves a transformation from model-specific data types (e.g. numbers, dates) to strings needed for GUI controls.
  • When the user presses the OK button the data entered into the input fields has to be stored in the model. This implies the inverse transformation, i.e. from plain strings to model-specific data types. Of course, this transformation can only be successful if the data entered by the user adheres to the expected format. So usually a Validator is involved, too, which checks the validity of the data entered before the transformation is actually performed.
The Transformer interface also consists only of a single method that looks as follows:
Object transform(Object o, TransformerContext ctx) throws Exception;
    
This can be read as: an object comes in and another one (the result of the transformation applied by this 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:

  • The current Locale.
  • The current resource manager. This may be useful if lanugage-specific texts need to be generated.
  • A ValidationMessageHandler object (this is discussed later).
  • A map with properties that can be used to alter the behavior of 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.

Existing classes

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.

Creating a custom transformer

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.

Creating a custom validator

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>
    
This bean declaration overrides the default declaration for the 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.