Question

Je veux pouvoir faire quelque chose comme:

@Email
public List<String> getEmailAddresses()
{
   return this.emailAddresses;
}

En d'autres termes, je veux que chaque élément de la liste soit validé en tant qu'adresse e-mail. Bien sûr, il n'est pas acceptable d'annoter une collection comme celle-ci.

Y a-t-il un moyen de faire cela?

Était-ce utile?

La solution

Ni JSR-303 ni le validateur Hibernate n'ont de contrainte prête à l'emploi qui peut valider chaque éléments de la collecte.

Une solution possible pour résoudre ce problème est de créer une personnalité @ValidCollection contrainte et mise en œuvre du validateur correspondant ValidCollectionValidator.

Pour valider chaque élément de la collection, nous avons besoin d'une instance de Validator à l'intérieur ValidCollectionValidator; Et pour obtenir une telle instance, nous avons besoin de l'implémentation personnalisée de ConstraintValidatorFactory.

Voyez si vous aimez suivre la solution ...

Simplement,

  • copier-coller toutes ces classes Java (et importer des classes relatives);
  • Ajouter la validation-API, l'hibenate-validator, SLF4J-LOG4J12 et TestNg Jars sur ClassPath;
  • Exécutez le cas de test.

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; }

}

CONSTRAINTVALIDATORFACTORYIMP

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;
    }

}

Employé

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; }

}

Équipe

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; }

}

Panier

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("me@gmail.com");

        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());
        }
    }

}

Production

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}

Noter:- Lorsque le bean a des contraintes ne spécifiez pas le constraints attribuer @ValidCollection contrainte. La constraints L'attribut est nécessaire lorsque Bean n'a aucune contrainte.

Autres conseils

Je n'ai pas une réputation suffisamment élevée pour commenter cela sur la réponse originale, mais peut-être qu'il convient de noter sur cette question que JSR-308 est dans sa dernière étape de version et résoudra ce problème lors de sa sortie! Cependant, il faudra au moins Java 8.

La seule différence serait que l'annotation de validation irait dans la déclaration de type.

//@Email
public List<@Email String> getEmailAddresses()
{
   return this.emailAddresses;
}

S'il vous plaît laissez-moi savoir où vous pensez que je pourrais mieux placer ces informations pour les autres qui recherchent. Merci!

PS pour plus d'informations, Découvrez ce post So.

Il n'est pas possible d'écrire une annotation générique en wrapper comme @EachElement Pour envelopper toute annotation de contrainte - en raison des limites des annotations Java elle-même. Cependant, vous pouvez écrire une classe de validateur de contraintes générique qui délégue la validation réelle de chaque élément à un validateur de contrainte existant. Vous devez écrire une annotation en wrapper pour chaque contrainte, mais juste un validateur.

J'ai mis en œuvre cette approche dans Jirutka / Validator-Collection (Disponible dans Maven Central). Par exemple:

@EachSize(min = 5, max = 255)
List<String> values;

Cette bibliothèque vous permet de créer facilement une «pseudo contrainte» pour n'importe quel contrainte de validation pour annoter une collection de types simples, sans écrire un validateur supplémentaire ou des classes de wrapper inutiles pour chaque collection. EachX La contrainte est prise en charge pour toutes les contraintes de validation des haricots standard et les contraintes spécifiques d'hibernate.

Pour créer un @EachAwesome pour toi @Awesome contrainte, copiez et collez la classe d'annotation, remplacez @Constraint annotation avec @Constraint(validatedBy = CommonEachValidator.class) et ajouter l'annotation @EachConstraint(validateAs = Awesome.class). C'est tout!

// common boilerplate
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE})
// this is important!
@EachConstraint(validateAs = Awesome.class)
@Constraint(validatedBy = CommonEachValidator.class)
public @interface EachAwesome {

    // copy&paste all attributes from Awesome annotation here
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String someAttribute();
}

Edit: mis à jour pour la version actuelle de la bibliothèque.

Merci pour la bonne réponse de Becomputer06. Mais je pense que les annotations suivantes devraient être ajoutées à la définition de validcollection:

@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidCollectionValidator.class)

Et je ne sais toujours pas quoi faire avec les collections de wrappers de type primitif et contraint les annotations comme @size, @min, @max etc., car la valeur ne peut pas être transmise par le chemin de Becomputer06.

Bien sûr, je peux créer des annotations de contributions personnalisées pour tous les cas de mon application, mais de toute façon, je dois ajouter des propriétés pour ces annotations à CollectionElementBean. Et cela semble être une solution suffisamment mauvaise.

JSR-303 a la capacité d'étendre les types cibles de contraintes intégrées: voir 7.1.2. Définitions de contrainte de remplacement dans XML.

Vous pouvez implémenter un ConstraintValidator<Email, List<String>> Ce qui fait la même chose que les réponses données, en déléguant au validateur primitif. Ensuite, vous pouvez conserver la définition de votre modèle et appliquer @Email sur List<String>.

Une solution de contournement très simple est possible. Vous pouvez plutôt valider une collection de vos classes qui enveloppent la propriété de valeur simple. Pour que cela fonctionne, vous devez utiliser @Valid Annotation sur la collection.

Exemple:

public class EmailAddress {

  @Email
  String email;

  public EmailAddress(String email){
    this.email = email;
  }
}

public class Foo {

  /* Validation that works */
  @Valid
  List<EmailAddress> getEmailAddresses(){
    return this.emails.stream().map(EmailAddress::new).collect(toList());
  }

}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top