|
|
@ -18,7 +18,6 @@ package org.springframework.boot.bind;
|
|
|
|
|
|
|
|
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.HashSet;
|
|
|
@ -35,6 +34,7 @@ import org.springframework.beans.InvalidPropertyException;
|
|
|
|
import org.springframework.beans.MutablePropertyValues;
|
|
|
|
import org.springframework.beans.MutablePropertyValues;
|
|
|
|
import org.springframework.beans.NotWritablePropertyException;
|
|
|
|
import org.springframework.beans.NotWritablePropertyException;
|
|
|
|
import org.springframework.beans.PropertyValue;
|
|
|
|
import org.springframework.beans.PropertyValue;
|
|
|
|
|
|
|
|
import org.springframework.core.convert.ConversionService;
|
|
|
|
import org.springframework.core.convert.TypeDescriptor;
|
|
|
|
import org.springframework.core.convert.TypeDescriptor;
|
|
|
|
import org.springframework.core.env.StandardEnvironment;
|
|
|
|
import org.springframework.core.env.StandardEnvironment;
|
|
|
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
@ -52,15 +52,11 @@ import org.springframework.validation.DataBinder;
|
|
|
|
* @author Dave Syer
|
|
|
|
* @author Dave Syer
|
|
|
|
* @author Phillip Webb
|
|
|
|
* @author Phillip Webb
|
|
|
|
* @author Stephane Nicoll
|
|
|
|
* @author Stephane Nicoll
|
|
|
|
|
|
|
|
* @author Andy Wilkinson
|
|
|
|
* @see RelaxedNames
|
|
|
|
* @see RelaxedNames
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public class RelaxedDataBinder extends DataBinder {
|
|
|
|
public class RelaxedDataBinder extends DataBinder {
|
|
|
|
|
|
|
|
|
|
|
|
private static final Set<String> BENIGN_PROPERTY_SOURCE_NAMES = Collections
|
|
|
|
|
|
|
|
.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
|
|
|
|
|
|
|
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
|
|
|
|
|
|
|
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Object BLANK = new Object();
|
|
|
|
private static final Object BLANK = new Object();
|
|
|
|
|
|
|
|
|
|
|
|
private String namePrefix;
|
|
|
|
private String namePrefix;
|
|
|
@ -83,12 +79,12 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
* @param namePrefix An optional prefix to be used when reading properties
|
|
|
|
* @param namePrefix An optional prefix to be used when reading properties
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
public RelaxedDataBinder(Object target, String namePrefix) {
|
|
|
|
public RelaxedDataBinder(Object target, String namePrefix) {
|
|
|
|
super(wrapTarget(target),
|
|
|
|
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix
|
|
|
|
(StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
|
|
|
|
: DEFAULT_OBJECT_NAME));
|
|
|
|
this.namePrefix = cleanNamePrefix(namePrefix);
|
|
|
|
this.namePrefix = cleanNamePrefix(namePrefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static String cleanNamePrefix(String namePrefix) {
|
|
|
|
private String cleanNamePrefix(String namePrefix) {
|
|
|
|
if (!StringUtils.hasLength(namePrefix)) {
|
|
|
|
if (!StringUtils.hasLength(namePrefix)) {
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -146,8 +142,7 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
propertyValues = addMapPrefix(propertyValues);
|
|
|
|
propertyValues = addMapPrefix(propertyValues);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
BeanWrapper wrapper = new BeanWrapperImpl(target);
|
|
|
|
BeanWrapper wrapper = new BeanWrapperImpl(target);
|
|
|
|
wrapper.setConversionService(
|
|
|
|
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
|
|
|
|
new RelaxedConversionService(getConversionService()));
|
|
|
|
|
|
|
|
wrapper.setAutoGrowNestedPaths(true);
|
|
|
|
wrapper.setAutoGrowNestedPaths(true);
|
|
|
|
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
|
|
|
|
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
|
|
|
|
Set<String> modifiedNames = new HashSet<String>();
|
|
|
|
Set<String> modifiedNames = new HashSet<String>();
|
|
|
@ -214,9 +209,10 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
if (name.startsWith(candidate)) {
|
|
|
|
if (name.startsWith(candidate)) {
|
|
|
|
name = name.substring(candidate.length());
|
|
|
|
name = name.substring(candidate.length());
|
|
|
|
if (!(this.ignoreNestedProperties && name.contains("."))) {
|
|
|
|
if (!(this.ignoreNestedProperties && name.contains("."))) {
|
|
|
|
PropertyOrigin propertyOrigin = findPropertyOrigin(value);
|
|
|
|
PropertyOrigin propertyOrigin = OriginCapablePropertyValue
|
|
|
|
rtn.addPropertyValue(new OriginCapablePropertyValue(name,
|
|
|
|
.getOrigin(value);
|
|
|
|
value.getValue(), propertyOrigin));
|
|
|
|
rtn.addPropertyValue(new OriginCapablePropertyValue(name, value
|
|
|
|
|
|
|
|
.getValue(), propertyOrigin));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -224,8 +220,7 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
return rtn;
|
|
|
|
return rtn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private PropertyValue modifyProperty(BeanWrapper target,
|
|
|
|
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
|
|
|
|
PropertyValue propertyValue) {
|
|
|
|
|
|
|
|
String name = propertyValue.getName();
|
|
|
|
String name = propertyValue.getName();
|
|
|
|
String normalizedName = normalizePath(target, name);
|
|
|
|
String normalizedName = normalizePath(target, name);
|
|
|
|
if (!normalizedName.equals(name)) {
|
|
|
|
if (!normalizedName.equals(name)) {
|
|
|
@ -251,55 +246,9 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
|
|
|
|
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
|
|
|
|
return new BeanPropertyBindingResult(getTarget(), getObjectName(),
|
|
|
|
return new RelaxedBeanPropertyBindingResult(getTarget(), getObjectName(),
|
|
|
|
isAutoGrowNestedPaths(), getAutoGrowCollectionLimit()) {
|
|
|
|
isAutoGrowNestedPaths(), getAutoGrowCollectionLimit(),
|
|
|
|
@Override
|
|
|
|
getConversionService());
|
|
|
|
protected BeanWrapper createBeanWrapper() {
|
|
|
|
|
|
|
|
BeanWrapper beanWrapper = new BeanWrapperImpl(getTarget()) {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void setPropertyValue(PropertyValue pv) throws BeansException {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
super.setPropertyValue(pv);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (NotWritablePropertyException ex) {
|
|
|
|
|
|
|
|
PropertyOrigin origin = findPropertyOrigin(pv);
|
|
|
|
|
|
|
|
if (isFatal(origin)) {
|
|
|
|
|
|
|
|
if (origin != null) {
|
|
|
|
|
|
|
|
throw new RelaxedBindingNotWritablePropertyException(
|
|
|
|
|
|
|
|
ex, origin);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
throw ex;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
logger.debug("Ignoring benign property binding failure",
|
|
|
|
|
|
|
|
ex);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
beanWrapper.setConversionService(
|
|
|
|
|
|
|
|
new RelaxedConversionService(getConversionService()));
|
|
|
|
|
|
|
|
beanWrapper.registerCustomEditor(InetAddress.class,
|
|
|
|
|
|
|
|
new InetAddressEditor());
|
|
|
|
|
|
|
|
return beanWrapper;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean isFatal(PropertyOrigin origin) {
|
|
|
|
|
|
|
|
if (origin == null) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return !BENIGN_PROPERTY_SOURCE_NAMES.contains(origin.getSource().getName());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private PropertyOrigin findPropertyOrigin(PropertyValue propertyValue) {
|
|
|
|
|
|
|
|
if (propertyValue instanceof OriginCapablePropertyValue) {
|
|
|
|
|
|
|
|
return ((OriginCapablePropertyValue) propertyValue).getOrigin();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return new OriginCapablePropertyValue(propertyValue).getOrigin();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
|
|
|
|
private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
|
|
|
@ -354,8 +303,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
|
|
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
|
|
|
|
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
|
|
|
|
Object value = (wrapper.isReadableProperty(propertyName)
|
|
|
|
Object value = (wrapper.isReadableProperty(propertyName) ? wrapper
|
|
|
|
? wrapper.getPropertyValue(propertyName) : null);
|
|
|
|
.getPropertyValue(propertyName) : null);
|
|
|
|
if (value instanceof Map) {
|
|
|
|
if (value instanceof Map) {
|
|
|
|
if (((Map) value).get(key) == BLANK) {
|
|
|
|
if (((Map) value).get(key) == BLANK) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
@ -364,8 +313,7 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path,
|
|
|
|
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
|
|
|
|
int index) {
|
|
|
|
|
|
|
|
String name = path.prefix(index);
|
|
|
|
String name = path.prefix(index);
|
|
|
|
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
|
|
|
|
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
|
|
|
|
.getElementTypeDescriptor();
|
|
|
|
.getElementTypeDescriptor();
|
|
|
@ -429,9 +377,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
String nested = resolvePropertyName(target, prefix, candidate.toString());
|
|
|
|
String nested = resolvePropertyName(target, prefix, candidate.toString());
|
|
|
|
if (nested != null) {
|
|
|
|
if (nested != null) {
|
|
|
|
Class<?> type = target.getPropertyType(nested);
|
|
|
|
Class<?> type = target.getPropertyType(nested);
|
|
|
|
if (type != null && Map.class.isAssignableFrom(type)) {
|
|
|
|
if ((type != null) && Map.class.isAssignableFrom(type)) {
|
|
|
|
// Special case for map property (gh-3836). Maybe could be fixed
|
|
|
|
// Special case for map property (gh-3836).
|
|
|
|
// in spring-beans)?
|
|
|
|
|
|
|
|
return nested + "[" + name.substring(candidate.length() + 1) + "]";
|
|
|
|
return nested + "[" + name.substring(candidate.length() + 1) + "]";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String propertyName = resolvePropertyName(target,
|
|
|
|
String propertyName = resolvePropertyName(target,
|
|
|
@ -507,6 +454,9 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* A path though properties of a bean.
|
|
|
|
|
|
|
|
*/
|
|
|
|
private static class BeanPath {
|
|
|
|
private static class BeanPath {
|
|
|
|
|
|
|
|
|
|
|
|
private List<PathNode> nodes;
|
|
|
|
private List<PathNode> nodes;
|
|
|
@ -678,4 +628,71 @@ public class RelaxedDataBinder extends DataBinder {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Extended version of {@link BeanPropertyBindingResult} to support relaxed binding.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private static class RelaxedBeanPropertyBindingResult extends
|
|
|
|
|
|
|
|
BeanPropertyBindingResult {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private RelaxedConversionService conversionService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public RelaxedBeanPropertyBindingResult(Object target, String objectName,
|
|
|
|
|
|
|
|
boolean autoGrowNestedPaths, int autoGrowCollectionLimit,
|
|
|
|
|
|
|
|
ConversionService conversionService) {
|
|
|
|
|
|
|
|
super(target, objectName, autoGrowNestedPaths, autoGrowCollectionLimit);
|
|
|
|
|
|
|
|
this.conversionService = new RelaxedConversionService(conversionService);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected BeanWrapper createBeanWrapper() {
|
|
|
|
|
|
|
|
BeanWrapper beanWrapper = new RelaxedBeanWrapper(getTarget());
|
|
|
|
|
|
|
|
beanWrapper.setConversionService(this.conversionService);
|
|
|
|
|
|
|
|
beanWrapper.registerCustomEditor(InetAddress.class, new InetAddressEditor());
|
|
|
|
|
|
|
|
return beanWrapper;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Extended version of {@link BeanWrapperImpl} to support relaxed binding.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private static class RelaxedBeanWrapper extends BeanWrapperImpl {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Set<String> BENIGN_PROPERTY_SOURCE_NAMES;
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
|
|
|
Set<String> names = new HashSet<String>();
|
|
|
|
|
|
|
|
names.add(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
|
|
|
|
|
|
|
|
names.add(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
|
|
|
|
|
|
|
|
BENIGN_PROPERTY_SOURCE_NAMES = Collections.unmodifiableSet(names);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public RelaxedBeanWrapper(Object target) {
|
|
|
|
|
|
|
|
super(target);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void setPropertyValue(PropertyValue pv) throws BeansException {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
super.setPropertyValue(pv);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (NotWritablePropertyException ex) {
|
|
|
|
|
|
|
|
PropertyOrigin origin = OriginCapablePropertyValue.getOrigin(pv);
|
|
|
|
|
|
|
|
if (isBenign(origin)) {
|
|
|
|
|
|
|
|
logger.debug("Ignoring benign property binding failure", ex);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (origin == null) {
|
|
|
|
|
|
|
|
throw ex;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new RelaxedBindingNotWritablePropertyException(ex, origin);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean isBenign(PropertyOrigin origin) {
|
|
|
|
|
|
|
|
String name = (origin == null ? null : origin.getSource().getName());
|
|
|
|
|
|
|
|
return BENIGN_PROPERTY_SOURCE_NAMES.contains(name);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|