Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Validation of Collections of Primitives

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?

like image 601
scrotty Avatar asked Nov 29 '10 21:11

scrotty


1 Answers

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,

  • copy-paste all these java classes (and import relavent classes);
  • add validation-api, hibenate-validator, slf4j-log4j12, and testng jars on classpath;
  • run the test-case.

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.

like image 123
dira Avatar answered Oct 06 '22 05:10

dira