SpringValidatorAdapter skips value retrieval for Set field without index

Issue: SPR-16177
This commit is contained in:
Juergen Hoeller 2017-11-13 21:50:55 +01:00
parent 0e49e32188
commit 3091feee23
3 changed files with 365 additions and 2 deletions

View File

@ -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<Child> 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<Child> childSet = new LinkedHashSet<>();
@Valid
private List<Child> 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<Child> getChildSet() {
return childSet;
}
public void setChildSet(Set<Child> childSet) {
this.childSet = childSet;
}
public List<Child> getChildList() {
return childList;
}
public void setChildList(List<Child> 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<? extends Payload>[] payload() default {};
}
public static class AnythingValidator implements ConstraintValidator<AnythingValid, Object> {
private static final String ID = "id";
@Override
public void initialize(AnythingValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<Field> 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

View File

@ -285,8 +285,8 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
@Nullable
protected Object getRejectedValue(String field, ConstraintViolation<Object> 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);

View File

@ -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<Child> 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<Child> childSet = new LinkedHashSet<>();
@Valid
private List<Child> 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<Child> getChildSet() {
return childSet;
}
public void setChildSet(Set<Child> childSet) {
this.childSet = childSet;
}
public List<Child> getChildList() {
return childList;
}
public void setChildList(List<Child> 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<? extends Payload>[] payload() default {};
}
public static class AnythingValidator implements ConstraintValidator<AnythingValid, Object> {
private static final String ID = "id";
@Override
public void initialize(AnythingValid constraintAnnotation) {
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
List<Field> 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();
}
}
}