/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation;
import java.beans.
PropertyEditor;
import java.lang.reflect.
Field;
import java.util.
ArrayList;
import java.util.
Arrays;
import java.util.
Collections;
import java.util.
HashMap;
import java.util.
List;
import java.util.
Map;
import java.util.
Optional;
import org.apache.commons.logging.
Log;
import org.apache.commons.logging.
LogFactory;
import org.springframework.beans.
ConfigurablePropertyAccessor;
import org.springframework.beans.
MutablePropertyValues;
import org.springframework.beans.
PropertyAccessException;
import org.springframework.beans.
PropertyAccessorUtils;
import org.springframework.beans.
PropertyBatchUpdateException;
import org.springframework.beans.
PropertyEditorRegistry;
import org.springframework.beans.
PropertyValue;
import org.springframework.beans.
PropertyValues;
import org.springframework.beans.
SimpleTypeConverter;
import org.springframework.beans.
TypeConverter;
import org.springframework.beans.
TypeMismatchException;
import org.springframework.core.
MethodParameter;
import org.springframework.core.convert.
ConversionService;
import org.springframework.format.
Formatter;
import org.springframework.format.support.
FormatterPropertyEditorAdapter;
import org.springframework.lang.
UsesJava8;
import org.springframework.util.
Assert;
import org.springframework.util.
ClassUtils;
import org.springframework.util.
ObjectUtils;
import org.springframework.util.
PatternMatchUtils;
import org.springframework.util.
StringUtils;
/**
* Binder that allows for setting property values onto a target object,
* including support for validation and binding result analysis.
* The binding process can be customized through specifying allowed fields,
* required fields, custom editors, etc.
*
* <p>Note that there are potential security implications in failing to set an array
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
* can attempt to subvert an application by supplying values for fields or properties
* that do not exist on the form. In some cases this could lead to illegal data being
* set on command objects <i>or their nested objects</i>. For this reason, it is
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
* on the DataBinder.
*
* <p>The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
* Missing fields and property access exceptions will be converted to {@link FieldError FieldErrors},
* collected in the Errors instance, using the following error codes:
*
* <ul>
* <li>Missing field error: "required"
* <li>Type mismatch error: "typeMismatch"
* <li>Method invocation error: "methodInvocation"
* </ul>
*
* <p>By default, binding errors get resolved through the {@link BindingErrorProcessor}
* strategy, processing for missing fields and property access exceptions: see the
* {@link #setBindingErrorProcessor} method. You can override the default strategy
* if needed, for example to generate different error codes.
*
* <p>Custom validation errors can be added afterwards. You will typically want to resolve
* such error codes into proper user-visible error messages; this can be achieved through
* resolving each error via a {@link org.springframework.context.MessageSource}, which is
* able to resolve an {@link ObjectError}/{@link FieldError} through its
* {@link org.springframework.context.MessageSource#getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)}
* method. The list of message codes can be customized through the {@link MessageCodesResolver}
* strategy: see the {@link #setMessageCodesResolver} method. {@link DefaultMessageCodesResolver}'s
* javadoc states details on the default resolution rules.
*
* <p>This generic data binder can be used in any kind of environment.
* It is typically used by Spring web MVC controllers, via the web-specific
* subclasses {@link org.springframework.web.bind.ServletRequestDataBinder}
* and {@link org.springframework.web.portlet.bind.PortletRequestDataBinder}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
* @see #setMessageCodesResolver
* @see #setBindingErrorProcessor
* @see #bind
* @see #getBindingResult
* @see DefaultMessageCodesResolver
* @see DefaultBindingErrorProcessor
* @see org.springframework.context.MessageSource
* @see org.springframework.web.bind.ServletRequestDataBinder
*/
public class
DataBinder implements
PropertyEditorRegistry,
TypeConverter {
/** Default object name used for binding: "target" */
public static final
String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256 */
public static final int
DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
/**
* We'll create a lot of DataBinder instances: Let's use a static logger.
*/
protected static final
Log logger =
LogFactory.
getLog(
DataBinder.class);
private static
Class<?>
javaUtilOptionalClass = null;
static {
try {
javaUtilOptionalClass =
ClassUtils.
forName("java.util.Optional",
DataBinder.class.
getClassLoader());
}
catch (
ClassNotFoundException ex) {
// Java 8 not available - Optional references simply not supported then.
}
}
private final
Object target;
private final
String objectName;
private
AbstractPropertyBindingResult bindingResult;
private
SimpleTypeConverter typeConverter;
private boolean
ignoreUnknownFields = true;
private boolean
ignoreInvalidFields = false;
private boolean
autoGrowNestedPaths = true;
private int
autoGrowCollectionLimit =
DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
private
String[]
allowedFields;
private
String[]
disallowedFields;
private
String[]
requiredFields;
private
ConversionService conversionService;
private
MessageCodesResolver messageCodesResolver;
private
BindingErrorProcessor bindingErrorProcessor = new
DefaultBindingErrorProcessor();
private final
List<
Validator>
validators = new
ArrayList<
Validator>();
/**
* Create a new DataBinder instance, with default object name.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @see #DEFAULT_OBJECT_NAME
*/
public
DataBinder(
Object target) {
this(
target,
DEFAULT_OBJECT_NAME);
}
/**
* Create a new DataBinder instance.
* @param target the target object to bind onto (or {@code null}
* if the binder is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public
DataBinder(
Object target,
String objectName) {
if (
target != null &&
target.
getClass() ==
javaUtilOptionalClass) {
this.
target =
OptionalUnwrapper.
unwrap(
target);
}
else {
this.
target =
target;
}
this.
objectName =
objectName;
}
/**
* Return the wrapped target object.
*/
public
Object getTarget() {
return this.
target;
}
/**
* Return the name of the bound object.
*/
public
String getObjectName() {
return this.
objectName;
}
/**
* Set whether this binder should attempt to "auto-grow" a nested path that contains a null value.
* <p>If "true", a null path location will be populated with a default object value and traversed
* instead of resulting in an exception. This flag also enables auto-growth of collection elements
* when accessing an out-of-bounds index.
* <p>Default is "true" on a standard DataBinder. Note that since Spring 4.1 this feature is supported
* for bean property access (DataBinder's default mode) and field access.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowNestedPaths
*/
public void
setAutoGrowNestedPaths(boolean
autoGrowNestedPaths) {
Assert.
state(this.
bindingResult == null,
"DataBinder is already initialized - call setAutoGrowNestedPaths before other configuration methods");
this.
autoGrowNestedPaths =
autoGrowNestedPaths;
}
/**
* Return whether "auto-growing" of nested paths has been activated.
*/
public boolean
isAutoGrowNestedPaths() {
return this.
autoGrowNestedPaths;
}
/**
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
* @see #initBeanPropertyAccess()
* @see org.springframework.beans.BeanWrapper#setAutoGrowCollectionLimit
*/
public void
setAutoGrowCollectionLimit(int
autoGrowCollectionLimit) {
Assert.
state(this.
bindingResult == null,
"DataBinder is already initialized - call setAutoGrowCollectionLimit before other configuration methods");
this.
autoGrowCollectionLimit =
autoGrowCollectionLimit;
}
/**
* Return the current limit for array and collection auto-growing.
*/
public int
getAutoGrowCollectionLimit() {
return this.
autoGrowCollectionLimit;
}
/**
* Initialize standard JavaBean property access for this DataBinder.
* <p>This is the default; an explicit call just leads to eager initialization.
* @see #initDirectFieldAccess()
* @see #createBeanPropertyBindingResult()
*/
public void
initBeanPropertyAccess() {
Assert.
state(this.
bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
this.
bindingResult =
createBeanPropertyBindingResult();
}
/**
* Create the {@link AbstractPropertyBindingResult} instance using standard
* JavaBean property access.
* @since 4.2.1
*/
protected
AbstractPropertyBindingResult createBeanPropertyBindingResult() {
BeanPropertyBindingResult result = new
BeanPropertyBindingResult(
getTarget(),
getObjectName(),
isAutoGrowNestedPaths(),
getAutoGrowCollectionLimit());
if (this.
conversionService != null) {
result.
initConversion(this.
conversionService);
}
if (this.
messageCodesResolver != null) {
result.
setMessageCodesResolver(this.
messageCodesResolver);
}
return
result;
}
/**
* Initialize direct field access for this DataBinder,
* as alternative to the default bean property access.
* @see #initBeanPropertyAccess()
* @see #createDirectFieldBindingResult()
*/
public void
initDirectFieldAccess() {
Assert.
state(this.
bindingResult == null,
"DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");
this.
bindingResult =
createDirectFieldBindingResult();
}
/**
* Create the {@link AbstractPropertyBindingResult} instance using direct
* field access.
* @since 4.2.1
*/
protected
AbstractPropertyBindingResult createDirectFieldBindingResult() {
DirectFieldBindingResult result = new
DirectFieldBindingResult(
getTarget(),
getObjectName(),
isAutoGrowNestedPaths());
if (this.
conversionService != null) {
result.
initConversion(this.
conversionService);
}
if (this.
messageCodesResolver != null) {
result.
setMessageCodesResolver(this.
messageCodesResolver);
}
return
result;
}
/**
* Return the internal BindingResult held by this DataBinder,
* as an AbstractPropertyBindingResult.
*/
protected
AbstractPropertyBindingResult getInternalBindingResult() {
if (this.
bindingResult == null) {
initBeanPropertyAccess();
}
return this.
bindingResult;
}
/**
* Return the underlying PropertyAccessor of this binder's BindingResult.
*/
protected
ConfigurablePropertyAccessor getPropertyAccessor() {
return
getInternalBindingResult().
getPropertyAccessor();
}
/**
* Return this binder's underlying SimpleTypeConverter.
*/
protected
SimpleTypeConverter getSimpleTypeConverter() {
if (this.
typeConverter == null) {
this.
typeConverter = new
SimpleTypeConverter();
if (this.
conversionService != null) {
this.
typeConverter.
setConversionService(this.
conversionService);
}
}
return this.
typeConverter;
}
/**
* Return the underlying TypeConverter of this binder's BindingResult.
*/
protected
PropertyEditorRegistry getPropertyEditorRegistry() {
if (
getTarget() != null) {
return
getInternalBindingResult().
getPropertyAccessor();
}
else {
return
getSimpleTypeConverter();
}
}
/**
* Return the underlying TypeConverter of this binder's BindingResult.
*/
protected
TypeConverter getTypeConverter() {
if (
getTarget() != null) {
return
getInternalBindingResult().
getPropertyAccessor();
}
else {
return
getSimpleTypeConverter();
}
}
/**
* Return the BindingResult instance created by this DataBinder.
* This allows for convenient access to the binding results after
* a bind operation.
* @return the BindingResult instance, to be treated as BindingResult
* or as Errors instance (Errors is a super-interface of BindingResult)
* @see Errors
* @see #bind
*/
public
BindingResult getBindingResult() {
return
getInternalBindingResult();
}
/**
* Set whether to ignore unknown fields, that is, whether to ignore bind
* parameters that do not have corresponding fields in the target object.
* <p>Default is "true". Turn this off to enforce that all bind parameters
* must have a matching field in the target object.
* <p>Note that this setting only applies to <i>binding</i> operations
* on this DataBinder, not to <i>retrieving</i> values via its
* {@link #getBindingResult() BindingResult}.
* @see #bind
*/
public void
setIgnoreUnknownFields(boolean
ignoreUnknownFields) {
this.
ignoreUnknownFields =
ignoreUnknownFields;
}
/**
* Return whether to ignore unknown fields when binding.
*/
public boolean
isIgnoreUnknownFields() {
return this.
ignoreUnknownFields;
}
/**
* Set whether to ignore invalid fields, that is, whether to ignore bind
* parameters that have corresponding fields in the target object which are
* not accessible (for example because of null values in the nested path).
* <p>Default is "false". Turn this on to ignore bind parameters for
* nested objects in non-existing parts of the target object graph.
* <p>Note that this setting only applies to <i>binding</i> operations
* on this DataBinder, not to <i>retrieving</i> values via its
* {@link #getBindingResult() BindingResult}.
* @see #bind
*/
public void
setIgnoreInvalidFields(boolean
ignoreInvalidFields) {
this.
ignoreInvalidFields =
ignoreInvalidFields;
}
/**
* Return whether to ignore invalid fields when binding.
*/
public boolean
isIgnoreInvalidFields() {
return this.
ignoreInvalidFields;
}
/**
* Register fields that should be allowed for binding. Default is all
* fields. Restrict this for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
* @param allowedFields array of field names
* @see #setDisallowedFields
* @see #isAllowed(String)
* @see org.springframework.web.bind.ServletRequestDataBinder
*/
public void
setAllowedFields(
String...
allowedFields) {
this.
allowedFields =
PropertyAccessorUtils.
canonicalPropertyNames(
allowedFields);
}
/**
* Return the fields that should be allowed for binding.
* @return array of field names
*/
public
String[]
getAllowedFields() {
return this.
allowedFields;
}
/**
* Register fields that should <i>not</i> be allowed for binding. Default is none.
* Mark fields as disallowed for example to avoid unwanted modifications
* by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> fields.
* @param disallowedFields array of field names
* @see #setAllowedFields
* @see #isAllowed(String)
* @see org.springframework.web.bind.ServletRequestDataBinder
*/
public void
setDisallowedFields(
String...
disallowedFields) {
this.
disallowedFields =
PropertyAccessorUtils.
canonicalPropertyNames(
disallowedFields);
}
/**
* Return the fields that should <i>not</i> be allowed for binding.
* @return array of field names
*/
public
String[]
getDisallowedFields() {
return this.
disallowedFields;
}
/**
* Register fields that are required for each binding process.
* <p>If one of the specified fields is not contained in the list of
* incoming property values, a corresponding "missing field" error
* will be created, with error code "required" (by the default
* binding error processor).
* @param requiredFields array of field names
* @see #setBindingErrorProcessor
* @see DefaultBindingErrorProcessor#MISSING_FIELD_ERROR_CODE
*/
public void
setRequiredFields(
String...
requiredFields) {
this.
requiredFields =
PropertyAccessorUtils.
canonicalPropertyNames(
requiredFields);
if (
logger.
isDebugEnabled()) {
logger.
debug("DataBinder requires binding of required fields [" +
StringUtils.
arrayToCommaDelimitedString(
requiredFields) + "]");
}
}
/**
* Return the fields that are required for each binding process.
* @return array of field names
*/
public
String[]
getRequiredFields() {
return this.
requiredFields;
}
/**
* Set whether to extract the old field value when applying a
* property editor to a new value for a field.
* <p>Default is "true", exposing previous field values to custom editors.
* Turn this to "false" to avoid side effects caused by getters.
* @deprecated as of Spring 4.3.5, in favor of customizing this in
* {@link #createBeanPropertyBindingResult()} or
* {@link #createDirectFieldBindingResult()} itself
*/
@
Deprecated
public void
setExtractOldValueForEditor(boolean
extractOldValueForEditor) {
getPropertyAccessor().
setExtractOldValueForEditor(
extractOldValueForEditor);
}
/**
* Set the strategy to use for resolving errors into message codes.
* Applies the given strategy to the underlying errors holder.
* <p>Default is a DefaultMessageCodesResolver.
* @see BeanPropertyBindingResult#setMessageCodesResolver
* @see DefaultMessageCodesResolver
*/
public void
setMessageCodesResolver(
MessageCodesResolver messageCodesResolver) {
Assert.
state(this.
messageCodesResolver == null, "DataBinder is already initialized with MessageCodesResolver");
this.
messageCodesResolver =
messageCodesResolver;
if (this.
bindingResult != null &&
messageCodesResolver != null) {
this.
bindingResult.
setMessageCodesResolver(
messageCodesResolver);
}
}
/**
* Set the strategy to use for processing binding errors, that is,
* required field errors and {@code PropertyAccessException}s.
* <p>Default is a DefaultBindingErrorProcessor.
* @see DefaultBindingErrorProcessor
*/
public void
setBindingErrorProcessor(
BindingErrorProcessor bindingErrorProcessor) {
Assert.
notNull(
bindingErrorProcessor, "BindingErrorProcessor must not be null");
this.
bindingErrorProcessor =
bindingErrorProcessor;
}
/**
* Return the strategy for processing binding errors.
*/
public
BindingErrorProcessor getBindingErrorProcessor() {
return this.
bindingErrorProcessor;
}
/**
* Set the Validator to apply after each binding step.
* @see #addValidators(Validator...)
* @see #replaceValidators(Validator...)
*/
public void
setValidator(
Validator validator) {
assertValidators(
validator);
this.
validators.
clear();
this.
validators.
add(
validator);
}
private void
assertValidators(
Validator...
validators) {
Assert.
notNull(
validators, "Validators required");
Object target =
getTarget();
for (
Validator validator :
validators) {
if (
validator != null && (
target != null && !
validator.
supports(
target.
getClass()))) {
throw new
IllegalStateException("Invalid target for Validator [" +
validator + "]: " +
target);
}
}
}
/**
* Add Validators to apply after each binding step.
* @see #setValidator(Validator)
* @see #replaceValidators(Validator...)
*/
public void
addValidators(
Validator...
validators) {
assertValidators(
validators);
this.
validators.
addAll(
Arrays.
asList(
validators));
}
/**
* Replace the Validators to apply after each binding step.
* @see #setValidator(Validator)
* @see #addValidators(Validator...)
*/
public void
replaceValidators(
Validator...
validators) {
assertValidators(
validators);
this.
validators.
clear();
this.
validators.
addAll(
Arrays.
asList(
validators));
}
/**
* Return the primary Validator to apply after each binding step, if any.
*/
public
Validator getValidator() {
return (this.
validators.
size() > 0 ? this.
validators.
get(0) : null);
}
/**
* Return the Validators to apply after data binding.
*/
public
List<
Validator>
getValidators() {
return
Collections.
unmodifiableList(this.
validators);
}
//---------------------------------------------------------------------
// Implementation of PropertyEditorRegistry/TypeConverter interface
//---------------------------------------------------------------------
/**
* Specify a Spring 3.0 ConversionService to use for converting
* property values, as an alternative to JavaBeans PropertyEditors.
*/
public void
setConversionService(
ConversionService conversionService) {
Assert.
state(this.
conversionService == null, "DataBinder is already initialized with ConversionService");
this.
conversionService =
conversionService;
if (this.
bindingResult != null &&
conversionService != null) {
this.
bindingResult.
initConversion(
conversionService);
}
}
/**
* Return the associated ConversionService, if any.
*/
public
ConversionService getConversionService() {
return this.
conversionService;
}
/**
* Add a custom formatter, applying it to all fields matching the
* {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void
addCustomFormatter(
Formatter<?>
formatter) {
FormatterPropertyEditorAdapter adapter = new
FormatterPropertyEditorAdapter(
formatter);
getPropertyEditorRegistry().
registerCustomEditor(
adapter.
getFieldType(),
adapter);
}
/**
* Add a custom formatter for the field type specified in {@link Formatter} class,
* applying it to the specified fields only, if any, or otherwise to all fields.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add, generically declared for a specific type
* @param fields the fields to apply the formatter to, or none if to be applied to all
* @since 4.2
* @see #registerCustomEditor(Class, String, PropertyEditor)
*/
public void
addCustomFormatter(
Formatter<?>
formatter,
String...
fields) {
FormatterPropertyEditorAdapter adapter = new
FormatterPropertyEditorAdapter(
formatter);
Class<?>
fieldType =
adapter.
getFieldType();
if (
ObjectUtils.
isEmpty(
fields)) {
getPropertyEditorRegistry().
registerCustomEditor(
fieldType,
adapter);
}
else {
for (
String field :
fields) {
getPropertyEditorRegistry().
registerCustomEditor(
fieldType,
field,
adapter);
}
}
}
/**
* Add a custom formatter, applying it to the specified field types only, if any,
* or otherwise to all fields matching the {@link Formatter}-declared type.
* <p>Registers a corresponding {@link PropertyEditor} adapter underneath the covers.
* @param formatter the formatter to add (does not need to generically declare a
* field type if field types are explicitly specified as parameters)
* @param fieldTypes the field types to apply the formatter to, or none if to be
* derived from the given {@link Formatter} implementation class
* @since 4.2
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void
addCustomFormatter(
Formatter<?>
formatter,
Class<?>...
fieldTypes) {
FormatterPropertyEditorAdapter adapter = new
FormatterPropertyEditorAdapter(
formatter);
if (
ObjectUtils.
isEmpty(
fieldTypes)) {
getPropertyEditorRegistry().
registerCustomEditor(
adapter.
getFieldType(),
adapter);
}
else {
for (
Class<?>
fieldType :
fieldTypes) {
getPropertyEditorRegistry().
registerCustomEditor(
fieldType,
adapter);
}
}
}
@
Override
public void
registerCustomEditor(
Class<?>
requiredType,
PropertyEditor propertyEditor) {
getPropertyEditorRegistry().
registerCustomEditor(
requiredType,
propertyEditor);
}
@
Override
public void
registerCustomEditor(
Class<?>
requiredType,
String field,
PropertyEditor propertyEditor) {
getPropertyEditorRegistry().
registerCustomEditor(
requiredType,
field,
propertyEditor);
}
@
Override
public
PropertyEditor findCustomEditor(
Class<?>
requiredType,
String propertyPath) {
return
getPropertyEditorRegistry().
findCustomEditor(
requiredType,
propertyPath);
}
@
Override
public <T> T
convertIfNecessary(
Object value,
Class<T>
requiredType) throws
TypeMismatchException {
return
getTypeConverter().
convertIfNecessary(
value,
requiredType);
}
@
Override
public <T> T
convertIfNecessary(
Object value,
Class<T>
requiredType,
MethodParameter methodParam)
throws
TypeMismatchException {
return
getTypeConverter().
convertIfNecessary(
value,
requiredType,
methodParam);
}
@
Override
public <T> T
convertIfNecessary(
Object value,
Class<T>
requiredType,
Field field)
throws
TypeMismatchException {
return
getTypeConverter().
convertIfNecessary(
value,
requiredType,
field);
}
/**
* Bind the given property values to this binder's target.
* <p>This call can create field errors, representing basic binding
* errors like a required field (code "required"), or type mismatch
* between value and bean property (code "typeMismatch").
* <p>Note that the given PropertyValues should be a throwaway instance:
* For efficiency, it will be modified to just contain allowed fields if it
* implements the MutablePropertyValues interface; else, an internal mutable
* copy will be created for this purpose. Pass in a copy of the PropertyValues
* if you want your original instance to stay unmodified in any case.
* @param pvs property values to bind
* @see #doBind(org.springframework.beans.MutablePropertyValues)
*/
public void
bind(
PropertyValues pvs) {
MutablePropertyValues mpvs = (
pvs instanceof
MutablePropertyValues) ?
(
MutablePropertyValues)
pvs : new
MutablePropertyValues(
pvs);
doBind(
mpvs);
}
/**
* Actual implementation of the binding process, working with the
* passed-in MutablePropertyValues instance.
* @param mpvs the property values to bind,
* as MutablePropertyValues instance
* @see #checkAllowedFields
* @see #checkRequiredFields
* @see #applyPropertyValues
*/
protected void
doBind(
MutablePropertyValues mpvs) {
checkAllowedFields(
mpvs);
checkRequiredFields(
mpvs);
applyPropertyValues(
mpvs);
}
/**
* Check the given property values against the allowed fields,
* removing values for fields that are not allowed.
* @param mpvs the property values to be bound (can be modified)
* @see #getAllowedFields
* @see #isAllowed(String)
*/
protected void
checkAllowedFields(
MutablePropertyValues mpvs) {
PropertyValue[]
pvs =
mpvs.
getPropertyValues();
for (
PropertyValue pv :
pvs) {
String field =
PropertyAccessorUtils.
canonicalPropertyName(
pv.
getName());
if (!
isAllowed(
field)) {
mpvs.
removePropertyValue(
pv);
getBindingResult().
recordSuppressedField(
field);
if (
logger.
isDebugEnabled()) {
logger.
debug("Field [" +
field + "] has been removed from PropertyValues " +
"and will not be bound, because it has not been found in the list of allowed fields");
}
}
}
}
/**
* Return if the given field is allowed for binding.
* Invoked for each passed-in property value.
* <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
* as well as direct equality, in the specified lists of allowed fields and
* disallowed fields. A field matching a disallowed pattern will not be accepted
* even if it also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses.
* @param field the field to check
* @return if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
*/
protected boolean
isAllowed(
String field) {
String[]
allowed =
getAllowedFields();
String[]
disallowed =
getDisallowedFields();
return ((
ObjectUtils.
isEmpty(
allowed) ||
PatternMatchUtils.
simpleMatch(
allowed,
field)) &&
(
ObjectUtils.
isEmpty(
disallowed) || !
PatternMatchUtils.
simpleMatch(
disallowed,
field)));
}
/**
* Check the given property values against the required fields,
* generating missing field errors where appropriate.
* @param mpvs the property values to be bound (can be modified)
* @see #getRequiredFields
* @see #getBindingErrorProcessor
* @see BindingErrorProcessor#processMissingFieldError
*/
protected void
checkRequiredFields(
MutablePropertyValues mpvs) {
String[]
requiredFields =
getRequiredFields();
if (!
ObjectUtils.
isEmpty(
requiredFields)) {
Map<
String,
PropertyValue>
propertyValues = new
HashMap<
String,
PropertyValue>();
PropertyValue[]
pvs =
mpvs.
getPropertyValues();
for (
PropertyValue pv :
pvs) {
String canonicalName =
PropertyAccessorUtils.
canonicalPropertyName(
pv.
getName());
propertyValues.
put(
canonicalName,
pv);
}
for (
String field :
requiredFields) {
PropertyValue pv =
propertyValues.
get(
field);
boolean
empty = (
pv == null ||
pv.
getValue() == null);
if (!
empty) {
if (
pv.
getValue() instanceof
String) {
empty = !
StringUtils.
hasText((
String)
pv.
getValue());
}
else if (
pv.
getValue() instanceof
String[]) {
String[]
values = (
String[])
pv.
getValue();
empty = (
values.length == 0 || !
StringUtils.
hasText(
values[0]));
}
}
if (
empty) {
// Use bind error processor to create FieldError.
getBindingErrorProcessor().
processMissingFieldError(
field,
getInternalBindingResult());
// Remove property from property values to bind:
// It has already caused a field error with a rejected value.
if (
pv != null) {
mpvs.
removePropertyValue(
pv);
propertyValues.
remove(
field);
}
}
}
}
}
/**
* Apply given property values to the target object.
* <p>Default implementation applies all of the supplied property
* values as bean property values. By default, unknown fields will
* be ignored.
* @param mpvs the property values to be bound (can be modified)
* @see #getTarget
* @see #getPropertyAccessor
* @see #isIgnoreUnknownFields
* @see #getBindingErrorProcessor
* @see BindingErrorProcessor#processPropertyAccessException
*/
protected void
applyPropertyValues(
MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().
setPropertyValues(
mpvs,
isIgnoreUnknownFields(),
isIgnoreInvalidFields());
}
catch (
PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (
PropertyAccessException pae :
ex.
getPropertyAccessExceptions()) {
getBindingErrorProcessor().
processPropertyAccessException(
pae,
getInternalBindingResult());
}
}
}
/**
* Invoke the specified Validators, if any.
* @see #setValidator(Validator)
* @see #getBindingResult()
*/
public void
validate() {
for (
Validator validator : this.
validators) {
validator.
validate(
getTarget(),
getBindingResult());
}
}
/**
* 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}
* @see #setValidator(Validator)
* @see SmartValidator#validate(Object, Errors, Object...)
*/
public void
validate(
Object...
validationHints) {
for (
Validator validator :
getValidators()) {
if (!
ObjectUtils.
isEmpty(
validationHints) &&
validator instanceof
SmartValidator) {
((
SmartValidator)
validator).
validate(
getTarget(),
getBindingResult(),
validationHints);
}
else if (
validator != null) {
validator.
validate(
getTarget(),
getBindingResult());
}
}
}
/**
* Close this DataBinder, which may result in throwing
* a BindException if it encountered any errors.
* @return the model Map, containing target object and Errors instance
* @throws BindException if there were any errors in the bind operation
* @see BindingResult#getModel()
*/
public
Map<?, ?>
close() throws
BindException {
if (
getBindingResult().
hasErrors()) {
throw new
BindException(
getBindingResult());
}
return
getBindingResult().
getModel();
}
/**
* Inner class to avoid a hard dependency on Java 8.
*/
@
UsesJava8
private static class
OptionalUnwrapper {
public static
Object unwrap(
Object optionalObject) {
Optional<?>
optional = (
Optional<?>)
optionalObject;
if (!
optional.
isPresent()) {
return null;
}
Object result =
optional.
get();
Assert.
isTrue(!(
result instanceof
Optional), "Multi-level Optional usage not supported");
return
result;
}
}
}