Bean Validation
Bean Validation
0. Intro
1. Standard Bean Validation
2. Custom Constraints / Validators
3. Cross-field Validation
4. Dynamic Data Driven Application
1. Standard Bean Validation
The Bean Validation model is supported by constraints in the form of
annotations placed on a field, method, or class of a JavaBeans component,
such as a managed bean. All Java EE 7 Built-in Constraints may be found here
Java EE 7 Validation Constraints
Sample Constraints:
● @NotNull
● @Min(integer minimum); @Max(integer maximum);
● @Past; @Future
● @Size(min = ; max = );
Hibernate Validator
Bean Validation API Hibernate Validator adds some useful constraints to those
built into EE 7. The full list can be found here Hibernate Custom Constraints
Some of the more interesting ones include:
● @NotEmpty
● @NotBlank
● @Email(regexp=, flags=)
● @SafeHtml (whitelistType=, additionalTags=)
● @URL(protocol=, host=, port=, regexp=, flags=)
Apply Constraints to Entity Fields
Simple Implementation
@Entity
public class Person {
@NotBlank(message = "Please enter first name.")
private String firstname;
@CheckDate(“YYYY”)
private String publicationDate;
@CheckDate([“YYYY”, “YYYY-MM”])
private String collectionDate;
……
}
Custom Validation Annotation
The first step to writing a custom constraint is to write the annotation as follows:
@Target( {FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = CheckDateValidator.class)
public @interface CheckDate {
//required
String message() default "Please enter a valid date for this field. “;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//optional
String[] dateFormat();
}
Validator Class
public class CheckDateValidator implements ConstraintValidator<CheckDate, String> {
String dateFormat[] = [“”];
@Override
public void initialize(CheckDate constraintAnnotation) {
dateFormat = constraintAnnotation.dateFormat();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return validate(value); // shown in next slide
}
Validator Class (Continued)
private boolean validate(String dateString) {
boolean valid = false;
if (dateString.length == 0){
return true;
}
for (String format : dateFormat) {
if (!valid && (format.isEmpty() || format.toUpperCase().equals("YYYY-MM-DD") )) {
valid = isValidDate(dateString, "yyyy-MM-dd");
}
if (!valid && (format.isEmpty() || format.toUpperCase().equals("YYYY-MM"))) {
valid = isValidDate(dateString, "yyyy-MM");
}
if (!valid && (format.isEmpty() || format.toUpperCase().equals("YYYY"))) {
valid = isValidDate(dateString, "yyyy");
}
}
return valid;
}
Validator Class (Continued)
private boolean isValidDate(String dateString, String pattern) {
boolean valid=true;
Date date;
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setLenient(false);
try {
dateString = dateString.trim();
date = sdf.parse(dateString);
}catch (ParseException e) {
valid=false;
}
return valid;
}
}
3. Cross-field Validation
...
}
Custom Validation Annotation
/**Validates that field {@code dependFieldName} is not null if field {@code fieldName} has value {@code fieldValue}.*/
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
@Documented
public @interface NotNullIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Validator Class
public class NotNullIfAnotherFieldHasValueValidator implements ConstraintValidator<NotNullIfAnotherFieldHasValue,
Object> {
@Override
public void initialize(final NotNullIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
...
Validator Class (continued)
@Override
public boolean isValid(final Object value, final ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
final String fieldValue = BeanUtils.getProperty(value, fieldName);
final String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Apply Annotation to Fields
@NotNullIfAnotherFieldHasValue.List({
@NotNullIfAnotherFieldHasValue(
fieldName = "department",
fieldValue = "Other",
dependFieldName = "otherDepartment"),
@NotNullIfAnotherFieldHasValue(
fieldName = "position",
fieldValue = "Other",
dependFieldName = "otherPosition")
})
public class SampleBean {
private String department;
private String otherDepartment;
private String position;
private String otherPosition;
...
}
4. Dynamic Data Driven Application
Software framework for publishing,
citing and preserving research data
(open source on github for others to install)
@Entity
@ValidateDatasetFieldType
public class DatasetField implements Serializable {
@Entity
@ValidateDatasetFieldType
public class DatasetFieldValue implements Serializable {
Custom Validation Annotation
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {DatasetFieldValidator.class, DatasetFieldValueValidator.class})
@Documented
public @interface ValidateDatasetFieldType {
}
Validator Class
public class DatasetFieldValueValidator implements ConstraintValidator<ValidateDatasetFieldType, DatasetFieldValue> {
if (fieldType.equals("float")) {
try {
Double.parseDouble(value.getValue());
} catch (Exception e) {
context.buildConstraintViolationWithTemplate(" " + dsfType.getDisplayName() + " is not a valid number.").
addConstraintViolation();
return false;
}
}
Validator Class (continued)
public class DatasetFieldValidator implements ConstraintValidator<ValidateDatasetFieldType, DatasetField> {
if (dontSave) {
return "";
}
… // continue save process
Using the Validator (continued)
for (DatasetField dsf : workingVersion.getFlatDatasetFields()) {
Set<ConstraintViolation<DatasetField>> constraintViolations = validator.validate(dsf);
for (ConstraintViolation<DatasetField> constraintViolation : constraintViolations) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Validation Error", constraintViolation.getMessage()));
dontSave = true;
break; // currently only support one message, so we can break out of the loop after the first constraint violation
}
for (DatasetFieldValue dsfv : dsf.getDatasetFieldValues()) {
Set<ConstraintViolation<DatasetFieldValue>> constraintViolations2 = validator.validate(dsfv);
for (ConstraintViolation<DatasetFieldValue> constraintViolation : constraintViolations2) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Validation Error", constraintViolation.getMessage()));
dontSave = true;
break; // currently only support one message, so we can break out of the loop after the first constraint violation
}
}
}
Using the Validator (continued)
Using the Validator (continued)
DatasetField.java and DatasetFieldValue.java:
@Transient
private String validationMessage;