Skip to content

Commit 955665b

Browse files
committed
Consistent processing of binding/validation failures for data classes
Includes an extension of SmartValidator for candidate value validation, as well as nullability refinements in Validator and BindingResult. Issue: SPR-16840 Issue: SPR-16841 Issue: SPR-16854
1 parent d8a2927 commit 955665b

File tree

12 files changed

+339
-97
lines changed

12 files changed

+339
-97
lines changed

spring-context/src/main/java/org/springframework/validation/AbstractBindingResult.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public Object getFieldValue(String field) {
222222
if (fieldError != null) {
223223
Object value = fieldError.getRejectedValue();
224224
// Do not apply formatting on binding failures like type mismatches.
225-
return (fieldError.isBindingFailure() ? value : formatFieldValue(field, value));
225+
return (fieldError.isBindingFailure() || getTarget() == null ? value : formatFieldValue(field, value));
226226
}
227227
else if (getTarget() != null) {
228228
Object value = getActualFieldValue(fixedField(field));
@@ -321,9 +321,8 @@ public String[] resolveMessageCodes(String errorCode) {
321321

322322
@Override
323323
public String[] resolveMessageCodes(String errorCode, @Nullable String field) {
324-
Class<?> fieldType = (getTarget() != null ? getFieldType(field) : null);
325324
return getMessageCodesResolver().resolveMessageCodes(
326-
errorCode, getObjectName(), fixedField(field), fieldType);
325+
errorCode, getObjectName(), fixedField(field), getFieldType(field));
327326
}
328327

329328
@Override
@@ -332,7 +331,7 @@ public void addError(ObjectError error) {
332331
}
333332

334333
@Override
335-
public void recordFieldValue(String field, Class<?> type, Object value) {
334+
public void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
336335
this.fieldTypes.put(field, type);
337336
this.fieldValues.put(field, value);
338337
}

spring-context/src/main/java/org/springframework/validation/BindException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ public void addError(ObjectError error) {
275275
}
276276

277277
@Override
278-
public void recordFieldValue(String field, Class<?> type, Object value) {
278+
public void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
279279
this.bindingResult.recordFieldValue(field, type, value);
280280
}
281281

spring-context/src/main/java/org/springframework/validation/BindingResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public interface BindingResult extends Errors {
143143
* @param value the original value
144144
* @since 5.0.4
145145
*/
146-
default void recordFieldValue(String field, Class<?> type, Object value) {
146+
default void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
147147
}
148148

149149
/**

spring-context/src/main/java/org/springframework/validation/DataBinder.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -853,25 +853,34 @@ protected void applyPropertyValues(MutablePropertyValues mpvs) {
853853
* @see #getBindingResult()
854854
*/
855855
public void validate() {
856+
Object target = getTarget();
857+
Assert.state(target != null, "No target to validate");
858+
BindingResult bindingResult = getBindingResult();
859+
// Call each validator with the same binding result
856860
for (Validator validator : this.validators) {
857-
validator.validate(getTarget(), getBindingResult());
861+
validator.validate(target, bindingResult);
858862
}
859863
}
860864

861865
/**
862866
* Invoke the specified Validators, if any, with the given validation hints.
863867
* <p>Note: Validation hints may get ignored by the actual target Validator.
864868
* @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
869+
* @since 3.1
865870
* @see #setValidator(Validator)
866871
* @see SmartValidator#validate(Object, Errors, Object...)
867872
*/
868873
public void validate(Object... validationHints) {
874+
Object target = getTarget();
875+
Assert.state(target != null, "No target to validate");
876+
BindingResult bindingResult = getBindingResult();
877+
// Call each validator with the same binding result
869878
for (Validator validator : getValidators()) {
870879
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
871-
((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
880+
((SmartValidator) validator).validate(target, bindingResult, validationHints);
872881
}
873882
else if (validator != null) {
874-
validator.validate(getTarget(), getBindingResult());
883+
validator.validate(target, bindingResult);
875884
}
876885
}
877886
}

spring-context/src/main/java/org/springframework/validation/SmartValidator.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,11 +39,29 @@ public interface SmartValidator extends Validator {
3939
* <p>Note: Validation hints may get ignored by the actual target {@code Validator},
4040
* in which case this method should behave just like its regular
4141
* {@link #validate(Object, Errors)} sibling.
42-
* @param target the object that is to be validated (can be {@code null})
43-
* @param errors contextual state about the validation process (never {@code null})
42+
* @param target the object that is to be validated
43+
* @param errors contextual state about the validation process
4444
* @param validationHints one or more hint objects to be passed to the validation engine
45-
* @see ValidationUtils
45+
* @see javax.validation.Validator#validate(Object, Class[])
4646
*/
47-
void validate(@Nullable Object target, Errors errors, Object... validationHints);
47+
void validate(Object target, Errors errors, Object... validationHints);
48+
49+
/**
50+
* Validate the supplied value for the specified field on the target type,
51+
* reporting the same validation errors as if the value would be bound to
52+
* the field on an instance of the target class.
53+
* @param targetType the target type
54+
* @param fieldName the name of the field
55+
* @param value the candidate value
56+
* @param errors contextual state about the validation process
57+
* @param validationHints one or more hint objects to be passed to the validation engine
58+
* @since 5.1
59+
* @see javax.validation.Validator#validateValue(Class, String, Object, Class[])
60+
*/
61+
default void validateValue(
62+
Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
63+
64+
throw new IllegalArgumentException("Cannot validate individual value for " + targetType);
65+
}
4866

4967
}

spring-context/src/main/java/org/springframework/validation/ValidationUtils.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,47 +45,47 @@ public abstract class ValidationUtils {
4545
/**
4646
* Invoke the given {@link Validator} for the supplied object and
4747
* {@link Errors} instance.
48-
* @param validator the {@code Validator} to be invoked (must not be {@code null})
49-
* @param obj the object to bind the parameters to
50-
* @param errors the {@link Errors} instance that should store the errors (must not be {@code null})
51-
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors} arguments is
52-
* {@code null}, or if the supplied {@code Validator} does not {@link Validator#supports(Class) support}
53-
* the validation of the supplied object's type
48+
* @param validator the {@code Validator} to be invoked
49+
* @param target the object to bind the parameters to
50+
* @param errors the {@link Errors} instance that should store the errors
51+
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors}
52+
* arguments is {@code null}, or if the supplied {@code Validator} does not
53+
* {@link Validator#supports(Class) support} the validation of the supplied object's type
5454
*/
55-
public static void invokeValidator(Validator validator, Object obj, Errors errors) {
56-
invokeValidator(validator, obj, errors, (Object[]) null);
55+
public static void invokeValidator(Validator validator, Object target, Errors errors) {
56+
invokeValidator(validator, target, errors, (Object[]) null);
5757
}
5858

5959
/**
6060
* Invoke the given {@link Validator}/{@link SmartValidator} for the supplied object and
6161
* {@link Errors} instance.
62-
* @param validator the {@code Validator} to be invoked (must not be {@code null})
63-
* @param obj the object to bind the parameters to
64-
* @param errors the {@link Errors} instance that should store the errors (must not be {@code null})
62+
* @param validator the {@code Validator} to be invoked
63+
* @param target the object to bind the parameters to
64+
* @param errors the {@link Errors} instance that should store the errors
6565
* @param validationHints one or more hint objects to be passed to the validation engine
66-
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors} arguments is
67-
* {@code null}, or if the supplied {@code Validator} does not {@link Validator#supports(Class) support}
68-
* the validation of the supplied object's type
66+
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors}
67+
* arguments is {@code null}, or if the supplied {@code Validator} does not
68+
* {@link Validator#supports(Class) support} the validation of the supplied object's type
6969
*/
7070
public static void invokeValidator(
71-
Validator validator, @Nullable Object obj, Errors errors, @Nullable Object... validationHints) {
71+
Validator validator, Object target, Errors errors, @Nullable Object... validationHints) {
7272

7373
Assert.notNull(validator, "Validator must not be null");
7474
Assert.notNull(errors, "Errors object must not be null");
7575

7676
if (logger.isDebugEnabled()) {
7777
logger.debug("Invoking validator [" + validator + "]");
7878
}
79-
if (obj != null && !validator.supports(obj.getClass())) {
79+
if (!validator.supports(target.getClass())) {
8080
throw new IllegalArgumentException(
81-
"Validator [" + validator.getClass() + "] does not support [" + obj.getClass() + "]");
81+
"Validator [" + validator.getClass() + "] does not support [" + target.getClass() + "]");
8282
}
8383

8484
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
85-
((SmartValidator) validator).validate(obj, errors, validationHints);
85+
((SmartValidator) validator).validate(target, errors, validationHints);
8686
}
8787
else {
88-
validator.validate(obj, errors);
88+
validator.validate(target, errors);
8989
}
9090

9191
if (logger.isDebugEnabled()) {

spring-context/src/main/java/org/springframework/validation/Validator.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.validation;
1818

19-
import org.springframework.lang.Nullable;
20-
2119
/**
2220
* A validator for application-specific objects.
2321
*
@@ -61,6 +59,7 @@
6159
* application.
6260
*
6361
* @author Rod Johnson
62+
* @see SmartValidator
6463
* @see Errors
6564
* @see ValidationUtils
6665
*/
@@ -87,10 +86,10 @@ public interface Validator {
8786
* typically has (or would) return {@code true}.
8887
* <p>The supplied {@link Errors errors} instance can be used to report
8988
* any resulting validation errors.
90-
* @param target the object that is to be validated (can be {@code null})
91-
* @param errors contextual state about the validation process (never {@code null})
89+
* @param target the object that is to be validated
90+
* @param errors contextual state about the validation process
9291
* @see ValidationUtils
9392
*/
94-
void validate(@Nullable Object target, Errors errors);
93+
void validate(Object target, Errors errors);
9594

9695
}

spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@
5050
* while also exposing the original JSR-303 Validator interface itself.
5151
*
5252
* <p>Can be used as a programmatic wrapper. Also serves as base class for
53-
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean}.
53+
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean},
54+
* and as the primary implementation of the {@link SmartValidator} interface.
5455
*
5556
* <p>As of Spring Framework 5.0, this adapter is fully compatible with
5657
* Bean Validation 1.1 as well as 2.0.
5758
*
5859
* @author Juergen Hoeller
5960
* @since 3.0
61+
* @see SmartValidator
62+
* @see CustomValidatorBean
63+
* @see LocalValidatorFactoryBean
6064
*/
6165
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
6266

@@ -99,26 +103,43 @@ public boolean supports(Class<?> clazz) {
99103
}
100104

101105
@Override
102-
public void validate(@Nullable Object target, Errors errors) {
106+
public void validate(Object target, Errors errors) {
103107
if (this.targetValidator != null) {
104108
processConstraintViolations(this.targetValidator.validate(target), errors);
105109
}
106110
}
107111

108112
@Override
109-
public void validate(@Nullable Object target, Errors errors, @Nullable Object... validationHints) {
113+
public void validate(Object target, Errors errors, Object... validationHints) {
110114
if (this.targetValidator != null) {
111-
Set<Class<?>> groups = new LinkedHashSet<>();
112-
if (validationHints != null) {
113-
for (Object hint : validationHints) {
114-
if (hint instanceof Class) {
115-
groups.add((Class<?>) hint);
116-
}
117-
}
118-
}
119115
processConstraintViolations(
120-
this.targetValidator.validate(target, ClassUtils.toClassArray(groups)), errors);
116+
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
117+
}
118+
}
119+
120+
@SuppressWarnings("unchecked")
121+
@Override
122+
public void validateValue(
123+
Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
124+
125+
if (this.targetValidator != null) {
126+
processConstraintViolations(this.targetValidator.validateValue(
127+
(Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
128+
}
129+
}
130+
131+
/**
132+
* Turn the specified validation hints into JSR-303 validation groups.
133+
* @since 5.1
134+
*/
135+
private Class<?>[] asValidationGroups(Object... validationHints) {
136+
Set<Class<?>> groups = new LinkedHashSet<>(4);
137+
for (Object hint : validationHints) {
138+
if (hint instanceof Class) {
139+
groups.add((Class<?>) hint);
140+
}
121141
}
142+
return ClassUtils.toClassArray(groups);
122143
}
123144

124145
/**

spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeBindException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public void addError(ObjectError error) {
261261
}
262262

263263
@Override
264-
public void recordFieldValue(String field, Class<?> type, Object value) {
264+
public void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
265265
this.bindingResult.recordFieldValue(field, type, value);
266266
}
267267

0 commit comments

Comments
 (0)