/* * Copyright 2002-2016 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. * *
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 {@link #setAllowedFields allowedFields} property * on the DataBinder. * *
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: * *
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. * *
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. * *
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 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 #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.
* 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.
* 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.
* 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
* {@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).
* 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
* {@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.
* Supports "xxx*", "*xxx" and "*xxx*" patterns. More sophisticated matching
* can be implemented by overriding the {@code isAllowed} method.
* Alternatively, specify a list of disallowed 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 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 {@code isAllowed} method.
* Alternatively, specify a list of allowed 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 not be allowed for binding.
* @return array of field names
*/
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).
* @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.
* 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.
* 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.
* 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");
for (Validator validator : validators) {
if (validator != null && (getTarget() != null && !validator.supports(getTarget().getClass()))) {
throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + getTarget());
}
}
}
/**
* 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 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.
* 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.
* 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 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.
* @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.
* 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.
* @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 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.
* 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;
}
}
}