mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Added new logic to RelaxedDataBinder to deal with nested stuff
* Leverages existing behaviour of BeanWrapperImpl where possible to autogrow collections and lists * Logic for scanning and converting bean paths encapsulated in BeanPath inner class [Fixes #53947797] [bs-249] @ConfigurationProperties cannot bind to Map<String,List<Thing>>
This commit is contained in:
parent
2d1f758fd8
commit
b1f4320c17
@ -17,6 +17,7 @@
|
|||||||
package org.springframework.bootstrap.bind;
|
package org.springframework.bootstrap.bind;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -26,6 +27,7 @@ import org.springframework.beans.BeanWrapperImpl;
|
|||||||
import org.springframework.beans.InvalidPropertyException;
|
import org.springframework.beans.InvalidPropertyException;
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.beans.PropertyValue;
|
import org.springframework.beans.PropertyValue;
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.DataBinder;
|
import org.springframework.validation.DataBinder;
|
||||||
|
|
||||||
@ -129,66 +131,228 @@ public class RelaxedDataBinder extends DataBinder {
|
|||||||
|
|
||||||
private void modifyProperty(MutablePropertyValues propertyValues, BeanWrapper target,
|
private void modifyProperty(MutablePropertyValues propertyValues, BeanWrapper target,
|
||||||
PropertyValue propertyValue, int index) {
|
PropertyValue propertyValue, int index) {
|
||||||
|
String oldName = propertyValue.getName();
|
||||||
String name = propertyValue.getName();
|
String name = normalizePath(target, oldName);
|
||||||
StringBuilder builder = new StringBuilder();
|
if (!name.equals(oldName)) {
|
||||||
Class<?> type = target.getWrappedClass();
|
propertyValues.setPropertyValueAt(
|
||||||
|
new PropertyValue(name, propertyValue.getValue()), index);
|
||||||
for (String key : StringUtils.delimitedListToStringArray(name, ".")) {
|
|
||||||
if (builder.length() != 0) {
|
|
||||||
builder.append(".");
|
|
||||||
}
|
|
||||||
String oldKey = key;
|
|
||||||
key = getActualPropertyName(target, builder.toString(), oldKey);
|
|
||||||
builder.append(key);
|
|
||||||
String base = builder.toString();
|
|
||||||
if (!oldKey.equals(key)) {
|
|
||||||
propertyValues.setPropertyValueAt(
|
|
||||||
new PropertyValue(base, propertyValue.getValue()), index);
|
|
||||||
}
|
|
||||||
type = target.getPropertyType(base);
|
|
||||||
|
|
||||||
// Any nested properties that are maps, are assumed to be simple nested
|
|
||||||
// maps of maps...
|
|
||||||
if (type != null && Map.class.isAssignableFrom(type)) {
|
|
||||||
Map<String, Object> nested = new LinkedHashMap<String, Object>();
|
|
||||||
if (target.getPropertyValue(base) != null) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, Object> existing = (Map<String, Object>) target
|
|
||||||
.getPropertyValue(base);
|
|
||||||
nested = existing;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
target.setPropertyValue(base, nested);
|
|
||||||
}
|
|
||||||
modifyPopertiesForMap(nested, propertyValues, index, base);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void modifyPopertiesForMap(Map<String, Object> target,
|
protected String normalizePath(BeanWrapper wrapper, String path) {
|
||||||
MutablePropertyValues propertyValues, int index, String base) {
|
return initializePath(wrapper, new BeanPath(path), 0);
|
||||||
PropertyValue propertyValue = propertyValues.getPropertyValueList().get(index);
|
}
|
||||||
String name = propertyValue.getName();
|
|
||||||
String suffix = name.substring(base.length());
|
|
||||||
Map<String, Object> value = new LinkedHashMap<String, Object>();
|
|
||||||
String[] tree = StringUtils.delimitedListToStringArray(
|
|
||||||
suffix.startsWith(".") ? suffix.substring(1) : suffix, ".");
|
|
||||||
for (int j = 0; j < tree.length - 1; j++) {
|
|
||||||
if (!target.containsKey(tree[j])) {
|
|
||||||
target.put(tree[j], value);
|
|
||||||
}
|
|
||||||
target = value;
|
|
||||||
value = new LinkedHashMap<String, Object>();
|
|
||||||
}
|
|
||||||
String refName = base + suffix.replaceAll("\\.([a-zA-Z0-9]*)", "[$1]");
|
|
||||||
propertyValues.setPropertyValueAt(
|
|
||||||
new PropertyValue(refName, propertyValue.getValue()), index);
|
|
||||||
|
|
||||||
|
private static class BeanPath {
|
||||||
|
|
||||||
|
private List<PathNode> nodes;
|
||||||
|
|
||||||
|
public BeanPath(String path) {
|
||||||
|
this.nodes = splitPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mapIndex(int index) {
|
||||||
|
PathNode node = this.nodes.get(index);
|
||||||
|
if (node instanceof PropertyNode) {
|
||||||
|
node = ((PropertyNode) node).mapIndex();
|
||||||
|
}
|
||||||
|
this.nodes.set(index, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String prefix(int index) {
|
||||||
|
return range(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rename(int index, String name) {
|
||||||
|
this.nodes.get(index).name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name(int index) {
|
||||||
|
if (index < this.nodes.size()) {
|
||||||
|
return this.nodes.get(index).name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return this.nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String range(int start, int end) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
PathNode node = this.nodes.get(i);
|
||||||
|
builder.append(node);
|
||||||
|
}
|
||||||
|
if (builder.toString().startsWith(("."))) {
|
||||||
|
builder.replace(0, 1, "");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isArrayIndex(int index) {
|
||||||
|
return this.nodes.get(index) instanceof ArrayIndexNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProperty(int index) {
|
||||||
|
return this.nodes.get(index) instanceof PropertyNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return prefix(this.nodes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PathNode {
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
public PathNode(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ArrayIndexNode extends PathNode {
|
||||||
|
|
||||||
|
public ArrayIndexNode(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + this.name + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MapIndexNode extends PathNode {
|
||||||
|
|
||||||
|
public MapIndexNode(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + this.name + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PropertyNode extends PathNode {
|
||||||
|
|
||||||
|
public PropertyNode(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapIndexNode mapIndex() {
|
||||||
|
return new MapIndexNode(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "." + this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PathNode> splitPath(String path) {
|
||||||
|
List<PathNode> nodes = new ArrayList<PathNode>();
|
||||||
|
for (String name : StringUtils.delimitedListToStringArray(path, ".")) {
|
||||||
|
for (String sub : StringUtils.delimitedListToStringArray(name, "[")) {
|
||||||
|
if (StringUtils.hasText(sub)) {
|
||||||
|
if (sub.endsWith("]")) {
|
||||||
|
sub = sub.substring(0, sub.length() - 1);
|
||||||
|
if (sub.matches("[0-9]+")) {
|
||||||
|
nodes.add(new ArrayIndexNode(sub));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nodes.add(new MapIndexNode(sub));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nodes.add(new PropertyNode(sub));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
|
||||||
|
String prefix = path.prefix(index);
|
||||||
|
String key = path.name(index);
|
||||||
|
if (key == null) {
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
if (path.isProperty(index)) {
|
||||||
|
key = getActualPropertyName(wrapper, prefix, key);
|
||||||
|
path.rename(index, key);
|
||||||
|
}
|
||||||
|
if (index >= path.length() - 1) {
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
String name = path.prefix(++index);
|
||||||
|
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name);
|
||||||
|
if (descriptor == null || descriptor.isMap()) {
|
||||||
|
if (descriptor != null) {
|
||||||
|
wrapper.getPropertyValue(name + "[foo]");
|
||||||
|
}
|
||||||
|
path.mapIndex(index);
|
||||||
|
extendMapIfNecessary(wrapper, path, index);
|
||||||
|
}
|
||||||
|
else if (descriptor.isCollection()) {
|
||||||
|
// TODO: test collection extension
|
||||||
|
extendCollectionIfNecessary(wrapper, path, index);
|
||||||
|
}
|
||||||
|
else if (descriptor.getType().equals(Object.class)) {
|
||||||
|
path.mapIndex(index);
|
||||||
|
name = path.prefix(index + 1);
|
||||||
|
if (wrapper.getPropertyValue(name) == null) {
|
||||||
|
wrapper.setPropertyValue(name, new LinkedHashMap<String, Object>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index < path.length()) {
|
||||||
|
return initializePath(wrapper, path, index);
|
||||||
|
}
|
||||||
|
return path.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
|
||||||
|
String name = path.prefix(index);
|
||||||
|
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
|
||||||
|
.getElementTypeDescriptor();
|
||||||
|
if (!elementDescriptor.isMap() && !elementDescriptor.isCollection()
|
||||||
|
&& !elementDescriptor.getType().equals(Object.class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object extend = new LinkedHashMap<String, Object>();
|
||||||
|
if (!elementDescriptor.isMap() && path.isArrayIndex(index + 1)) {
|
||||||
|
extend = new ArrayList<Object>();
|
||||||
|
}
|
||||||
|
wrapper.setPropertyValue(path.prefix(index + 1), extend);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extendMapIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
|
||||||
|
String name = path.prefix(index);
|
||||||
|
TypeDescriptor parent = wrapper.getPropertyTypeDescriptor(name);
|
||||||
|
if (parent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TypeDescriptor descriptor = parent.getMapValueTypeDescriptor();
|
||||||
|
if (!descriptor.isMap() && !descriptor.isCollection()
|
||||||
|
&& !descriptor.getType().equals(Object.class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object extend = new LinkedHashMap<String, Object>();
|
||||||
|
if (descriptor.isCollection()) {
|
||||||
|
extend = new ArrayList<Object>();
|
||||||
|
}
|
||||||
|
wrapper.setPropertyValue(path.prefix(index + 1), extend);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
|
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
|
||||||
|
prefix = StringUtils.hasText(prefix) ? prefix + "." : "";
|
||||||
for (Variation variation : Variation.values()) {
|
for (Variation variation : Variation.values()) {
|
||||||
for (Manipulation manipulation : Manipulation.values()) {
|
for (Manipulation manipulation : Manipulation.values()) {
|
||||||
// Apply all manipulations before attempting variations
|
// Apply all manipulations before attempting variations
|
||||||
@ -199,7 +363,7 @@ public class RelaxedDataBinder extends DataBinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InvalidPropertyException ex) {
|
catch (InvalidPropertyException ex) {
|
||||||
// swallow and contrinue
|
// swallow and continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2013 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.bootstrap.bind;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.BeanWrapperImpl;
|
||||||
|
import org.springframework.bootstrap.bind.RelaxedDataBinderTests.TargetWithNestedObject;
|
||||||
|
import org.springframework.context.expression.MapAccessor;
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
public class BindingPreparationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeanWrapperCreatesNewMaps() throws Exception {
|
||||||
|
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
// For a nested map, you only have to get an element of it for it to be created
|
||||||
|
wrapper.getPropertyValue("nested[foo]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeanWrapperCreatesNewMapEntries() throws Exception {
|
||||||
|
TargetWithNestedMapOfBean target = new TargetWithNestedMapOfBean();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
// For a nested map, you only have to get an element of it for it to be created
|
||||||
|
wrapper.getPropertyValue("nested[foo]");
|
||||||
|
wrapper.setPropertyValue("nested[foo].foo", "bar");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowWithFuzzyNameCapitals() throws Exception {
|
||||||
|
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
String result = binder.normalizePath(wrapper, "NESTED[foo][bar]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertEquals("nested[foo][bar]", result);
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo][bar]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowWithFuzzyNameUnderscores() throws Exception {
|
||||||
|
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
String result = binder.normalizePath(wrapper, "nes_ted[foo][bar]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertEquals("nested[foo][bar]", result);
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo][bar]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowNewNestedMapOfMaps() throws Exception {
|
||||||
|
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
String result = binder.normalizePath(wrapper, "nested[foo][bar]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertEquals("nested[foo][bar]", result);
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo][bar]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowNewNestedMapOfBeans() throws Exception {
|
||||||
|
TargetWithNestedMapOfBean target = new TargetWithNestedMapOfBean();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
String result = binder.normalizePath(wrapper, "nested[foo].foo");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertEquals("nested[foo].foo", result);
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowNewNestedMapOfBeansWithPeriod() throws Exception {
|
||||||
|
TargetWithNestedMapOfBean target = new TargetWithNestedMapOfBean();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
String result = binder.normalizePath(wrapper, "nested.foo.foo");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertEquals("nested[foo].foo", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoGrowNewNestedMapOfListOfString() throws Exception {
|
||||||
|
TargetWithNestedMapOfListOfString target = new TargetWithNestedMapOfListOfString();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
RelaxedDataBinder binder = new RelaxedDataBinder(target);
|
||||||
|
binder.normalizePath(wrapper, "nested[foo][0]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeanWrapperCreatesNewNestedMaps() throws Exception {
|
||||||
|
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
// For a nested map, you only have to get an element of it for it to be created
|
||||||
|
wrapper.getPropertyValue("nested[foo]");
|
||||||
|
// To decide what type to create for nested[foo] we need to look ahead and see
|
||||||
|
// what the user is trying to bind it to, e.g. if nested[foo][bar] then it's a map
|
||||||
|
wrapper.setPropertyValue("nested[foo]", new LinkedHashMap<String, Object>());
|
||||||
|
// But it might equally well be a collection, if nested[foo][0]
|
||||||
|
wrapper.setPropertyValue("nested[foo]", new ArrayList<Object>());
|
||||||
|
// Then it would have to be actually bound to get the list to autogrow
|
||||||
|
wrapper.setPropertyValue("nested[foo][0]", "bar");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo][0]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeanWrapperCreatesNewObjects() throws Exception {
|
||||||
|
TargetWithNestedObject target = new TargetWithNestedObject();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
// For a nested object, you have to set a property for it to be created
|
||||||
|
wrapper.setPropertyValue("nested.foo", "bar");
|
||||||
|
wrapper.getPropertyValue("nested");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBeanWrapperLists() throws Exception {
|
||||||
|
TargetWithNestedMapOfListOfString target = new TargetWithNestedMapOfListOfString();
|
||||||
|
BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
|
||||||
|
wrapper.setAutoGrowNestedPaths(true);
|
||||||
|
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor("nested");
|
||||||
|
assertTrue(descriptor.isMap());
|
||||||
|
wrapper.getPropertyValue("nested[foo]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested"));
|
||||||
|
// You also need to bind to a value here
|
||||||
|
wrapper.setPropertyValue("nested[foo][0]", "bar");
|
||||||
|
wrapper.getPropertyValue("nested[foo][0]");
|
||||||
|
assertNotNull(wrapper.getPropertyValue("nested[foo]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Work in progress")
|
||||||
|
public void testExpressionLists() throws Exception {
|
||||||
|
TargetWithNestedMapOfListOfString target = new TargetWithNestedMapOfListOfString();
|
||||||
|
LinkedHashMap<String, List<String>> map = new LinkedHashMap<String, List<String>>();
|
||||||
|
// map.put("foo", Arrays.asList("bar"));
|
||||||
|
target.setNested(map);
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
StandardEvaluationContext context = new StandardEvaluationContext(target);
|
||||||
|
context.addPropertyAccessor(new MapAccessor());
|
||||||
|
Expression expression = parser.parseExpression("nested.foo");
|
||||||
|
assertNotNull(expression.getValue(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TargetWithNestedMap {
|
||||||
|
private Map<String, Object> nested;
|
||||||
|
|
||||||
|
public Map<String, Object> getNested() {
|
||||||
|
return this.nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNested(Map<String, Object> nested) {
|
||||||
|
this.nested = nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TargetWithNestedMapOfListOfString {
|
||||||
|
private Map<String, List<String>> nested;
|
||||||
|
|
||||||
|
public Map<String, List<String>> getNested() {
|
||||||
|
return this.nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNested(Map<String, List<String>> nested) {
|
||||||
|
this.nested = nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TargetWithNestedMapOfBean {
|
||||||
|
private Map<String, VanillaTarget> nested;
|
||||||
|
|
||||||
|
public Map<String, VanillaTarget> getNested() {
|
||||||
|
return this.nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNested(Map<String, VanillaTarget> nested) {
|
||||||
|
this.nested = nested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VanillaTarget {
|
||||||
|
|
||||||
|
private String foo;
|
||||||
|
|
||||||
|
public String getFoo() {
|
||||||
|
return this.foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFoo(String foo) {
|
||||||
|
this.foo = foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,6 @@ import javax.validation.ConstraintValidatorContext;
|
|||||||
import javax.validation.Payload;
|
import javax.validation.Payload;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
@ -76,12 +75,19 @@ public class RelaxedDataBinderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBindUnderscore() throws Exception {
|
public void testBindUnderscoreInActualPropertyName() throws Exception {
|
||||||
VanillaTarget target = new VanillaTarget();
|
VanillaTarget target = new VanillaTarget();
|
||||||
bind(target, "foo-bar: bar");
|
bind(target, "foo-bar: bar");
|
||||||
assertEquals("bar", target.getFoo_bar());
|
assertEquals("bar", target.getFoo_bar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBindHyphen() throws Exception {
|
||||||
|
VanillaTarget target = new VanillaTarget();
|
||||||
|
bind(target, "foo-baz: bar");
|
||||||
|
assertEquals("bar", target.getFooBaz());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBindCamelCase() throws Exception {
|
public void testBindCamelCase() throws Exception {
|
||||||
VanillaTarget target = new VanillaTarget();
|
VanillaTarget target = new VanillaTarget();
|
||||||
@ -208,7 +214,7 @@ public class RelaxedDataBinderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Should be possible but currently not supported")
|
// @Ignore("Should be possible but currently not supported")
|
||||||
// FIXME: bind to map containing beans
|
// FIXME: bind to map containing beans
|
||||||
public void testBindNestedMapOfBean() throws Exception {
|
public void testBindNestedMapOfBean() throws Exception {
|
||||||
TargetWithNestedMapOfBean target = new TargetWithNestedMapOfBean();
|
TargetWithNestedMapOfBean target = new TargetWithNestedMapOfBean();
|
||||||
@ -218,7 +224,7 @@ public class RelaxedDataBinderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Should be possible but currently not supported")
|
// @Ignore("Should be possible but currently not supported")
|
||||||
// FIXME: bind to map containing beans
|
// FIXME: bind to map containing beans
|
||||||
public void testBindNestedMapOfListOfBean() throws Exception {
|
public void testBindNestedMapOfListOfBean() throws Exception {
|
||||||
TargetWithNestedMapOfListOfBean target = new TargetWithNestedMapOfListOfBean();
|
TargetWithNestedMapOfListOfBean target = new TargetWithNestedMapOfListOfBean();
|
||||||
@ -265,7 +271,7 @@ public class RelaxedDataBinderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBindMapNestedInMap() throws Exception {
|
public void testBindMapNestedMap() throws Exception {
|
||||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||||
BindingResult result = bind(target, "spam: bar\n" + "vanilla.foo.value: 123",
|
BindingResult result = bind(target, "spam: bar\n" + "vanilla.foo.value: 123",
|
||||||
"vanilla");
|
"vanilla");
|
||||||
|
@ -14,6 +14,6 @@
|
|||||||
<appender-ref ref="CONSOLE" />
|
<appender-ref ref="CONSOLE" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
<logger name="org.springframework" level="DEBUG"/>
|
<!-- logger name="org.springframework" level="DEBUG"/-->
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
Loading…
Reference in New Issue
Block a user