Nov 4, 2021 2:21:29 PM Thomas Dumont avatar

Bean validation (JSR 303)

Introduction


The JSR 303 defines a meta-data model and an API to validate Java Beans. This validation is done using annotations but it is possible to use XML files. This JSR was finalized in November 2009 and is part of the JEE6 specification released a month later. Partial native support was introduced in Lutece in version 3.0.8 with the class fr.paris.lutece.util.beanvalidation.BeanValidationUtil. More complete support is available in version 4.1 which we will describe below.

Reminder of the JSR 303


Here are the annotations of the standard constraints defined by the JSR:

Annotation Description of the constraint Type on which the constraint can apply
@Null Element must be null Object
@NotNull The element must be nonzero Object
@AssertTrue Element must be true boolean, Boolean
@AssertFalse Element must be false boolean, Boolean
@Min The element must be greater than the value specified in the annotation BigDecimal, BigInteger, byte, short, int, long
@Max Element must be less than the value specified in the annotation BigDecimal, BigInteger, byte, short, int, long
@DecimalMin The element must be greater than the value specified in the annotation BigDecimal, BigInteger, String, byte, short, int, long
@DecimalMax Element must be less than the value specified in the annotation BigDecimal, BigInteger, String, byte, short, int, long
@Size The element must be between two specified sizes String, Collection, Map, Array
@Digits The item must be a number within a certain window BigDecimal, BigInteger, String, byte, short, int, long
@Past The item must be a date in the past Date, Calendar
@Future The item must be a date in the future Date, Calendar
@Pattern The element must respect a regular expression String

Other frameworks such as Hibernate provide interesting constraints: @CreditCardNumber, @Email, @NotBlank, @NotEmpty, @Range, @ScriptAssert, @URL. These constraints may apply to an attribute, method, or class. In the majority of cases they will be placed at the attribute level as follows:

MyBean.java
@NotEmpty ()
    @Pattern (regexp = "[a-z-a-z]")
    @Size (max = 5)
    private String _strName;
    @Size (min = 10)
    private String _strDescription;
    @Min (value = 5)
    private int _nAge;
    @E-mail()
    private String _strEmail;
    @Past()
    private Date _dateBirth;
    @Future ()
    private Date _dateEndOfWorld;
    @DecimalMin (value = "1500.0")
    private BigDecimal _salary;
    @DecimalMax (value = "100.0")
    private BigDecimal _percent;
    @Digits (integer = 15, fraction = 2)
    private String _strCurrency;

Validation of the Bean Using an Instance of the Validator Class Will Produce a Set of Constraint Violations

// Check constraints
    Set <ConstraintViolation <Person >> errors = validator.validate (myBean);
    if (errors.size ()> 0)
    {
        // Handle errors
        ...
    }

The implementation proposed by Lutece from version 4.1


Contribution of the implementation compared to the basic implementation

The main contribution of the implementation proposed by Lutece lies in the management of error messages. Indeed it offers:

  • local management and plugin model support (error messages can be defined in plugin internationalized resource files)
  • management of the display of the list of errors for the back office
  • a standardized model providing for the use of standard or field-specific messages.

Messages in plugin resource files

The basic implementation of the JSR 303 offers default messages or customization using a single bundle validation.properties file. This can not be appropriate in the context of Lutece plugins. However, the standard provides that you can modify message handling by using a specific implementation of the MessageInterpolator interface. This is the choice for Lutece. A LuteceMessageInterpolator class has been created so that messages can use the features of I18nService, including writing to find the value of a key. The keys used for the messages can thus be managed in the same way as those of the HTML templates, with as convention: name_of_plugin.validation.name_object_metier.name_attribut.name_constraint.

The implementation of this specific MessageInterpolator is done through the configuration file META-INF / validation.xml.

Here is how to write the validation constraints on the fields of a business object:

// Person.java

  @NotEmpty (message = "")
  @Size (max = 20, message = "")
private String _strPersonName;

The keys are declared in the resource files as follows:

# myplugin_messages_en.properties
validation.person.personName.notEmpty = The 'Name' field is required. Please fill it out.
validation.person.personName.size = The field 'Name' must not contain more than 20 characters.

Standard messages offered by the implementation

The implementation offers default messages for a number of standard or specific annotations (Hibernate).

These messages are stored in the core resource file: validation_messages * .properties. Here is the list:

