/*
 * Copyright 2002-2019 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 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.core.convert.TypeDescriptor;
import org.springframework.format.Formatter;
import org.springframework.format.support.FormatterPropertyEditorAdapter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
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.

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 or their nested objects. For this reason, it is highly recommended to specify the allowedFields property on the DataBinder.

The binding results can be examined via the BindingResult interface, extending the Errors interface: see the getBindingResult() method. Missing fields and property access exceptions will be converted to FieldErrors, collected in the Errors instance, using the following error codes:

  • Missing field error: "required"
  • Type mismatch error: "typeMismatch"
  • Method invocation error: "methodInvocation"

By default, binding errors get resolved through the BindingErrorProcessor strategy, processing for missing fields and property access exceptions: see the setBindingErrorProcessor method. You can override the default strategy if needed, for example to generate different error codes.

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 MessageSource, which is able to resolve an ObjectError/FieldError through its MessageSource.getMessage(MessageSourceResolvable, Locale) method. The list of message codes can be customized through the MessageCodesResolver strategy: see the setMessageCodesResolver method. DefaultMessageCodesResolver's javadoc states details on the default resolution rules.

This generic data binder can be used in any kind of environment.

Author:Rod Johnson, Juergen Hoeller, Rob Harrop, Stephane Nicoll, Kazuki Shimizu
See Also:
/** * 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. * * @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 */
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Default object name used for binding: "target".
/** Default object name used for binding: "target". */
public static final String DEFAULT_OBJECT_NAME = "target";
Default limit for array and collection growing: 256.
/** 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.
/** * We'll create a lot of DataBinder instances: Let's use a static logger. */
protected static final Log logger = LogFactory.getLog(DataBinder.class); @Nullable private final Object target; private final String objectName; @Nullable private AbstractPropertyBindingResult bindingResult; @Nullable private SimpleTypeConverter typeConverter; private boolean ignoreUnknownFields = true; private boolean ignoreInvalidFields = false; private boolean autoGrowNestedPaths = true; private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT; @Nullable private String[] allowedFields; @Nullable private String[] disallowedFields; @Nullable private String[] requiredFields; @Nullable private ConversionService conversionService; @Nullable private MessageCodesResolver messageCodesResolver; private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor(); private final List<Validator> validators = new ArrayList<>();
Create a new DataBinder instance, with default object name.
Params:
  • target – the target object to bind onto (or null if the binder is just used to convert a plain parameter value)
See Also:
/** * 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(@Nullable Object target) { this(target, DEFAULT_OBJECT_NAME); }
Create a new DataBinder instance.
Params:
  • target – the target object to bind onto (or null if the binder is just used to convert a plain parameter value)
  • objectName – the name of the target object
/** * 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(@Nullable Object target, String objectName) { this.target = ObjectUtils.unwrapOptional(target); this.objectName = objectName; }
Return the wrapped target object.
/** * Return the wrapped target object. */
@Nullable public Object getTarget() { return this.target; }
Return the name of the bound object.
/** * 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.

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.

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 Also:
/** * 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.
/** * 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.

Default is 256, preventing OutOfMemoryErrors in case of large indexes. Raise this limit if your auto-growing needs are unusually high.

See Also:
/** * 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.
/** * Return the current limit for array and collection auto-growing. */
public int getAutoGrowCollectionLimit() { return this.autoGrowCollectionLimit; }
Initialize standard JavaBean property access for this DataBinder.

This is the default; an explicit call just leads to eager initialization.

See Also:
/** * 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 AbstractPropertyBindingResult instance using standard JavaBean property access.
Since:4.2.1
/** * 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 Also:
/** * 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 AbstractPropertyBindingResult instance using direct field access.
Since:4.2.1
/** * 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.
/** * 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.
/** * Return the underlying PropertyAccessor of this binder's BindingResult. */
protected ConfigurablePropertyAccessor getPropertyAccessor() { return getInternalBindingResult().getPropertyAccessor(); }
Return this binder's underlying SimpleTypeConverter.
/** * 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.
/** * 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.
/** * 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.
See Also:
Returns:the BindingResult instance, to be treated as BindingResult or as Errors instance (Errors is a super-interface of BindingResult)
/** * 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.

Default is "true". Turn this off to enforce that all bind parameters must have a matching field in the target object.

Note that this setting only applies to binding operations on this DataBinder, not to retrieving values via its BindingResult.

See Also:
/** * 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.
/** * 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).

Default is "false". Turn this on to ignore bind parameters for nested objects in non-existing parts of the target object graph.

Note that this setting only applies to binding operations on this DataBinder, not to retrieving values via its BindingResult.

See Also:
/** * 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.
/** * 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.

Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching can be implemented by overriding the isAllowed method.

Alternatively, specify a list of disallowed fields.

Params:
  • allowedFields – array of field names
See Also:
/** * 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) */
public void setAllowedFields(@Nullable String... allowedFields) { this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields); }
Return the fields that should be allowed for binding.
Returns:array of field names
/** * Return the fields that should be allowed for binding. * @return array of field names */
@Nullable public String[] getAllowedFields() { return this.allowedFields; }
Register fields that should not 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.

Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching can be implemented by overriding the isAllowed method.

