I want to be able to do something like:
@Email public List<String> getEmailAddresses() { return this.emailAddresses; }
In other words, I want each item in the list to be validated as an email address. Of course, it is not acceptable to annotate a collection like this.
Is there a way to do this?
Neither JSR-303 nor Hibernate Validator has any ready-made constraint that can validate each elements of Collection.
One possible solution to address this issue is to create a custom @ValidCollection
constraint and corresponding validator implementation ValidCollectionValidator
.
To validate each element of collection we need an instance of Validator
inside ValidCollectionValidator
; and to get such instance we need custom implementation of ConstraintValidatorFactory
.
See if you like following solution...
Simply,
ValidCollection
public @interface ValidCollection { Class<?> elementType(); /* Specify constraints when collection element type is NOT constrained * validator.getConstraintsForClass(elementType).isBeanConstrained(); */ Class<?>[] constraints() default {}; boolean allViolationMessages() default true; String message() default "{ValidCollection.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
ValidCollectionValidator
public class ValidCollectionValidator implements ConstraintValidator<ValidCollection, Collection>, ValidatorContextAwareConstraintValidator { private static final Logger logger = LoggerFactory.getLogger(ValidCollectionValidator.class); private ValidatorContext validatorContext; private Class<?> elementType; private Class<?>[] constraints; private boolean allViolationMessages; @Override public void setValidatorContext(ValidatorContext validatorContext) { this.validatorContext = validatorContext; } @Override public void initialize(ValidCollection constraintAnnotation) { elementType = constraintAnnotation.elementType(); constraints = constraintAnnotation.constraints(); allViolationMessages = constraintAnnotation.allViolationMessages(); } @Override public boolean isValid(Collection collection, ConstraintValidatorContext context) { boolean valid = true; if(collection == null) { //null collection cannot be validated return false; } Validator validator = validatorContext.getValidator(); boolean beanConstrained = validator.getConstraintsForClass(elementType).isBeanConstrained(); for(Object element : collection) { Set<ConstraintViolation<?>> violations = new HashSet<ConstraintViolation<?>> (); if(beanConstrained) { boolean hasValidCollectionConstraint = hasValidCollectionConstraint(elementType); if(hasValidCollectionConstraint) { // elementType has @ValidCollection constraint violations.addAll(validator.validate(element)); } else { violations.addAll(validator.validate(element)); } } else { for(Class<?> constraint : constraints) { String propertyName = constraint.getSimpleName(); propertyName = Introspector.decapitalize(propertyName); violations.addAll(validator.validateValue(CollectionElementBean.class, propertyName, element)); } } if(!violations.isEmpty()) { valid = false; } if(allViolationMessages) { //TODO improve for(ConstraintViolation<?> violation : violations) { logger.debug(violation.getMessage()); ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(violation.getMessage()); violationBuilder.addConstraintViolation(); } } } return valid; } private boolean hasValidCollectionConstraint(Class<?> beanType) { BeanDescriptor beanDescriptor = validatorContext.getValidator().getConstraintsForClass(beanType); boolean isBeanConstrained = beanDescriptor.isBeanConstrained(); if(!isBeanConstrained) { return false; } Set<ConstraintDescriptor<?>> constraintDescriptors = beanDescriptor.getConstraintDescriptors(); for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) { if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) { return true; } } Set<PropertyDescriptor> propertyDescriptors = beanDescriptor.getConstrainedProperties(); for(PropertyDescriptor propertyDescriptor : propertyDescriptors) { constraintDescriptors = propertyDescriptor.getConstraintDescriptors(); for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) { if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) { return true; } } } return false; } }
ValidatorContextAwareConstraintValidator
public interface ValidatorContextAwareConstraintValidator { void setValidatorContext(ValidatorContext validatorContext); }
CollectionElementBean
public class CollectionElementBean { /* add more properties on-demand */ private Object notNull; private String notBlank; private String email; protected CollectionElementBean() { } @NotNull public Object getNotNull() { return notNull; } public void setNotNull(Object notNull) { this.notNull = notNull; } @NotBlank public String getNotBlank() { return notBlank; } public void setNotBlank(String notBlank) { this.notBlank = notBlank; } @Email public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
ConstraintValidatorFactoryImpl
public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory { private ValidatorContext validatorContext; public ConstraintValidatorFactoryImpl(ValidatorContext nativeValidator) { this.validatorContext = nativeValidator; } @Override public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) { T instance = null; try { instance = key.newInstance(); } catch (Exception e) { // could not instantiate class e.printStackTrace(); } if(ValidatorContextAwareConstraintValidator.class.isAssignableFrom(key)) { ValidatorContextAwareConstraintValidator validator = (ValidatorContextAwareConstraintValidator) instance; validator.setValidatorContext(validatorContext); } return instance; } }
Employee
public class Employee { private String firstName; private String lastName; private List<String> emailAddresses; @NotNull public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @ValidCollection(elementType=String.class, constraints={Email.class}) public List<String> getEmailAddresses() { return emailAddresses; } public void setEmailAddresses(List<String> emailAddresses) { this.emailAddresses = emailAddresses; } }
Team
public class Team { private String name; private Set<Employee> members; public String getName() { return name; } public void setName(String name) { this.name = name; } @ValidCollection(elementType=Employee.class) public Set<Employee> getMembers() { return members; } public void setMembers(Set<Employee> members) { this.members = members; } }
ShoppingCart
public class ShoppingCart { private List<String> items; @ValidCollection(elementType=String.class, constraints={NotBlank.class}) public List<String> getItems() { return items; } public void setItems(List<String> items) { this.items = items; } }
ValidCollectionTest
public class ValidCollectionTest { private static final Logger logger = LoggerFactory.getLogger(ValidCollectionTest.class); private ValidatorFactory validatorFactory; @BeforeClass public void createValidatorFactory() { validatorFactory = Validation.buildDefaultValidatorFactory(); } private Validator getValidator() { ValidatorContext validatorContext = validatorFactory.usingContext(); validatorContext.constraintValidatorFactory(new ConstraintValidatorFactoryImpl(validatorContext)); Validator validator = validatorContext.getValidator(); return validator; } @Test public void beanConstrained() { Employee se = new Employee(); se.setFirstName("Santiago"); se.setLastName("Ennis"); se.setEmailAddresses(new ArrayList<String> ()); se.getEmailAddresses().add("segmail.com"); Employee me = new Employee(); me.setEmailAddresses(new ArrayList<String> ()); me.getEmailAddresses().add("[email protected]"); Team team = new Team(); team.setMembers(new HashSet<Employee>()); team.getMembers().add(se); team.getMembers().add(me); Validator validator = getValidator(); Set<ConstraintViolation<Team>> violations = validator.validate(team); for(ConstraintViolation<Team> violation : violations) { logger.info(violation.getMessage()); } } @Test public void beanNotConstrained() { ShoppingCart cart = new ShoppingCart(); cart.setItems(new ArrayList<String> ()); cart.getItems().add("JSR-303 Book"); cart.getItems().add(""); Validator validator = getValidator(); Set<ConstraintViolation<ShoppingCart>> violations = validator.validate(cart, Default.class); for(ConstraintViolation<ShoppingCart> violation : violations) { logger.info(violation.getMessage()); } } }
Output
02:16:37,581 INFO main validation.ValidCollectionTest:66 - {ValidCollection.message} 02:16:38,303 INFO main validation.ValidCollectionTest:66 - may not be null 02:16:39,092 INFO main validation.ValidCollectionTest:66 - not a well-formed email address 02:17:46,460 INFO main validation.ValidCollectionTest:81 - may not be empty 02:17:47,064 INFO main validation.ValidCollectionTest:81 - {ValidCollection.message}
Note:- When bean has constraints do NOT specify the constraints
attribute of @ValidCollection
constraint. The constraints
attribute is necessary when bean has no constraint.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With