Domanda

Voglio essere in grado di fare qualcosa del tipo:

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

In altre parole, voglio che ogni elemento nell'elenco venga convalidato come indirizzo e -mail. Certo, non è accettabile annotare una collezione come questa.

C'è un modo per fare questo?

È stato utile?

Soluzione

Né JSR-303 né Hibernate Validatore hanno alcun vincolo pronto in grado di convalidare ogni elemento di raccolta.

Una possibile soluzione per affrontare questo problema è creare un'usanza @ValidCollection vincolo e corrispondente implementazione del validatore ValidCollectionValidator.

Per convalidare ogni elemento di raccolta abbiamo bisogno di un'istanza di Validator dentro ValidCollectionValidator; e per ottenere tale istanza abbiamo bisogno di implementazione personalizzata di ConstraintValidatorFactory.

Vedi se ti piace seguire la soluzione ...

Semplicemente,

  • Copia incolla tutte queste classi Java (e classi di import religiose);
  • Aggiungi convalida-API, Hibenato-Validator, SLF4J-LOG4J12 e Testng Jars su ClassPath;
  • Esegui il caso di prova.

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

}

VintentValidatorFactoryImpl

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

}

Dipendente

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

}

Squadra

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

}

Carrello della spesa

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

}

Produzione

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}

Nota:- Quando il bean ha vincoli non specificano il constraints attributo di @ValidCollection vincolo. Il constraints L'attributo è necessario quando Bean non ha vincoli.

Altri suggerimenti

Non ho una reputazione abbastanza alta da commentare questo sulla risposta originale, ma forse vale la pena notare su questa domanda che JSR-308 è nella sua fase di rilascio finale e affronterà questo problema quando viene rilasciato! Richiederà almeno Java 8, tuttavia.

L'unica differenza sarebbe che l'annotazione di convalida andrebbe all'interno della dichiarazione del tipo.

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

Per favore fatemi sapere dove pensi di poter mettere meglio queste informazioni per gli altri che stanno guardando. Grazie!

PS per maggiori informazioni, Dai un'occhiata a questo POST.

Non è possibile scrivere una generica annotazione del wrapper come @EachElement Avvolgere qualsiasi annotazione del vincolo - a causa delle limitazioni delle annotazioni Java stesso. Tuttavia, è possibile scrivere una classe di validatore di vincoli generici che delega la convalida effettiva di ogni elemento a un validatore di vincoli esistente. Devi scrivere un'annotazione wrapper per ogni vincolo, ma solo un validatore.

Ho implementato questo approccio in Collezione Jirutka/Validatore (Disponibile in Maven Central). Per esempio:

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

Questa libreria ti consente di creare facilmente un "pseudo vincolo" per qualunque Vincolo di convalida per annotare una raccolta di tipi semplici, senza scrivere un validatore extra o lezioni di avvolgimento inutili per ogni raccolta. EachX Il vincolo è supportato per tutti i vincoli di convalida dei fagioli standard e vincoli specifici in letargo.

Per creare un @EachAwesome per il tuo @Awesome vincolo, basta copiare e incollare la classe di annotazione, sostituire @Constraint annotazione con @Constraint(validatedBy = CommonEachValidator.class) e aggiungi l'annotazione @EachConstraint(validateAs = Awesome.class). È tutto!

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

Modifica: aggiornato per la versione corrente della libreria.

Grazie per la grande risposta da Becomputer06. Ma penso che le seguenti annotazioni debbano essere aggiunte alla definizione di validità:

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

E non ho ancora inopportuno cosa fare con le raccolte di involucri di tipo primitivo e limita annotazioni come @size, @min, @max ecc., Perché il valore non può essere passato attraverso la via di Becomputer06.

Naturalmente, posso creare annotazioni di contrapposto personalizzate per tutti i casi nella mia applicazione, ma comunque devo aggiungere proprietà per queste annotazioni a CollectionElementBean. E sembra essere una soluzione abbastanza brutta.

JSR-303 ha la capacità di estendere i tipi target di vincoli integrati: vedere 7.1.2. Definizioni di vincoli prevalenti in XML.

Puoi implementare un file ConstraintValidator<Email, List<String>> Il che fa la stessa cosa delle risposte fornite, delegando al validatore primitivo. Quindi puoi mantenere la definizione del tuo modello e applicare @Email Su List<String>.

È possibile una soluzione molto semplice. Puoi invece convalidare una raccolta delle tue classi che avvolgono la proprietà del valore semplice. Perché questo funzioni devi usare @Valid Annotazione sulla collezione.

Esempio:

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

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top