使用Bean Validation验证集合不会正确返回无效元素

时间:2015-04-22 15:23:25

标签: java spring-mvc bean-validation hibernate-validator

我正在尝试按照this link的答案中的示例为集合创建BV约束验证器。

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

这是验证器。

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

收集验证器:

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

}

带有GEO Id列表,州号和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    

}

测试程序:

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

在ValidCollectionTest.java中,迭代Set of ConstraintViolation以列出所有违规。我试图列出每个violation.getInvalidValue()让用户知道。但getInvalidValue()仅返回整个集合而不是失败的值。

我想向用户显示无效值,因为我有一个这样的表单:

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

其中GEO Id,州号和CIN是三种不同的输入格式。

此问题是否有解决方法只能登记失败的值?

1 个答案:

答案 0 :(得分:0)

我认为您所指的帖子中的解决方案不是一个好主意。您是否需要针对一个特定约束进行验证?如果是这样,只需创建一个ConstraintValidator<MyConstraint, Collection>。迭代传递的集合并自己验证每个元素。它还有助于显示您拥有的代码(约束,约束验证器等)。或者如果您使用的是Java 8,您可以尝试使用最新的Hibernate Validator 5.2版本,它允许您使用类型注释,例如List<@MyConstraint String>