All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.validation.DataBinder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2024 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
 *
 *      https://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.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Predicate;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
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.PropertyEditorRegistrar;
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.CollectionFactory;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
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;
import org.springframework.validation.annotation.ValidationAnnotationUtils;

/**
 * Binder that allows applying property values to a target object via constructor
 * and setter injection, and also supports validation and binding result analysis.
 *
 * 

The binding process can be customized by specifying allowed field patterns, * required fields, custom editors, etc. * *

WARNING: Data binding can lead to security issues by exposing * parts of the object graph that are not meant to be accessed or modified by * external clients. Therefore, the design and use of data binding should be considered * carefully with regard to security. For more details, please refer to the dedicated * sections on data binding for * Spring Web MVC and * Spring WebFlux * in the reference manual. * *

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: * *

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

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. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop * @author Stephane Nicoll * @author Kazuki Shimizu * @author Sam Brannen * @see #setAllowedFields * @see #setRequiredFields * @see #registerCustomEditor * @see #setMessageCodesResolver * @see #setBindingErrorProcessor * @see #construct * @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". */ 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); @Nullable private Object target; @Nullable ResolvableType targetType; private final String objectName; @Nullable private AbstractPropertyBindingResult bindingResult; private boolean directFieldAccess = false; @Nullable private ExtendedTypeConverter typeConverter; private boolean declarativeBinding = false; 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 NameResolver nameResolver; @Nullable private ConversionService conversionService; @Nullable private MessageCodesResolver messageCodesResolver; private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor(); private final List validators = new ArrayList<>(); @Nullable private Predicate excludedValidators; /** * 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. * @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. *

If the target object is {@code null} and {@link #getTargetType()} is set, * then {@link #construct(ValueResolver)} may be called to create the target. */ @Nullable public Object getTarget() { return this.target; } /** * Return the name of the bound object. */ public String getObjectName() { return this.objectName; } /** * Set the type for the target object. When the target is {@code null}, * setting the targetType allows using {@link #construct} to create the target. * @param targetType the type of the target object * @since 6.1 * @see #construct */ public void setTargetType(ResolvableType targetType) { Assert.state(this.target == null, "targetType is used to for target creation but target is already set"); this.targetType = targetType; } /** * Return the {@link #setTargetType configured} type for the target object. * @since 6.1 */ @Nullable public ResolvableType getTargetType() { return this.targetType; } /** * 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. *

Used for setter/field injection via {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}. * @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. *

Used for setter/field injection via {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}. * @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.directFieldAccess = false; } /** * 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.directFieldAccess = true; } /** * 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) { this.bindingResult = (this.directFieldAccess ? createDirectFieldBindingResult(): createBeanPropertyBindingResult()); } 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 ExtendedTypeConverter(); 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 bind only fields explicitly intended for binding including: *

    *
  • Constructor binding via {@link #construct}. *
  • Property binding with configured * {@link #setAllowedFields(String...) allowedFields}. *
*

Default is "false". Turn this on to limit binding to constructor * parameters and allowed fields. * @since 6.1 */ public void setDeclarativeBinding(boolean declarativeBinding) { this.declarativeBinding = declarativeBinding; } /** * Return whether to bind only fields intended for binding. * @since 6.1 */ public boolean isDeclarativeBinding() { return this.declarativeBinding; } /** * 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}. *

Used for binding to fields with {@link #bind(PropertyValues)}, * and not applicable to constructor binding via {@link #construct} * which uses only the values it needs. * @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}. *

Used for binding to fields with {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}, * which uses only the values it needs. * @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 field patterns 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 {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as * well as direct equality. *

The default implementation of this method stores allowed field patterns * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical} * form. Subclasses which override this method must therefore take this into * account. *

More sophisticated matching can be implemented by overriding the * {@link #isAllowed} method. *

Alternatively, specify a list of disallowed field patterns. *

Used for binding to fields with {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}, * which uses only the values it needs. * @param allowedFields array of allowed field patterns * @see #setDisallowedFields * @see #isAllowed(String) */ public void setAllowedFields(@Nullable String... allowedFields) { this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields); } /** * Return the field patterns that should be allowed for binding. * @return array of allowed field patterns * @see #setAllowedFields(String...) */ @Nullable public String[] getAllowedFields() { return this.allowedFields; } /** * Register field patterns 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 {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as * well as direct equality. *

The default implementation of this method stores disallowed field patterns * in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical} * form and also transforms disallowed field patterns to * {@linkplain String#toLowerCase() lowercase} to support case-insensitive * pattern matching in {@link #isAllowed}. Subclasses which override this * method must therefore take both of these transformations into account. *

More sophisticated matching can be implemented by overriding the * {@link #isAllowed} method. *

Alternatively, specify a list of allowed field patterns. *

Used for binding to fields with {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}, * which uses only the values it needs. * @param disallowedFields array of disallowed field patterns * @see #setAllowedFields * @see #isAllowed(String) */ public void setDisallowedFields(@Nullable String... disallowedFields) { if (disallowedFields == null) { this.disallowedFields = null; } else { String[] fieldPatterns = new String[disallowedFields.length]; for (int i = 0; i < fieldPatterns.length; i++) { String field = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]); fieldPatterns[i] = field.toLowerCase(Locale.ROOT); } this.disallowedFields = fieldPatterns; } } /** * Return the field patterns that should not be allowed for binding. * @return array of disallowed field patterns * @see #setDisallowedFields(String...) */ @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). *