Alternatively, specify a list of allowed fields.

Params:
  • disallowedFields – array of field names
See Also:
/** * 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) */
public void setDisallowedFields(@Nullable String... disallowedFields) { this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields); }
Return the fields that should not be allowed for binding.
Returns:array of field names
/** * Return the fields that should <i>not</i> be allowed for binding. * @return array of field names */
@Nullable public String[] getDisallowedFields() { return this.disallowedFields; }
Register fields that are required for each binding process.

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).

Params:
  • requiredFields – array of field names
See Also:
/** * 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(@Nullable 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.
Returns:array of field names
/** * Return the fields that are required for each binding process. * @return array of field names */
@Nullable public String[] getRequiredFields() { return this.requiredFields; }
Set the strategy to use for resolving errors into message codes. Applies the given strategy to the underlying errors holder.

Default is a DefaultMessageCodesResolver.

See Also:
/** * 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(@Nullable 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 PropertyAccessExceptions.

Default is a DefaultBindingErrorProcessor.

See Also:
/** * 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.
/** * Return the strategy for processing binding errors. */
public BindingErrorProcessor getBindingErrorProcessor() { return this.bindingErrorProcessor; }
Set the Validator to apply after each binding step.
See Also:
/** * Set the Validator to apply after each binding step. * @see #addValidators(Validator...) * @see #replaceValidators(Validator...) */
public void setValidator(@Nullable Validator validator) { assertValidators(validator); this.validators.clear(); if (validator != null) { this.validators.add(validator); } } private void assertValidators(Validator... validators) { 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 Also:
/** * 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 Also:
/** * 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.
/** * Return the primary Validator to apply after each binding step, if any. */
@Nullable public Validator getValidator() { return (!this.validators.isEmpty() ? this.validators.get(0) : null); }
Return the Validators to apply after data binding.
/** * 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.
/** * Specify a Spring 3.0 ConversionService to use for converting * property values, as an alternative to JavaBeans PropertyEditors. */
public void setConversionService(@Nullable 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.
/** * Return the associated ConversionService, if any. */
@Nullable public ConversionService getConversionService() { return this.conversionService; }
Add a custom formatter, applying it to all fields matching the Formatter-declared type.

Registers a corresponding PropertyEditor adapter underneath the covers.

Params:
  • formatter – the formatter to add, generically declared for a specific type
See Also:
Since:4.2
/** * 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 Formatter class, applying it to the specified fields only, if any, or otherwise to all fields.

Registers a corresponding PropertyEditor adapter underneath the covers.

Params:
  • formatter – the formatter to add, generically declared for a specific type
  • fields – the fields to apply the formatter to, or none if to be applied to all
See Also:
Since:4.2
/** * 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 Formatter-declared type.

Registers a corresponding PropertyEditor adapter underneath the covers.

Params:
  • formatter – the formatter to add (does not need to generically declare a field type if field types are explicitly specified as parameters)
  • fieldTypes – the field types to apply the formatter to, or none if to be derived from the given Formatter implementation class
See Also:
Since:4.2
/** * 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(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor) { getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor); } @Override @Nullable public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) { return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath); } @Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType); } @Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); } @Override @Nullable public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, field); } @Nullable @Override public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor); }
Bind the given property values to this binder's target.

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").

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.

Params:
  • pvs – property values to bind
See Also:
/** * 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.
Params:
  • mpvs – the property values to bind, as MutablePropertyValues instance
See Also:
/** * 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.
Params:
  • mpvs – the property values to be bound (can be modified)
See Also:
/** * 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.

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.

Can be overridden in subclasses.

Params:
  • field – the field to check
See Also:
Returns:if the field is allowed
/** * 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.
Params:
  • mpvs – the property values to be bound (can be modified)
See Also:
/** * 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<>(); 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.

Default implementation applies all of the supplied property values as bean property values. By default, unknown fields will be ignored.

Params:
  • mpvs – the property values to be bound (can be modified)
See Also:
/** * 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 Also:
/** * Invoke the specified Validators, if any. * @see #setValidator(Validator) * @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 : getValidators()) { validator.validate(target, bindingResult); } }
Invoke the specified Validators, if any, with the given validation hints.

Note: Validation hints may get ignored by the actual target Validator.

Params:
  • validationHints – one or more hint objects to be passed to a SmartValidator
See Also:
Since:3.1
/** * 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(target, bindingResult, validationHints); } else if (validator != null) { validator.validate(target, bindingResult); } } }
Close this DataBinder, which may result in throwing a BindException if it encountered any errors.
Throws:
See Also:
Returns:the model Map, containing target object and Errors instance
/** * 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(); } }