简体   繁体   中英

Validating collection using Bean Validation is not returning invalid elements properly

I am trying to create a BV constraint validator for collections following the example in the answer of this link .

public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {

    private ValidatorContext validatorContext;

    public ConstraintValidatorFactoryImpl(ValidatorContext nativeValidator) {
        this.validatorContext = nativeValidator;
    }

    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        T instance = null;
        try {
            instance = key.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        if(ValidatorContextAwareConstraintValidator.class.isAssignableFrom(key)) {
            ValidatorContextAwareConstraintValidator validator = (ValidatorContextAwareConstraintValidator) instance;
            validator.setValidatorContext(validatorContext);
        }

        return instance;
    }
}

Here is the validator.

public class CinCodeValidator implements ConstraintValidator<CinCode, String> {

    private static Pattern cinCodePattern;

    public void initialize(CinCode constraintAnnotation) {
        if (cinCodePattern == null) {
            cinCodePattern = Pattern.compile("([0-9]{1,5})");
        }
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        boolean result = false;
        if (isNotNull(value)) {
            result = matchCode(value);
        }
        if(!result) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate( "Invalid Cin code"  ).addConstraintViolation();
        }
        return result;
    }

    private static boolean isNotNull(Object obj) {
        return obj != null;
    }

    private boolean matchCode(String value) {
        Matcher matcher = cinCodePattern.matcher(value);
        return matcher.matches();
    }
}


public class CollectionElementBean {
    private String cinCode;

    @CinCode(message = "This should be a valid CIN code")
    public String getCinCode() {
        return cinCode;
    }

    public void setCinCode(String cinCode) {
        this.cinCode = cinCode;
    }
}


public interface ValidatorContextAwareConstraintValidator {
    void setValidatorContext(ValidatorContext validatorContext);
}

Validator for collection:

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;

    public void setValidatorContext(ValidatorContext validatorContext) {
        this.validatorContext = validatorContext;
    }

    public void initialize(ValidCollection constraintAnnotation) {
        elementType = constraintAnnotation.elementType();
        constraints = constraintAnnotation.constraints();
        allViolationMessages = constraintAnnotation.allViolationMessages();
    }

    public boolean isValid(Collection collection, ConstraintValidatorContext context) {
        boolean valid = true;
        if (collection == null) {
            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));  // Here, only failed values get added.
                }
            }

            if (!violations.isEmpty()) {
                valid = false;
            }

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

}

Form with List of GEO Id, State number and CIN:

public class FormWithCollection {
    private List<String> cinCodes;
    @NotNull
    @ValidCollection(elementType = String.class, constraints = { CinCode.class })
    public List<String> getCinCodes() {
        return cinCodes;
    }
    public void setCinCodes(List<String> cinCodes) {
        this.cinCodes = cinCodes;
    }
    //Same goes for GEO Id and State number    

}

Test program:

public class ValidCollectionTest {
    private ValidatorFactory validatorFactory;

    @Before
    public void createValidatorFactory() {
        validatorFactory = Validation.buildDefaultValidatorFactory();
    }

    private Validator getValidator() {
        ValidatorContext validatorContext = validatorFactory.usingContext();
        validatorContext.constraintValidatorFactory(new ConstraintValidatorFactoryImpl(validatorContext));
        Validator validator = validatorContext.getValidator();
        return validator;
    }

    /**
    A valid CIN code is 1 to 5 digit numeric.
    */
    @Test    
    public void validateCollectionWithInvalidCIN() {
        FormWithCollection formWithCollection = new FormWithCollection();
        formWithCollection.setCinCode(Arrays.asList("12345", "111a1"));
        Validator validator = getValidator();

        Set<ConstraintViolation<FormWithCollection>> violations = validator.validate(formWithCollection, Default.class);
        for (ConstraintViolation<FormWithCollection> violation : violations) {
            System.out.println(violation.getMessage() + "\t" + violation.getInvalidValue());
        }
        Assert.assertEquals(1, violations.size());  // I expect to see just 1 violation as there is only one invalid value "111a1". But 2 violations are returned.
    }
}

In ValidCollectionTest.java , Set of ConstraintViolation is iterated to list out all the violations. I tried to list each violation.getInvalidValue() to let the user know. But getInvalidValue() returns the whole collection instead of the failed value alone.

I'd like to show the user the invalid values, because I have a form like this:

+-----------+----------+---------+
|Geo ID     |State #   |CIN      |
+-----------+----------+---------+
|           |          |         |
+-----------+----------+---------+
|           |          |         |
+-----------+----------+---------+
|           |          |         |
+-----------+----------+---------+

Where GEO Id, state number and CIN are three different input formats.

Is there a workaround to this problem to enlist only the failed values ?

I don't think that the solution in the post you are referring to is a good idea. Do you need validation for one particular constraint? If so just create a ConstraintValidator<MyConstraint, Collection> . Iterate the passed collection and validate each element yourself. It would also help to show the code you have (constraints, constraint validator, etc). Or in case you are using Java 8, you could try the latest Hibernate Validator 5.2 version which allows you to use type annotations, eg List<@MyConstraint String> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM