Pregunta

Quiero poder hacer algo como:

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

En otras palabras, quiero que cada elemento de la lista se valida como una dirección de correo electrónico. Por supuesto, no es aceptable anotar una colección como esta.

¿Hay alguna forma de hacer esto?

¿Fue útil?

Solución

Ni JSR-303 ni Validador Hibernate tienen ninguna restricción preparada que pueda validar cada elemento de recolección.

Una posible solución para abordar este problema es crear una personalización @ValidCollection Restricción e implementación de validador correspondiente ValidCollectionValidator.

Para validar cada elemento de la colección, necesitamos una instancia de Validator en el interior ValidCollectionValidator; y para obtener tal instancia necesitamos una implementación personalizada de ConstraintValidatorFactory.

Vea si le gusta la siguiente solución ...

Simplemente,

  • Copiar Paste Todas estas clases de Java (e importar clases de relaves);
  • Agregar validación-api, hibenate-validator, slf4j-log4j12 y testng frascos en classpath;
  • Ejecute el caso de prueba.

Validolección

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

}

ValidatorContextAwarEcstaintValidator

public interface ValidatorContextAwareConstraintValidator {

    void setValidatorContext(ValidatorContext validatorContext);

}

Colección

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

}

RestrictValidatorFactoryImpl

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

}

Empleado

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

}

Equipo

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

}

Carrito de compras

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

}

VarkCollectionTest

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

}

Producción

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:- Cuando Bean tiene restricciones, no especifique el constraints atributo de @ValidCollection restricción. los constraints El atributo es necesario cuando Bean no tiene restricción.

Otros consejos

No tengo una reputación lo suficientemente alta como para comentar esto sobre la respuesta original, pero tal vez valga la pena señalar sobre esta pregunta que JSR-308 ¡Está en su etapa de lanzamiento final y abordará este problema cuando se lance! Sin embargo, al menos requerirá Java 8.

La única diferencia sería que la anotación de validación entraría dentro de la declaración de tipo.

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

Por favor, avíseme dónde cree que podría poner esta información para otros que están buscando. ¡Gracias!

PD para más información, Mira esto, así que publica.

No es posible escribir una anotación de envoltorio genérico como @EachElement para envolver cualquier anotación de restricción, debido a las limitaciones de las anotaciones de Java en sí. Sin embargo, puede escribir una clase de validador de restricciones genéricas que delega la validación real de cada elemento a un validador de restricción existente. Tienes que escribir una anotación de envoltura para cada restricción, pero solo un validador.

He implementado este enfoque en Jirutka/Validador-colección (Disponible en Maven Central). Por ejemplo:

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

Esta biblioteca le permite crear fácilmente una "pseudo restricción" para ningún Restricción de validación para anotar una colección de tipos simples, sin escribir un validador adicional o clases de envoltura innecesaria para cada colección. EachX La restricción es compatible para todas las restricciones de validación de frijol estándar y las restricciones específicas de hibernación.

Para crear un @EachAwesome para el tuyo @Awesome restricción, simplemente copie y pegue la clase de anotación, reemplace @Constraint anotación con @Constraint(validatedBy = CommonEachValidator.class) y agregar la anotación @EachConstraint(validateAs = Awesome.class). ¡Eso es todo!

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

Editar: Actualizado para la versión actual de la biblioteca.

Gracias por una gran respuesta de Becomputer06. Pero creo que las siguientes anotaciones deberían agregarse a la definición de valor de valor:

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

Y todavía no me ocurren qué hacer con las colecciones de envoltorios de tipo primitivo y restringe anotaciones como @size, @min, @max, etc., porque el valor no se puede pasar por el camino de Becomputer06.

Por supuesto, puedo crear anotaciones de contradicción personalizadas para todos los casos en mi aplicación, pero de todos modos tengo que agregar propiedades para estas anotaciones a CollectionElementBean. Y parece ser una solución bastante mala.

JSR-303 tiene la capacidad de extender los tipos objetivo de restricciones incorporadas: ver 7.1.2. Definiciones de restricción primaria en XML.

Puede implementar un ConstraintValidator<Email, List<String>> que hace lo mismo que las respuestas dadas, delegando al validador primitivo. Entonces puede mantener la definición de su modelo y aplicar @Email en List<String>.

Es posible una solución muy simple. En su lugar, puede validar una colección de sus clases que envuelven la propiedad de valor simple. Para que esto funcione, necesitas usar @Valid Anotación en la colección.

Ejemplo:

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

}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top