diff --git a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java index b95c0a3de3f..d6d9016afd1 100644 --- a/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java +++ b/spring-context-support/src/test/java/org/springframework/validation/beanvalidation2/SpringValidatorAdapterTests.java @@ -21,11 +21,16 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -148,6 +153,44 @@ public class SpringValidatorAdapterTests { is("Email required")); } + @Test // SPR-16177 + public void testWithList() { + Parent parent = new Parent(); + parent.setName("Parent whit list"); + parent.getChildList().addAll(createChildren(parent)); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(parent, "parent"); + validatorAdapter.validate(parent, errors); + + assertTrue(errors.getErrorCount() > 0); + } + + @Test // SPR-16177 + public void testWithSet() { + Parent parent = new Parent(); + parent.setName("Parent whith set"); + parent.getChildSet().addAll(createChildren(parent)); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(parent, "parent"); + validatorAdapter.validate(parent, errors); + + assertTrue(errors.getErrorCount() > 0); + } + + private List createChildren(Parent parent) { + Child child1 = new Child(); + child1.setName("Child1"); + child1.setAge(null); + child1.setParent(parent); + + Child child2 = new Child(); + child2.setName(null); + child2.setAge(17); + child2.setParent(parent); + + return Arrays.asList(child1, child2); + } + @Test // SPR-15839 public void testListElementConstraint() { BeanWithListElementConstraint bean = new BeanWithListElementConstraint(); @@ -308,6 +351,143 @@ public class SpringValidatorAdapterTests { } + public static class Parent { + + private Integer id; + + @NotNull + private String name; + + @Valid + private Set childSet = new LinkedHashSet<>(); + + @Valid + private List childList = new LinkedList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getChildSet() { + return childSet; + } + + public void setChildSet(Set childSet) { + this.childSet = childSet; + } + + public List getChildList() { + return childList; + } + + public void setChildList(List childList) { + this.childList = childList; + } + } + + + @AnythingValid + public static class Child { + + private Integer id; + + @javax.validation.constraints.NotNull + private String name; + + @javax.validation.constraints.NotNull + private Integer age; + + @javax.validation.constraints.NotNull + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + + @Constraint(validatedBy = AnythingValidator.class) + @Retention(RUNTIME) + public @interface AnythingValid { + + String message() default "{AnythingValid.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + + public static class AnythingValidator implements ConstraintValidator { + + private static final String ID = "id"; + + @Override + public void initialize(AnythingValid constraintAnnotation) { + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + List fieldsErros = new ArrayList<>(); + Arrays.asList(value.getClass().getDeclaredFields()).forEach(f -> { + f.setAccessible(true); + try { + if (!f.getName().equals(ID) && f.get(value) == null) { + fieldsErros.add(f); + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) + .addPropertyNode(f.getName()) + .addConstraintViolation(); + } + } catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } + + }); + return fieldsErros.isEmpty(); + } + } + + public class BeanWithListElementConstraint { @Valid diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index d15db694710..6f6115ef8aa 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -285,8 +285,8 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. @Nullable protected Object getRejectedValue(String field, ConstraintViolation violation, BindingResult bindingResult) { Object invalidValue = violation.getInvalidValue(); - if (!"".equals(field) && (invalidValue == violation.getLeafBean() || - (!field.contains("[]") && (field.contains("[") || field.contains("."))))) { + if (!"".equals(field) && !field.contains("[]") && + (invalidValue == violation.getLeafBean() || field.contains("[") || field.contains("."))) { // Possibly a bean constraint with property path: retrieve the actual property value. // However, explicitly avoid this for "address[]" style paths that we can't handle. invalidValue = bindingResult.getRawFieldValue(field); diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java index a4044dedd2f..befc921f748 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/SpringValidatorAdapterTests.java @@ -21,13 +21,22 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Locale; +import java.util.Set; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; +import javax.validation.Valid; import javax.validation.Validation; import javax.validation.Validator; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; @@ -141,6 +150,43 @@ public class SpringValidatorAdapterTests { is("Email required")); } + @Test // SPR-16177 + public void testWithList() { + Parent parent = new Parent(); + parent.setName("Parent whit list"); + parent.getChildList().addAll(createChildren(parent)); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(parent, "parent"); + validatorAdapter.validate(parent, errors); + + assertTrue(errors.getErrorCount() > 0); + } + + @Test // SPR-16177 + public void testWithSet() { + Parent parent = new Parent(); + parent.setName("Parent whith set"); + parent.getChildSet().addAll(createChildren(parent)); + + BeanPropertyBindingResult errors = new BeanPropertyBindingResult(parent, "parent"); + validatorAdapter.validate(parent, errors); + + assertTrue(errors.getErrorCount() > 0); + } + + private List createChildren(Parent parent) { + Child child1 = new Child(); + child1.setName("Child1"); + child1.setAge(null); + child1.setParent(parent); + + Child child2 = new Child(); + child2.setName(null); + child2.setAge(17); + child2.setParent(parent); + + return Arrays.asList(child1, child2); + } @Same(field = "password", comparingField = "confirmPassword") @@ -259,4 +305,141 @@ public class SpringValidatorAdapterTests { } } + + public static class Parent { + + private Integer id; + + @NotNull + private String name; + + @Valid + private Set childSet = new LinkedHashSet<>(); + + @Valid + private List childList = new LinkedList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getChildSet() { + return childSet; + } + + public void setChildSet(Set childSet) { + this.childSet = childSet; + } + + public List getChildList() { + return childList; + } + + public void setChildList(List childList) { + this.childList = childList; + } + } + + + @AnythingValid + public static class Child { + + private Integer id; + + @javax.validation.constraints.NotNull + private String name; + + @javax.validation.constraints.NotNull + private Integer age; + + @javax.validation.constraints.NotNull + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + + @Constraint(validatedBy = AnythingValidator.class) + @Retention(RUNTIME) + public @interface AnythingValid { + + String message() default "{AnythingValid.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } + + + public static class AnythingValidator implements ConstraintValidator { + + private static final String ID = "id"; + + @Override + public void initialize(AnythingValid constraintAnnotation) { + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + List fieldsErros = new ArrayList<>(); + Arrays.asList(value.getClass().getDeclaredFields()).forEach(f -> { + f.setAccessible(true); + try { + if (!f.getName().equals(ID) && f.get(value) == null) { + fieldsErros.add(f); + context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()) + .addPropertyNode(f.getName()) + .addConstraintViolation(); + } + } catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } + + }); + return fieldsErros.isEmpty(); + } + } + }