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: