diff --git a/src/main/java/cz/jirutka/validator/collection/CommonEachValidator.java b/src/main/java/cz/jirutka/validator/collection/CommonEachValidator.java index 6466ec3..7fdd0c4 100644 --- a/src/main/java/cz/jirutka/validator/collection/CommonEachValidator.java +++ b/src/main/java/cz/jirutka/validator/collection/CommonEachValidator.java @@ -76,6 +76,8 @@ public class CommonEachValidator implements ConstraintValidator(2); if (eachAType.isAnnotationPresent(EachConstraint.class)) { - Class constraintClass = eachAType.getAnnotation(EachConstraint.class).validateAs(); + EachConstraint eachConstraint = eachAType.getAnnotation(EachConstraint.class); + Class constraintClass = eachConstraint.validateAs(); Annotation constraint = createConstraintAndCopyAttributes(constraintClass, eachAnnotation); ConstraintDescriptor descriptor = createConstraintDescriptor(constraint); descriptors = unmodifiableList(asList(descriptor)); + stopAfterFoundFirstInvalidField = eachConstraint.stopAfterFoundFirstInvalidField(); // legacy and deprecated, will be removed in next major version! } else if (isWrapperAnnotation(eachAType)) { @@ -130,6 +134,7 @@ public boolean isValid(Collection collection, ConstraintValidatorContext cont } context.disableDefaultConstraintViolation(); //do not add wrapper's message + boolean valid = true; int index = 0; for (Iterator it = collection.iterator(); it.hasNext(); index++) { Object element = it.next(); @@ -152,11 +157,14 @@ public boolean isValid(Collection collection, ConstraintValidatorContext cont : readAttribute(descriptor.getAnnotation(), "message", String.class); addConstraintViolationInIterable(context, message, index); - return false; + if (stopAfterFoundFirstInvalidField) { + return false; + } + valid = false; } } } - return true; + return valid; } public void setValidatorFactory(ValidatorFactory factory) { diff --git a/src/main/java/cz/jirutka/validator/collection/constraints/EachConstraint.java b/src/main/java/cz/jirutka/validator/collection/constraints/EachConstraint.java index a1bfb29..fdbbbb0 100644 --- a/src/main/java/cz/jirutka/validator/collection/constraints/EachConstraint.java +++ b/src/main/java/cz/jirutka/validator/collection/constraints/EachConstraint.java @@ -47,4 +47,9 @@ * will be validated with the validator of this constraint. */ Class validateAs(); + + /** + * Indicates whether stop the validation after first invalid field found. + */ + boolean stopAfterFoundFirstInvalidField() default true; } diff --git a/src/test/groovy/cz/jirutka/validator/collection/CommonEachValidatorIT.groovy b/src/test/groovy/cz/jirutka/validator/collection/CommonEachValidatorIT.groovy index 3c49925..9f4f9e8 100644 --- a/src/test/groovy/cz/jirutka/validator/collection/CommonEachValidatorIT.groovy +++ b/src/test/groovy/cz/jirutka/validator/collection/CommonEachValidatorIT.groovy @@ -135,23 +135,49 @@ class CommonEachValidatorIT extends Specification { ['ab', 'cd'] | 'valid values' || true } + def 'validate @EachX for dont stop after found first invalid field [ #desc ]'() { + given: + constraint = '@EachNotNullAll' + expect: + assertViolations values, isValid, invalidIndexes, 'may not be null' + where: + values | desc || isValid || invalidIndexes + ['a', null, null] | '2 invalid values' || false || [1, 2] + ['a', null, 'c'] | 'an invalid value' || false || [1] + ['a', 'b', 'c'] | 'not null values' || true || [] + } //////// Helpers //////// void assertViolations(Object value, boolean shouldBeValid, Integer invalidIndex, String expectedMessage) { + assertViolations(value, shouldBeValid, [ invalidIndex ], expectedMessage) + } + + void assertViolations(Object value, boolean shouldBeValid, List invalidIndexes, String expectedMessage) { def entity = evalClassWithConstraint(constraint, value) - def propertyPath = HV_VERSION >= 5_0_0 ? "valuesList[${invalidIndex}]" : 'valuesList' - def violations = validate(entity) - - assert violations.isEmpty() == shouldBeValid - - if (!shouldBeValid) { - assert violations.size() == 1 - assert violations[0].invalidValue == value - assert violations[0].propertyPath.toString() == propertyPath - assert violations[0].rootBean.is(entity) - assert violations[0].rootBeanClass == entity.class - assert violations[0].message == expectedMessage + for (invalidIndex in invalidIndexes) { + def propertyPath = HV_VERSION >= 5_0_0 ? "valuesList[${invalidIndex}]" : 'valuesList' + def violations = validate(entity) + + assert violations.isEmpty() == shouldBeValid + + if (!shouldBeValid) { + if(HV_VERSION >= 5_0_0) { + assert violations.size() == invalidIndexes.size() + } else { + assert violations.size() == 1 + } + for (violation in violations) { + def next = violation.propertyPath.iterator().next() + println next.key; + if (violation.propertyPath.toString() == propertyPath) { + assert violation.invalidValue == value + assert violation.rootBean.is(entity) + assert violation.rootBeanClass == entity.class + assert violation.message == expectedMessage + } + } + } } } } diff --git a/src/test/java/cz/jirutka/validator/collection/fixtures/EachNotNullAll.java b/src/test/java/cz/jirutka/validator/collection/fixtures/EachNotNullAll.java new file mode 100644 index 0000000..d0653ff --- /dev/null +++ b/src/test/java/cz/jirutka/validator/collection/fixtures/EachNotNullAll.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * + * Copyright 2013-2014 Jakub Jirutka . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package cz.jirutka.validator.collection.fixtures; + +import cz.jirutka.validator.collection.CommonEachValidator; +import cz.jirutka.validator.collection.constraints.EachConstraint; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.constraints.NotNull; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @see NotNull + * @see CommonEachValidator + */ +@Documented +@Retention(RUNTIME) +@Target({METHOD, FIELD, ANNOTATION_TYPE}) +@EachConstraint(validateAs = NotNull.class, stopAfterFoundFirstInvalidField = false) +@Constraint(validatedBy = CommonEachValidator.class) +public @interface EachNotNullAll { + + String message() default ""; + + Class[] groups() default { }; + + Class[] payload() default { }; +}