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