Annotation Key Message Origin of the annotation
@Size (min, max) portal.validation.message.size The value of the field {0} is invalid. The field must contain between {1} and {2} characters. Standard javax.validation
@Size (min) portal.validation.message.sizeMin The value of the field {0} must be larger than {2} characters. Standard javax.validation
@Size (max) portal.validation.message.sizeMax The value of the field {0} must be smaller than {2} characters. Standard javax.validation
@Pattern (regexp) portal.validation.message.pattern The format of the {0} field does not respect the form {1}. Standard javax.validation
@Min (value) portal.validation.message.min The value of the field {0} must be greater than {1}. Standard javax.validation
@Max (value) portal.validation.message.max The value of the field {0} must be less than {1}. Standard javax.validation
@DecimalMin (value) portal.validation.message.decimalMin The value of the field {0} must be greater than {1}. Standard javax.validation
@DecimalMax (value) portal.validation.message.decimalMax The value of the field {0} must be less than {1}. Standard javax.validation
@Digits (integer, fraction) portal.validation.message.digits The value of the field {0} must have an integer less than {1} digits and a decimal part less than {2} digits. Standard javax.validation
@Past portal.validation.message.past The date of field {0} must be earlier than the current date. Standard javax.validation
@Future portal.validation.message.future The date of field {0} must be after the current date. Standard javax.validation
@NotEmpty portal.validation.message.notEmpty The field {0} is required. Please fill it out. Hibernate validators
@Email portal.validation.message.email The value of the field {0} does not correspond to a valid email. Hibernate validators

The writing of these constraints in the bean is thus of the following form:

// MyBean.java

@NotEmpty(message = "Le champ {0} est obligatoire. Veuillez le remplir SVP.")
    @Pattern(regexp = "[a-z-A-Z]", message = "Le format du champ {0} ne respecte pas la forme {1}. ")
    @Size(max = 5, message = "La valeur du champ {0} doit avoir une taille inférieure à {2} caractères.")
    private String _strName;
    @Size(min = 10, max = 50, message = "La valeur du champ {0} est invalide. Le champ doit contenir entre {1} et {2} caractères.")
    private String _strDescription;
    @Min(value = 5, message = "La valeur du champ {0} doit être supérieure à {1}.")
    private int _nAge;
    @Email(message = "La valeur du champ {0} ne correspond pas à un email valide.")
    private String _strEmail;
    @Past(message = "La date du champ {0} doit être antérieure à la date du jour.")
    private Date _dateBirth;
    @Future(message = "La date du champ {0} doit être postérieure à la date du jour.")
    private Date _dateEndOfWorld;
    @DecimalMin(value = "1500.0", message = "La valeur du champ {0} doit être supérieure à {1}.")
    private BigDecimal _salary;
    @DecimalMax(value = "100.0", message = "La valeur du champ {0} doit être inférieure à {1}.")
    private BigDecimal _percent;
    @Digits(integer = 15, fraction = 2, message = "La valeur du champ {0} doit avoir une partie entière inférieure à {1} chiffres et une partie décimale inférieure à {2} chiffres.")
    private String _strCurrency;
    @URL( message = "La valeur du champ {0} ne correspond pas à une URL valide." )
    private String _strUrl;

Posting messages

A new ValidationError class has been created to format constraint violation messages by retrieving the field name and constraint parameters.

The list of methods for creating messages in the BackOffice of the AdminMessageService class has been extended to receive error lists as parameters:

  • Set <ConstraintViolation> for raw recovery of constraint messages or
  • List <ValidationError> to benefit from standard messages with field names (to be preferred)

Field names are retrieved through resource files. The keys will be defined in the resource files by using the following convention model.entity. <Entity> .attribute. <Attribute>.

The prefix of the keys for a given bean is: "model.entity.mybean.attribute."

For example, concerning the _strPersonName attribute of the Person class, here is the key for the resource file:

# myplugin_messages_fr.properties

model.entity.person.attribute.personName = No

In a JspBean of the BackOffice, here is the declaration of the prefix and the form that the validation of a bean takes:

// PersonJspBean.java
private static final String VALIDATION_ATTRIBUTES_PREFIX = "model.entity.person.attribute.";
        ...

    public String doCreatePerson (...)
    {
        ...
        // Check constraints
        List <ValidationError> errors = validate (person, VALIDATION_ATTRIBUTES_PREFIX);
        if (errors.size ()> 0)
        {
            return AdminMessageService.getMessageUrl (request, Messages.MESSAGE_INVALID_ENTRY, errors);
        }
        ...
    }

Advanced options for configuring ValidationError

The default options used to convert and format ConstraintViolation to ValidationError are defined in a DefaultValidatorErrorConfig class that implements the ValidatorErrorConfig class.

It is possible to extend this class to handle new constraint parameters or to change the rendering of the field name (surrounded by the default <strong> tag). It will then be necessary to pass this implementation to the validate method instead of the prefix (the latter also being part of the configuration parameters).