Used for binding to fields with {@link #bind(PropertyValues)}, and not * applicable to constructor binding via {@link #construct}, * which uses only the values it needs. * @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. * @return array of field names */ @Nullable public String[] getRequiredFields() { return this.requiredFields; } /** * Configure a resolver to determine the name of the value to bind to a * constructor parameter in {@link #construct}. *

If not configured, or if the name cannot be resolved, by default * {@link org.springframework.core.DefaultParameterNameDiscoverer} is used. * @param nameResolver the resolver to use * @since 6.1 */ public void setNameResolver(NameResolver nameResolver) { this.nameResolver = nameResolver; } /** * Return the {@link #setNameResolver configured} name resolver for * constructor parameters. * @since 6.1 */ @Nullable public NameResolver getNameResolver() { return this.nameResolver; } /** * 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(@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 {@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(@Nullable Validator validator) { assertValidators(validator); this.validators.clear(); if (validator != null) { this.validators.add(validator); } } @SuppressWarnings("NullAway") private void assertValidators(@Nullable 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); } } } /** * Configure a predicate to exclude validators. * @since 6.1 */ public void setExcludedValidators(Predicate predicate) { this.excludedValidators = predicate; } /** * 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. */ @Nullable public Validator getValidator() { return (!this.validators.isEmpty() ? this.validators.get(0) : null); } /** * Return the Validators to apply after data binding. */ public List getValidators() { return Collections.unmodifiableList(this.validators); } /** * Return the Validators to apply after data binding. This includes the * configured {@link #getValidators() validators} filtered by the * {@link #setExcludedValidators(Predicate) exclude predicate}. * @since 6.1 */ @SuppressWarnings("NullAway") public List getValidatorsToApply() { return (this.excludedValidators != null ? this.validators.stream().filter(validator -> !this.excludedValidators.test(validator)).toList() : Collections.unmodifiableList(this.validators)); } //--------------------------------------------------------------------- // Implementation of PropertyEditorRegistry/TypeConverter interface //--------------------------------------------------------------------- /** * Specify a {@link 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. */ @Nullable public ConversionService getConversionService() { return this.conversionService; } /** * Add a custom formatter, applying it to all fields matching the * {@link Formatter}-declared type. *

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(@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 convertIfNecessary(@Nullable Object value, @Nullable Class requiredType) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType); } @Override @Nullable public T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, methodParam); } @Override @Nullable public T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType, @Nullable Field field) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, field); } @Nullable @Override public T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException { return getTypeConverter().convertIfNecessary(value, requiredType, typeDescriptor); } /** * Create the target with constructor injection of values. It is expected that * {@link #setTargetType(ResolvableType)} was previously called and that * {@link #getTarget()} is {@code null}. *

Uses a public, no-arg constructor if available in the target object type, * also supporting a "primary constructor" approach for data classes as follows: * It understands the JavaBeans {@code ConstructorProperties} annotation as * well as runtime-retained parameter names in the bytecode, associating * input values with constructor arguments by name. If no such constructor is * found, the default constructor will be used (even if not public), assuming * subsequent bean property bindings through setter methods. *

After the call, use {@link #getBindingResult()} to check for failures * to bind to, and/or validate constructor arguments. If there are no errors, * the target is set, and {@link #doBind(MutablePropertyValues)} can be used * for further initialization via setters. * @param valueResolver to resolve constructor argument values with * @throws BeanInstantiationException in case of constructor failure * @since 6.1 */ public void construct(ValueResolver valueResolver) { Assert.state(this.target == null, "Target instance already available"); Assert.state(this.targetType != null, "Target type not set"); this.target = createObject(this.targetType, "", valueResolver); if (!getBindingResult().hasErrors()) { this.bindingResult = null; if (this.typeConverter != null) { this.typeConverter.registerCustomEditors(getPropertyAccessor()); } } } @Nullable private Object createObject(ResolvableType objectType, String nestedPath, ValueResolver valueResolver) { Class clazz = objectType.resolve(); boolean isOptional = (clazz == Optional.class); clazz = (isOptional ? objectType.resolveGeneric(0) : clazz); if (clazz == null) { throw new IllegalStateException( "Insufficient type information to create instance of " + objectType); } Object result = null; Constructor ctor = BeanUtils.getResolvableConstructor(clazz); if (ctor.getParameterCount() == 0) { // A single default constructor -> clearly a standard JavaBeans arrangement. result = BeanUtils.instantiateClass(ctor); } else { // A single data class constructor -> resolve constructor arguments from request parameters. String[] paramNames = BeanUtils.getParameterNames(ctor); Class[] paramTypes = ctor.getParameterTypes(); Object[] args = new Object[paramTypes.length]; Set failedParamNames = new HashSet<>(4); for (int i = 0; i < paramNames.length; i++) { MethodParameter param = MethodParameter.forFieldAwareConstructor(ctor, i, paramNames[i]); String lookupName = null; if (this.nameResolver != null) { lookupName = this.nameResolver.resolveName(param); } if (lookupName == null) { lookupName = paramNames[i]; } String paramPath = nestedPath + lookupName; Class paramType = paramTypes[i]; ResolvableType resolvableType = ResolvableType.forMethodParameter(param); Object value = valueResolver.resolveValue(paramPath, paramType); if (value == null) { if (List.class.isAssignableFrom(paramType)) { value = createList(paramPath, paramType, resolvableType, valueResolver); } else if (Map.class.isAssignableFrom(paramType)) { value = createMap(paramPath, paramType, resolvableType, valueResolver); } else if (paramType.isArray()) { value = createArray(paramPath, resolvableType, valueResolver); } } if (value == null && shouldConstructArgument(param) && hasValuesFor(paramPath, valueResolver)) { args[i] = createObject(resolvableType, paramPath + ".", valueResolver); } else { try { if (value == null && (param.isOptional() || getBindingResult().hasErrors())) { args[i] = (param.getParameterType() == Optional.class ? Optional.empty() : null); } else { args[i] = convertIfNecessary(value, paramType, param); } } catch (TypeMismatchException ex) { ex.initPropertyName(paramPath); args[i] = null; failedParamNames.add(paramPath); getBindingResult().recordFieldValue(paramPath, paramType, value); getBindingErrorProcessor().processPropertyAccessException(ex, getBindingResult()); } } } if (getBindingResult().hasErrors()) { for (int i = 0; i < paramNames.length; i++) { String paramPath = nestedPath + paramNames[i]; if (!failedParamNames.contains(paramPath)) { Object value = args[i]; getBindingResult().recordFieldValue(paramPath, paramTypes[i], value); validateConstructorArgument(ctor.getDeclaringClass(), nestedPath, paramNames[i], value); } } if (!(objectType.getSource() instanceof MethodParameter param && param.isOptional())) { try { result = BeanUtils.instantiateClass(ctor, args); } catch (BeanInstantiationException ex) { // swallow and proceed without target instance } } } else { try { result = BeanUtils.instantiateClass(ctor, args); } catch (BeanInstantiationException ex) { if (KotlinDetector.isKotlinType(clazz) && ex.getCause() instanceof NullPointerException cause) { ObjectError error = new ObjectError(ctor.getName(), cause.getMessage()); getBindingResult().addError(error); } else { throw ex; } } } } return (isOptional && !nestedPath.isEmpty() ? Optional.ofNullable(result) : result); } /** * Whether to instantiate the constructor argument of the given type, * matching its own constructor arguments to bind values. *

By default, simple value types, maps, collections, and arrays are * excluded from nested constructor binding initialization. * @since 6.1.2 */ protected boolean shouldConstructArgument(MethodParameter param) { Class type = param.nestedIfOptional().getNestedParameterType(); return !BeanUtils.isSimpleValueType(type) && !type.getPackageName().startsWith("java."); } private boolean hasValuesFor(String paramPath, ValueResolver resolver) { for (String name : resolver.getNames()) { if (name.startsWith(paramPath + ".")) { return true; } } return false; } @SuppressWarnings("unchecked") @Nullable private List createList( String paramPath, Class paramType, ResolvableType type, ValueResolver valueResolver) { ResolvableType elementType = type.getNested(2); SortedSet indexes = getIndexes(paramPath, valueResolver); if (indexes == null) { return null; } int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1 : 0); List list = (List) CollectionFactory.createCollection(paramType, size); indexes.forEach(i -> list.add(null)); for (int index : indexes) { list.set(index, (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver)); } return list; } @SuppressWarnings("unchecked") @Nullable private Map createMap( String paramPath, Class paramType, ResolvableType type, ValueResolver valueResolver) { ResolvableType elementType = type.getNested(2); Map map = null; for (String name : valueResolver.getNames()) { if (!name.startsWith(paramPath + "[")) { continue; } int startIdx = paramPath.length() + 1; int endIdx = name.indexOf(']', startIdx); String nestedPath = name.substring(0, endIdx + 2); boolean quoted = (endIdx - startIdx > 2 && name.charAt(startIdx) == '\'' && name.charAt(endIdx - 1) == '\''); String key = (quoted ? name.substring(startIdx + 1, endIdx - 1) : name.substring(startIdx, endIdx)); if (map == null) { map = CollectionFactory.createMap(paramType, 16); } if (!map.containsKey(key)) { map.put(key, (V) createObject(elementType, nestedPath, valueResolver)); } } return map; } @SuppressWarnings("unchecked") @Nullable private V[] createArray(String paramPath, ResolvableType type, ValueResolver valueResolver) { ResolvableType elementType = type.getNested(2); SortedSet indexes = getIndexes(paramPath, valueResolver); if (indexes == null) { return null; } int size = (indexes.last() < this.autoGrowCollectionLimit ? indexes.last() + 1: 0); V[] array = (V[]) Array.newInstance(elementType.resolve(), size); for (int index : indexes) { array[index] = (V) createObject(elementType, paramPath + "[" + index + "].", valueResolver); } return array; } @Nullable private static SortedSet getIndexes(String paramPath, ValueResolver valueResolver) { SortedSet indexes = null; for (String name : valueResolver.getNames()) { if (name.startsWith(paramPath + "[")) { int endIndex = name.indexOf(']', paramPath.length() + 2); String rawIndex = name.substring(paramPath.length() + 1, endIndex); int index = Integer.parseInt(rawIndex); indexes = (indexes != null ? indexes : new TreeSet<>()); indexes.add(index); } } return indexes; } private void validateConstructorArgument( Class constructorClass, String nestedPath, String name, @Nullable Object value) { Object[] hints = null; if (this.targetType != null && this.targetType.getSource() instanceof MethodParameter parameter) { for (Annotation ann : parameter.getParameterAnnotations()) { hints = ValidationAnnotationUtils.determineValidationHints(ann); if (hints != null) { break; } } } if (hints == null) { return; } for (Validator validator : getValidatorsToApply()) { if (validator instanceof SmartValidator smartValidator) { boolean isNested = !nestedPath.isEmpty(); if (isNested) { getBindingResult().pushNestedPath(nestedPath.substring(0, nestedPath.length() - 1)); } try { smartValidator.validateValue(constructorClass, name, value, getBindingResult(), hints); } catch (IllegalArgumentException ex) { // No corresponding field on the target class... } if (isNested) { getBindingResult().popNestedPath(); } } } } /** * 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. * @param pvs property values to bind * @see #doBind(org.springframework.beans.MutablePropertyValues) */ public void bind(PropertyValues pvs) { if (shouldNotBindPropertyValues()) { return; } MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ? mutablePropertyValues : new MutablePropertyValues(pvs)); doBind(mpvs); } /** * Whether to not bind parameters to properties. Returns "true" if * {@link #isDeclarativeBinding()} is on, and * {@link #setAllowedFields(String...) allowedFields} are not configured. * @since 6.1 */ protected boolean shouldNotBindPropertyValues() { return (isDeclarativeBinding() && ObjectUtils.isEmpty(this.allowedFields)); } /** * 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"); } } } } /** * Determine if the given field is allowed for binding. *

Invoked for each passed-in property value. *

Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and * {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as * well as direct equality, in the configured lists of allowed field patterns * and disallowed field patterns. *

Matching against allowed field patterns is case-sensitive; whereas, * matching against disallowed field patterns is case-insensitive. *

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, but care must be taken to honor the * aforementioned contract. * @param field the field to check * @return {@code true} 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.toLowerCase(Locale.ROOT)))); } /** * 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 */ @SuppressWarnings("NullAway") protected void checkRequiredFields(MutablePropertyValues mpvs) { String[] requiredFields = getRequiredFields(); if (!ObjectUtils.isEmpty(requiredFields)) { Map 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 text) { empty = !StringUtils.hasText(text); } else if (pv.getValue() instanceof String[] values) { 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 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() { 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 : getValidatorsToApply()) { 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. * @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 : getValidatorsToApply()) { if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) { smartValidator.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. * @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(); } /** * Strategy to determine the name of the value to bind to a method parameter. * Supported on constructor parameters with {@link #construct constructor binding} * which performs lookups via {@link ValueResolver#resolveValue}. */ public interface NameResolver { /** * Return the name to use for the given method parameter, or {@code null} * if unresolved. For constructor parameters, the name is determined via * {@link org.springframework.core.DefaultParameterNameDiscoverer} if unresolved. */ @Nullable String resolveName(MethodParameter parameter); } /** * Strategy for {@link #construct constructor binding} to look up the values * to bind to a given constructor parameter. */ public interface ValueResolver { /** * Resolve the value for the given name and target parameter type. * @param name the name to use for the lookup, possibly a nested path * for constructor parameters on nested objects * @param type the target type, based on the constructor parameter type * @return the resolved value, possibly {@code null} if none found */ @Nullable Object resolveValue(String name, Class type); /** * Return the names of all property values. * @since 6.1.2 */ Set getNames(); } /** * {@link SimpleTypeConverter} that is also {@link PropertyEditorRegistrar}. */ private static class ExtendedTypeConverter extends SimpleTypeConverter implements PropertyEditorRegistrar { @Override public void registerCustomEditors(PropertyEditorRegistry registry) { copyCustomEditorsTo(registry, null); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy