Handle unresolved property names with underscore
When the configuration property name is not resolved against the target bean's property descriptors and the property name is delimited by `underscore` instead of `dot`, then `RelaxedDataBinder` doesn't bind those properties to the target if the property is of type Map. With thanks to @ilayaperumalg for the original PR. Updated by @dsyer to recognize that the property is of type Map, rather than blindly trying all combinations of property paths. Fixes gh-3836
This commit is contained in:
parent
d46cf8e82e
commit
5a28745527
|
@ -83,8 +83,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
* @param namePrefix An optional prefix to be used when reading properties
|
||||
*/
|
||||
public RelaxedDataBinder(Object target, String namePrefix) {
|
||||
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix
|
||||
: DEFAULT_OBJECT_NAME));
|
||||
super(wrapTarget(target),
|
||||
(StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
|
||||
this.namePrefix = cleanNamePrefix(namePrefix);
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
propertyValues = addMapPrefix(propertyValues);
|
||||
}
|
||||
BeanWrapper wrapper = new BeanWrapperImpl(target);
|
||||
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
|
||||
wrapper.setConversionService(
|
||||
new RelaxedConversionService(getConversionService()));
|
||||
wrapper.setAutoGrowNestedPaths(true);
|
||||
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
|
||||
Set<String> modifiedNames = new HashSet<String>();
|
||||
|
@ -214,8 +215,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
name = name.substring(candidate.length());
|
||||
if (!(this.ignoreNestedProperties && name.contains("."))) {
|
||||
PropertyOrigin propertyOrigin = findPropertyOrigin(value);
|
||||
rtn.addPropertyValue(new OriginCapablePropertyValue(name, value
|
||||
.getValue(), propertyOrigin));
|
||||
rtn.addPropertyValue(new OriginCapablePropertyValue(name,
|
||||
value.getValue(), propertyOrigin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +224,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
return rtn;
|
||||
}
|
||||
|
||||
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
|
||||
private PropertyValue modifyProperty(BeanWrapper target,
|
||||
PropertyValue propertyValue) {
|
||||
String name = propertyValue.getName();
|
||||
String normalizedName = normalizePath(target, name);
|
||||
if (!normalizedName.equals(name)) {
|
||||
|
@ -277,8 +279,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
}
|
||||
}
|
||||
};
|
||||
beanWrapper.setConversionService(new RelaxedConversionService(
|
||||
getConversionService()));
|
||||
beanWrapper.setConversionService(
|
||||
new RelaxedConversionService(getConversionService()));
|
||||
beanWrapper.registerCustomEditor(InetAddress.class,
|
||||
new InetAddressEditor());
|
||||
return beanWrapper;
|
||||
|
@ -352,8 +354,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
|
||||
Object value = (wrapper.isReadableProperty(propertyName) ? wrapper
|
||||
.getPropertyValue(propertyName) : null);
|
||||
Object value = (wrapper.isReadableProperty(propertyName)
|
||||
? wrapper.getPropertyValue(propertyName) : null);
|
||||
if (value instanceof Map) {
|
||||
if (((Map) value).get(key) == BLANK) {
|
||||
return true;
|
||||
|
@ -362,7 +364,8 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
|
||||
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path,
|
||||
int index) {
|
||||
String name = path.prefix(index);
|
||||
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
|
||||
.getElementTypeDescriptor();
|
||||
|
@ -425,6 +428,12 @@ public class RelaxedDataBinder extends DataBinder {
|
|||
candidate.append(field);
|
||||
String nested = resolvePropertyName(target, prefix, candidate.toString());
|
||||
if (nested != null) {
|
||||
Class<?> type = target.getPropertyType(nested);
|
||||
if (type != null && Map.class.isAssignableFrom(type)) {
|
||||
// Special case for map property (gh-3836). Maybe could be fixed
|
||||
// in spring-beans)?
|
||||
return nested + "[" + name.substring(candidate.length() + 1) + "]";
|
||||
}
|
||||
String propertyName = resolvePropertyName(target,
|
||||
joinString(prefix, nested),
|
||||
name.substring(candidate.length() + 1));
|
||||
|
|
|
@ -16,6 +16,15 @@
|
|||
|
||||
package org.springframework.boot.bind;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
|
@ -51,15 +60,6 @@ import org.springframework.validation.DataBinder;
|
|||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RelaxedDataBinder}.
|
||||
*
|
||||
|
@ -323,6 +323,14 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals("123", target.getNested().get("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedMapPropsWithUnderscores() throws Exception {
|
||||
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||
bind(target, "nested_foo: bar\n" + "nested_value: 123");
|
||||
assertEquals("123", target.getNested().get("value"));
|
||||
assertEquals("bar", target.getNested().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedUntypedMap() throws Exception {
|
||||
TargetWithNestedUntypedMap target = new TargetWithNestedUntypedMap();
|
||||
|
@ -338,6 +346,22 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals("123", target.getNested().get("value.foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedMapOfStringWithUnderscore() throws Exception {
|
||||
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
|
||||
bind(target, "nested_foo: bar\n" + "nested_value_foo: 123");
|
||||
assertEquals("bar", target.getNested().get("foo"));
|
||||
assertEquals("123", target.getNested().get("value_foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedMapOfStringWithUnderscoreAndupperCase() throws Exception {
|
||||
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
|
||||
bind(target, "NESTED_FOO: bar\n" + "NESTED_VALUE_FOO: 123");
|
||||
assertEquals("bar", target.getNested().get("FOO"));
|
||||
assertEquals("123", target.getNested().get("VALUE_FOO"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedMapOfStringReferenced() throws Exception {
|
||||
TargetWithNestedMapOfString target = new TargetWithNestedMapOfString();
|
||||
|
@ -686,7 +710,7 @@ public class RelaxedDataBinderTests {
|
|||
}
|
||||
|
||||
public static class RequiredKeysValidator implements
|
||||
ConstraintValidator<RequiredKeys, Map<String, Object>> {
|
||||
ConstraintValidator<RequiredKeys, Map<String, Object>> {
|
||||
|
||||
private String[] fields;
|
||||
|
||||
|
|
Loading…
Reference in New Issue