7) Validation


In many cases it is required to limit the users data entry or to validate the input. For these cases, the framework provides some features which are introduced in this chapter. Validation can generally be classified in static and dynamic validation. Static constraints can be expressed by annotations in the domain class. If the validation depends on particular situations, respective the domain object state, the dynamic approach provides more flexibility via custom validation methods. All validation annotations must be added to the getter methods of the properties or the fields themselves. Generally the framework performs static validations through the standardized Bean Validation (JSR 303 and JSR 349). The bean validation annotations are also recognized by JPA implementations among others. Using the standard thus allows performing validations on different layers, both automatically and programmatically.

7.1) Static Validation

We will now go through the most interesting validation annotations. For more info and a guide on how to create your own validation annotations, see the Hibernate Validator documentation, as that is the implementation used for this functionality. Validations generally occur when the form is submitted (or when leaving the component when @Eager synchronization is used). The standard integration of wicket is used for this feature. In comparison to the normal bean validation it does not validate the whole model, but only specific properties that are bound by the wicket components. This results in only visible properties being validated (meaning hidden or invisible properties will not get validated). To get the whole model validated you can still invoke the normal bean validation manually (e.g. before inserting into a database).

Mandatory Data

Realization: A property can be tagged as a mandatory using the @NotNull annotation. All data types which can be null are supported. Primitive data types already have a built in validation for this (as seen in the previous chapter).
Effect:The framework checks if the component contains any input. An empty component is tagged to have invalid input. Method buttons are disabled until the input is corrected. When this annotation is used on an enum type that is displayed as a combo box, then the "empty" value is omitted in the allowed options to be selected. The framework treats the JPA annotation @Column(nullable=false) the same.

Numbers With Limited Range

Realization: The range of values for a numeric property can be limited by the @Min and @Max annotations.
Supported data types: all numeric types (use @DecimalMin and @DecimalMax for floating point numbers)
Effect: The range of values is checked against the given value. In case of a range violation, the component is tagged to have invalid input. Method buttons are disabled until the input is corrected.

Defined Format & Allowed Characters

Realization:The input format for a property can be specified in a detailed way using the @Pattern annotation. The annotation expects a string with a regular expression which the property value must match. Supported data types: String
Effect: The input is matched against the regular expression. In case of an expression violation, the component is tagged to have invalid input. Method buttons are disabled until the input is corrected.

Length Limitation

Realization: The length of a property can be limited using the @Size annotation. It allows specifying both a minimum and a maximum length.
Supported data types: String, Collection, Map, Array
Effect: Maximum length: the appropriate components accept only the specified number of characters
Minimum length: If the input is too short, the component is tagged to have invalid input. Method buttons are disabled until the input is corrected.

Decimal Places

Realization: The number of decimal places can be specified using the @Digits annotation.
Supported data types: BigDecimal, BigInteger, String, byte, short, int, long
Example: @Digits(integer=5, fraction=2) means: 5 digits before the decimal point, 2 digits after the decimal point
Effect: The framework checks whether the input has a valid number of digits.
Not enough decimal places: The component is filled with missing 0 characters, if this does not exceed the maximum length, otherwise the component is tagged to have invalid input. Method buttons are disabled until the input is corrected.
Too many decimal places: The component is tagged to have invalid input. Method buttons are disabled until the input is corrected.

7.2) Dynamic Validation

It may happen that the validation rules for an object depend on its state. For these cases, the framework provides a dynamic validation feature based on naming conventions for utility methods.

Validate Utility Methods

These methods are called by the framework before updating the appropriate domain object property. Validate methods must have the following signature:

public String validate<NameOfProperty>(<TypOfProperty> newValue) { ... }

The return value of this method is an error text which is displayed in a feedback box or around the component in case of a validation error. A return value of null expresses a successful validation causing the interactive input to be transferred to the domain object afterwards. For example the input for a property "price" may be dynamically validated by a method called "validatePrice". You do not have to worry about XOR relationships between properties in validators, since the two step validation that is done by the framework ensures that all validations produce the correct result, independent of the synchronization order.

Choice Utility Methods

These methods allow a dynamic limitation of the value set for a property. Choice methods must have the following signature:

public <TypeOfProperty>[] get<NameOfProperty>Choice() { ... }

The return value is the list of supported values for the current situation, respectively the current object state. If a choice method exists, the property is displayed as a combo box (if it is a simple type) or a table (if it is a structured type). Though you can for example convert a table tag to a select tag. In this case the combo box displays the result of the toString() method of these objects. You can also do it the other way around. Choice methods allow a new kind of data manipulation. With a choice function, the user manipulates the selection (via property getter/setter) while the allowed choices remain the same (via choice utility method). If a property may be null, the corresponding choice method must contain a null value as a valid selection. Otherwise the framework assumes that null is not allowed in the current situation even though the data type may generally support null values. It is important to know that the framework compares the current property value with the values from the corresponding choice method to check its validity. Structured data types therefore must define a valid equals method or the choice method must return the identical value set with each call, not only an equal one. The comparison takes place whenever the user interface and the domain objects in the background are synchronized, which is usually quite often, for example when a component is submitted or a button is pressed.

7.3) Try It

Now let us apply this new knowledge on our previous car example:

public class Car implements Serializable {

    // constructor, attributes and uninteresting getter and setter methods omitted here

    @Pattern(regexp = "[A-Z]{1,3}-[A-Z]{1,2} [1-9][0-9]{0,3}")
    public String getLicenseNumber() { return licenseNumber; }

    @NotNull
    public CarBrand getBrand() { return brand; }

    @Digits(fraction = 2, integer = 19)
    public BigDecimal getPurchasePrice() { return purchasePrice; }

    @Min(0)
    @Max(1000000)
    public long getKmMileage() { return kmMileage; }

    @Pattern(regexp = "[45]")
    @Size(max = 1)
    public String getNumberOfGears() { return numberOfGears; }

    public CarBrand[] getBrandChoice() {
        // filter PlutoNebula because someone said: "Pluto is not a planet!"
        return new CarBrand[] { CarBrand.LunarIndustries, CarBrand.MarsRoveries };
    }

    private boolean isPurchasePriceGreaterThan(final BigDecimal purchasePrice, final long price) {
        return purchasePrice != null && purchasePrice.compareTo(new BigDecimal("" + price)) >= 1;
    }

    public String validatePurchasePrice(final BigDecimal value) {
        if (brand != null && brand == CarBrand.MarsRoveries && isPurchasePriceGreaterThan(value, 20000)) {
            return "is too expensive";
        } else {
            return null;
        }
    }
}
    
public class Motor implements Serializable {

    // constructor, uninteresting attributes, getter and setter methods omitted here
	
    @Min(0)
    private int powerInHP;
	
}

To see some validation messages, try to Turn On the car below:

Motor
<-- CLICK HERE!
Trip Book
Distance In KM From To 
384,403
Earth
Moon
384,403
Moon
Earth