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
This commit is contained in:
@@ -222,7 +222,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
if (fieldError != null) {
|
||||
Object value = fieldError.getRejectedValue();
|
||||
// Do not apply formatting on binding failures like type mismatches.
|
||||
return (fieldError.isBindingFailure() ? value : formatFieldValue(field, value));
|
||||
return (fieldError.isBindingFailure() || getTarget() == null ? value : formatFieldValue(field, value));
|
||||
}
|
||||
else if (getTarget() != null) {
|
||||
Object value = getActualFieldValue(fixedField(field));
|
||||
@@ -321,9 +321,8 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
|
||||
@Override
|
||||
public String[] resolveMessageCodes(String errorCode, @Nullable String field) {
|
||||
Class<?> fieldType = (getTarget() != null ? getFieldType(field) : null);
|
||||
return getMessageCodesResolver().resolveMessageCodes(
|
||||
errorCode, getObjectName(), fixedField(field), fieldType);
|
||||
errorCode, getObjectName(), fixedField(field), getFieldType(field));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -332,7 +331,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFieldValue(String field, Class<?> type, Object value) {
|
||||
public void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
|
||||
this.fieldTypes.put(field, type);
|
||||
this.fieldValues.put(field, value);
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ public class BindException extends Exception implements BindingResult {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFieldValue(String field, Class<?> type, Object value) {
|
||||
public void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
|
||||
this.bindingResult.recordFieldValue(field, type, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ public interface BindingResult extends Errors {
|
||||
* @param value the original value
|
||||
* @since 5.0.4
|
||||
*/
|
||||
default void recordFieldValue(String field, Class<?> type, Object value) {
|
||||
default void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -853,8 +853,12 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
* @see #getBindingResult()
|
||||
*/
|
||||
public void validate() {
|
||||
Object target = getTarget();
|
||||
Assert.state(target != null, "No target to validate");
|
||||
BindingResult bindingResult = getBindingResult();
|
||||
// Call each validator with the same binding result
|
||||
for (Validator validator : this.validators) {
|
||||
validator.validate(getTarget(), getBindingResult());
|
||||
validator.validate(target, bindingResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,16 +866,21 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
||||
* Invoke the specified Validators, if any, with the given validation hints.
|
||||
* <p>Note: Validation hints may get ignored by the actual target Validator.
|
||||
* @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
|
||||
* @since 3.1
|
||||
* @see #setValidator(Validator)
|
||||
* @see SmartValidator#validate(Object, Errors, Object...)
|
||||
*/
|
||||
public void validate(Object... validationHints) {
|
||||
Object target = getTarget();
|
||||
Assert.state(target != null, "No target to validate");
|
||||
BindingResult bindingResult = getBindingResult();
|
||||
// Call each validator with the same binding result
|
||||
for (Validator validator : getValidators()) {
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
|
||||
((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
|
||||
((SmartValidator) validator).validate(target, bindingResult, validationHints);
|
||||
}
|
||||
else if (validator != null) {
|
||||
validator.validate(getTarget(), getBindingResult());
|
||||
validator.validate(target, bindingResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,11 +39,29 @@ public interface SmartValidator extends Validator {
|
||||
* <p>Note: Validation hints may get ignored by the actual target {@code Validator},
|
||||
* in which case this method should behave just like its regular
|
||||
* {@link #validate(Object, Errors)} sibling.
|
||||
* @param target the object that is to be validated (can be {@code null})
|
||||
* @param errors contextual state about the validation process (never {@code null})
|
||||
* @param target the object that is to be validated
|
||||
* @param errors contextual state about the validation process
|
||||
* @param validationHints one or more hint objects to be passed to the validation engine
|
||||
* @see ValidationUtils
|
||||
* @see javax.validation.Validator#validate(Object, Class[])
|
||||
*/
|
||||
void validate(@Nullable Object target, Errors errors, Object... validationHints);
|
||||
void validate(Object target, Errors errors, Object... validationHints);
|
||||
|
||||
/**
|
||||
* Validate the supplied value for the specified field on the target type,
|
||||
* reporting the same validation errors as if the value would be bound to
|
||||
* the field on an instance of the target class.
|
||||
* @param targetType the target type
|
||||
* @param fieldName the name of the field
|
||||
* @param value the candidate value
|
||||
* @param errors contextual state about the validation process
|
||||
* @param validationHints one or more hint objects to be passed to the validation engine
|
||||
* @since 5.1
|
||||
* @see javax.validation.Validator#validateValue(Class, String, Object, Class[])
|
||||
*/
|
||||
default void validateValue(
|
||||
Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
|
||||
|
||||
throw new IllegalArgumentException("Cannot validate individual value for " + targetType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,30 +45,30 @@ public abstract class ValidationUtils {
|
||||
/**
|
||||
* Invoke the given {@link Validator} for the supplied object and
|
||||
* {@link Errors} instance.
|
||||
* @param validator the {@code Validator} to be invoked (must not be {@code null})
|
||||
* @param obj the object to bind the parameters to
|
||||
* @param errors the {@link Errors} instance that should store the errors (must not be {@code null})
|
||||
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors} arguments is
|
||||
* {@code null}, or if the supplied {@code Validator} does not {@link Validator#supports(Class) support}
|
||||
* the validation of the supplied object's type
|
||||
* @param validator the {@code Validator} to be invoked
|
||||
* @param target the object to bind the parameters to
|
||||
* @param errors the {@link Errors} instance that should store the errors
|
||||
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors}
|
||||
* arguments is {@code null}, or if the supplied {@code Validator} does not
|
||||
* {@link Validator#supports(Class) support} the validation of the supplied object's type
|
||||
*/
|
||||
public static void invokeValidator(Validator validator, Object obj, Errors errors) {
|
||||
invokeValidator(validator, obj, errors, (Object[]) null);
|
||||
public static void invokeValidator(Validator validator, Object target, Errors errors) {
|
||||
invokeValidator(validator, target, errors, (Object[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given {@link Validator}/{@link SmartValidator} for the supplied object and
|
||||
* {@link Errors} instance.
|
||||
* @param validator the {@code Validator} to be invoked (must not be {@code null})
|
||||
* @param obj the object to bind the parameters to
|
||||
* @param errors the {@link Errors} instance that should store the errors (must not be {@code null})
|
||||
* @param validator the {@code Validator} to be invoked
|
||||
* @param target the object to bind the parameters to
|
||||
* @param errors the {@link Errors} instance that should store the errors
|
||||
* @param validationHints one or more hint objects to be passed to the validation engine
|
||||
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors} arguments is
|
||||
* {@code null}, or if the supplied {@code Validator} does not {@link Validator#supports(Class) support}
|
||||
* the validation of the supplied object's type
|
||||
* @throws IllegalArgumentException if either of the {@code Validator} or {@code Errors}
|
||||
* arguments is {@code null}, or if the supplied {@code Validator} does not
|
||||
* {@link Validator#supports(Class) support} the validation of the supplied object's type
|
||||
*/
|
||||
public static void invokeValidator(
|
||||
Validator validator, @Nullable Object obj, Errors errors, @Nullable Object... validationHints) {
|
||||
Validator validator, Object target, Errors errors, @Nullable Object... validationHints) {
|
||||
|
||||
Assert.notNull(validator, "Validator must not be null");
|
||||
Assert.notNull(errors, "Errors object must not be null");
|
||||
@@ -76,16 +76,16 @@ public abstract class ValidationUtils {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invoking validator [" + validator + "]");
|
||||
}
|
||||
if (obj != null && !validator.supports(obj.getClass())) {
|
||||
if (!validator.supports(target.getClass())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Validator [" + validator.getClass() + "] does not support [" + obj.getClass() + "]");
|
||||
"Validator [" + validator.getClass() + "] does not support [" + target.getClass() + "]");
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
|
||||
((SmartValidator) validator).validate(obj, errors, validationHints);
|
||||
((SmartValidator) validator).validate(target, errors, validationHints);
|
||||
}
|
||||
else {
|
||||
validator.validate(obj, errors);
|
||||
validator.validate(target, errors);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.validation;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A validator for application-specific objects.
|
||||
*
|
||||
@@ -61,6 +59,7 @@ import org.springframework.lang.Nullable;
|
||||
* application.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @see SmartValidator
|
||||
* @see Errors
|
||||
* @see ValidationUtils
|
||||
*/
|
||||
@@ -87,10 +86,10 @@ public interface Validator {
|
||||
* typically has (or would) return {@code true}.
|
||||
* <p>The supplied {@link Errors errors} instance can be used to report
|
||||
* any resulting validation errors.
|
||||
* @param target the object that is to be validated (can be {@code null})
|
||||
* @param errors contextual state about the validation process (never {@code null})
|
||||
* @param target the object that is to be validated
|
||||
* @param errors contextual state about the validation process
|
||||
* @see ValidationUtils
|
||||
*/
|
||||
void validate(@Nullable Object target, Errors errors);
|
||||
void validate(Object target, Errors errors);
|
||||
|
||||
}
|
||||
|
||||
@@ -50,13 +50,17 @@ import org.springframework.validation.SmartValidator;
|
||||
* while also exposing the original JSR-303 Validator interface itself.
|
||||
*
|
||||
* <p>Can be used as a programmatic wrapper. Also serves as base class for
|
||||
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean}.
|
||||
* {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean},
|
||||
* and as the primary implementation of the {@link SmartValidator} interface.
|
||||
*
|
||||
* <p>As of Spring Framework 5.0, this adapter is fully compatible with
|
||||
* Bean Validation 1.1 as well as 2.0.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see SmartValidator
|
||||
* @see CustomValidatorBean
|
||||
* @see LocalValidatorFactoryBean
|
||||
*/
|
||||
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
|
||||
|
||||
@@ -99,28 +103,45 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable Object target, Errors errors) {
|
||||
public void validate(Object target, Errors errors) {
|
||||
if (this.targetValidator != null) {
|
||||
processConstraintViolations(this.targetValidator.validate(target), errors);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(@Nullable Object target, Errors errors, @Nullable Object... validationHints) {
|
||||
public void validate(Object target, Errors errors, Object... validationHints) {
|
||||
if (this.targetValidator != null) {
|
||||
Set<Class<?>> groups = new LinkedHashSet<>();
|
||||
if (validationHints != null) {
|
||||
for (Object hint : validationHints) {
|
||||
if (hint instanceof Class) {
|
||||
groups.add((Class<?>) hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
processConstraintViolations(
|
||||
this.targetValidator.validate(target, ClassUtils.toClassArray(groups)), errors);
|
||||
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void validateValue(
|
||||
Class<?> targetType, String fieldName, @Nullable Object value, Errors errors, Object... validationHints) {
|
||||
|
||||
if (this.targetValidator != null) {
|
||||
processConstraintViolations(this.targetValidator.validateValue(
|
||||
(Class) targetType, fieldName, value, asValidationGroups(validationHints)), errors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the specified validation hints into JSR-303 validation groups.
|
||||
* @since 5.1
|
||||
*/
|
||||
private Class<?>[] asValidationGroups(Object... validationHints) {
|
||||
Set<Class<?>> groups = new LinkedHashSet<>(4);
|
||||
for (Object hint : validationHints) {
|
||||
if (hint instanceof Class) {
|
||||
groups.add((Class<?>) hint);
|
||||
}
|
||||
}
|
||||
return ClassUtils.toClassArray(groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given JSR-303 ConstraintViolations, adding corresponding errors to
|
||||
* the provided Spring {@link Errors} object.
|
||||
|
||||
Reference in New Issue
Block a user