Bean Validation

References

Java EE 7 tuitor

https://openjpa.apache.org/bean-validation-primer.html

Using Bean Validation Constraints

  • Constraints in the form of annotations placed on Java Beans component (such as managed bean)

    • field, getter (not setter)

    • constructor and non-static method parameters and return values

    • class

  • can be

    • built-in : in javax.validation.constraints package

      • @AssertTrue / @AssertFalse

      • @DecimalMax / @DecimalMin("5.00")

      • @Digits(integer = 6, fraction=2)

      • @Future / @Past

      • @Max / @Min(5)

      • @NotNull / @Null

      • @Pattern(regexp="")

      • @Size(min=2, max=240) - for String, Collection, Map, Array

    • user defined

  • Chained validation:

    • @Valid @Embedded public Location getLocation();

    • when entity get validated, all constraints on Locatioin are also validated

    • in JPA, only embeddables can be @Valid chain validated; referenced entities and collections are validated separately

  • Validating Null and Empty Strings - set javax.faces.INTERPRETE_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL (JSF specific)

  • Validating constructors and methods

    • non-static methods and constructors, non-static method return values

    • cross-parameter constraints:

      • applied at method / constructor

      • to avoid confusion of cross-parameter constraint annotations and return value constraints, choose a name for custom contraints like @CustomParameter, or set validationAppliesTo element.

        • example:

        • @Manager(validationAppliesTo=ConstraintTarget.RETURN_VALUE)

        • public Employee getManager(Employee employee) { ... }

      • in case of parameter constraint violation, argPARAMETER_INDEX give position, starts from 0

Custom Constraints

Constraint target and removing ambiguity in target:

  • @Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })

  • on usage, specify validationAppliesTo

Using built-in constraints to make a new constraint, example:

@Pattern.List({

@Pattern(regexp = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."

+"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*"

+"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")

})

@Constraint(validatedBy = {})

@Documented

@Target({ElementType.METHOD,

ElementType.FIELD,

ElementType.ANNOTATION_TYPE,

ElementType.CONSTRUCTOR,

ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

public @interface Email {

String message() default "{invalid.email}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

@Target({ElementType.METHOD,

ElementType.FIELD,

ElementType.ANNOTATION_TYPE,

ElementType.CONSTRUCTOR,

ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@interface List {

Email[] value();

}

}

Validator

Usage:

Define the constraint annotation

@Documented @Constraint(validatedBy = ImageContentValidator.class) @Target({ METHOD, FIELD }) @Retention(RUNTIME) public @interface ImageContent { String message() default "Image data is not a supported format."; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; ImageType[] value() default { ImageType.GIF, ImageType.JPEG }; }

Implement the validator

public class SomeCustomValidator implements javax.validation.ConstraintValidator<ImageContent, byte[]> {

// constraint type is provided on initialization

public void initialize(ImageContent constraint) { allowedTypes = Arrays.asList(constraint.value()); } /** * Validate a specified value. */ public boolean isValid(byte[] value, ConstraintValidatorContext context) {

}

Constraint messages

  • Bean Validation includes a source bundle of default messages which can be customized

    • ValidationMessages.properties - place in default package of application

    • Local variants: ValidationMessages_cn.properties, etc.

Type-level Constraints

Verify an entity, instead of a single primary type.

Applying constraint

When other constraints are applied at field, method level, those first level check will be done first. Then, the type-level constraint is fired.

@Entity @ImageConstraint(groups=ImageGroup.class) public class Image {

Defining annotation

package org.apache.openjpa.example.gallery.constraint;

@Documented @Constraint(validatedBy = ImageValidator.class) @Target({ TYPE }) @Retention(RUNTIME) public @interface ImageConstraint { String message() default "Image data is not a supported format."; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; ImageType[] value() default { ImageType.GIF, ImageType.JPEG }; }

Implementing validator

public class ImageValidator implements ConstraintValidator<ImageConstraint,Image> { .....

public void initialize(ImageConstraint constraint) { allowedTypes = Arrays.asList(constraint.value()); } /** * Validate a specified value. */ public boolean isValid(Image value, ConstraintValidatorContext context) { ...... } }

JPA Note: entity might be partially loaded (LAZY fetch) or cannot be loaded (for example, detached). Use PersistenceUtil to help.

byte[] data = value.getData(); PersistenceUtil putil = Persistence.getPersistenceUtil(); if (!putil.isLoaded(value, "data")) { // don't validate the data }

Validation Groups

  • used to create subsets of constraints, to only validate for a particular object

  • by default, all constraints are in Default group : javax.validation.groups.Default

  • group is represented by interfaces: public interface GroupName{}

  • inherit: public interface InheritedGroup extends GroupName {}

  • when applying constraint, declare the groups to which that constraint belongs:

    • @ConstraintAnnotation(group=GroupName.class)

    • multiple groups: @ConstraintAnnotation(group={Group1.class, Group2.class})

  • customizing group validation sequence with a new group: @GroupSequence({Default.class, Group1.class}) public interface SequenceGroup{}

Example:

See below JPA integration

JPA Integration

If validation provider is on classpath, validation is enabled by default.

Validation mode:

  • auto (default) : validate when validation provider is available

  • callback (recommended) : throw exception if provider not found.

  • none (recommended if not desired)

Configure validation modes either by <validation-mode>AUTO</validation-mode> in persistence unit, or put a property when creating entity manager factory.

Bean validation occurs during JPA lifecycle event processing, at final stage of PrePersist, PreUpdate and PreRemove events, after all user defined lifecycle events.

By default, the Default group is validated for PrePersist and PreUpdate. PreRemove is not validated.

Customize:

<persistence-unit name="non-default-validation-groups"> <class>my.Entity</class> <validation-mode>CALLBACK</validation-mode> <properties> <property name="javax.persistence.validation.group.pre-persist" value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/> <property name="javax.persistence.validation.group.pre-update" value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/> <property name="javax.persistence.validation.group.pre-remove" value="javax.validation.groups.Default"/> </properties> </persistence-unit>

Type Hierarchies

Example: Person <- Employee

Constraint to parameter of subclass method call (Employee) should not be strengthened: If some validation is applied to Employee, parameters that were valid with Person may NOT be valid with Employee, therefore "strengthening the preconditions". This should be avoided. (break protocol)

Return value of parent class method call should not be weakened (weakening the post-conditions)

ConstraintDeclarationException will be thrown if any this happens.

This also applies to implement multiple interface scenarios.

Rules:

  • when override / implement methods, do not add method parameter constraints (pre-conditions)

  • when override / implement methods, do not add method parameter constraints that was originally declared in several parallel types (interfaces)

  • may add return value constraints to an overriden / implemented method in subtype