Chapter 7. Validation

7.1. Why validation

Users make mistakes. They won’t admit it, but they do. As GUI developers, our task is to make sure that their mistakes don’t crash or cripple our system. Input validation can help a great deal with this. Valkyrie supports validators for various input. These are all subclasses of RichValidator.

7.2. Validation choices

Valkyrie has multiple validation strategies:

  • The built-in rules system

  • Using a JSR303 Validator

  • Your own validation framework

  • A combination of any of the above

7.3. Rule validation

7.3.1. Using the rules framework

Valkyrie has created their own validation framework, which is rule based. A change to a value it put through a number of checks, which are contained within a rule, and results in a success or a validation error.

The FormModelHelper can create formmodel with rule validation by providing a RulesSource in one of its factory methods.

A rule source contains different rules for a certain object type. If, for example, we want to validate that the field1 property of the TestObject class is never empty, we can create a RulesSource like this:

DefaultRulesSource source = new DefaultRulesSource();
Rules rules = new Rules(TestObject.class);
Constraints c = new Constraints();
rules.add(c.required("field1"));
source.addRules(rules);

7.3.2. Constraints

Constraints contain the actual logic that checks the values.

The Constraints class contains a lot of predefined constraints. Among these you can find

  • Maximum length of string

  • Maximum value

  • Not null

  • Minimum value

Creating your own constraint is done by implementing the Constraint interface, which consists of one method

public interface Constraint
{
    boolean test(Object argument);
}

This constraint will test any object. Most of the time, we’ll want to split up functionality to check individual properties, so that we can reuse this logic elsewhere.

For this, you’ll need to subclass AbstractPropertyConstraint, which needs a property name. Also, in it’s test method, it provides an easy way to get values of individual properties.

Say we want to create a constraint that checks whether a String property’s value equals “RCP” (silly, but a good example). We’ll end up with something like this:

public class RcpConstraint extends AbstractPropertyConstraint
{
    protected boolean test(final PropertyAccessStrategy domainObjectAccessStrategy)
    {
        Object prop = domainObjectAccessStrategy.getPropertyValue(getPropertyName());
        return !(prop instanceof String) || ((String) prop).equals("RCP");
    }
}

This constraint you can then add to your RulesSource for a specific property.

7.3.3. Validation triggers

Validation on a property is triggered when that property is changed in its valuemodel. Valkyrie will search for rules for that property and execute them.

7.3.4. Dependent properties

Some rules that are registered for a certain property need to be triggered when another property is changed (for example two dates, for which the first needs to be before the last). Valkyrie supports this by overriding the isDependentOn(…) method. Out-of-the-box, this method returns true if the parameters equals the property name for which the rule is defined. However, you can add additional properties to this method. Every change in a property that returns true on this method will cause this rule to be checked.

An example of this type of rule is the RequiredIfTrue constraint. This constraint can accept a property and another PropertyConstraint. For example, you want the driver’s license number required if the age of the person is over 21. Creating a constraint for this would be something like this:

Rules rules = new Rules(Person.class);
rules.add(new RequiredIfTrue("driverLicenseNumber", new PropertyValueConstraint("age",
                new Constraint()
                {
                    public boolean test(final Object argument)
                    {
                        return ((Integer) argument).intValue() >= 21;
                    }
                })));

This constraint will be fired if the value of driverLicenseNumber or age is fired. Mind you though, the validation error will only be reported on the ‘main’ property, which is driverLicenseNumber.

7.3.5. Setting i18n messages for custom rules

Getting the message for a custom rule is done by implementing TypeResolvable in your constraint class. For convenience, there’s already a constraint class with custom message support called TypeResolvableConstraint. Valkyrie will then use that message in its validation results.

When you provide TypeResolvable support in a property constraint, Valkyrie will add the message to the property message (which is also used for the label).

7.4. JSR validator integration

JSR303 Bean Validation is a well-known framework for validating JavaBeans, of which Hibernate Validator is the reference implementation

Simply put, if the object behind a form has JSR303 annotations, by setting the validator to a JSR303Validator, it will validate using those annotations. This validator will check all property-based validations (no @AssertTrue support at the moment yet). You can even turn off validation for certain properties (for example, a code is @NotNull, but in the GUI you’re allowed to leave it empty because you’ll generate one on the fly when that’s the case before sending it to the business layer).

7.5. Combining validators

Valkyrie also supports combining different validators. For example, AssertTrue validations are currently not checked when using the JSR303 integration. To be able to check those validations, you can transform these into rules in the Valkyrie’s rules system. By combining the two, you then have the complete validation for your object.

Building a composite validator can be done by the following snippet

JSR303Validator jsrValidator = ...
RulesValidator rulesValidator = ...
CompositeRichValidator validator = new CompositeRichValidator(new RichValidator[] { jsrValidator, rulesValidator });

7.6. Integrating your own or another third-party validation framework

If you want to integrate your own validation framework, you’ll need to subclass the RichValidator class and wire your validator to produce validation results.