A library that provides support for input forms also has to deal with the data entered by the user:
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);
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:
Object transform(Object o, TransformerContext ctx) throws Exception;
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 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:
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:
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>
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.