mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-10 03:19:01 +00:00
Merge branch '1.2.x'
This commit is contained in:
+1
-1
@@ -66,7 +66,7 @@ public class XADataSourceAutoConfigurationTests {
|
||||
public void createFromClass() throws Exception {
|
||||
ApplicationContext context = createContext(
|
||||
FromProperties.class,
|
||||
"spring.datasource.xa.data-source-class:org.hsqldb.jdbc.pool.JDBCXADataSource",
|
||||
"spring.datasource.xa.data-source-class-name:org.hsqldb.jdbc.pool.JDBCXADataSource",
|
||||
"spring.datasource.xa.properties.database-name:test");
|
||||
context.getBean(DataSource.class);
|
||||
MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
|
||||
|
||||
@@ -869,6 +869,10 @@ relative to the current directory.
|
||||
Log files will rotate when they reach 10 Mb and as with console output, `ERROR`, `WARN`
|
||||
and `INFO` level messages are logged by default.
|
||||
|
||||
NOTE: The logging system is initialized early in the application lifecycle and as such
|
||||
logging properties will not be found in property files loaded via `@PropertySource`
|
||||
annotations.
|
||||
|
||||
|
||||
|
||||
[[boot-features-custom-log-levels]]
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,6 +47,8 @@ import org.springframework.validation.DataBinder;
|
||||
*/
|
||||
public class RelaxedDataBinder extends DataBinder {
|
||||
|
||||
private static final Object BLANK = new Object();
|
||||
|
||||
private String namePrefix;
|
||||
|
||||
private boolean ignoreNestedProperties;
|
||||
@@ -112,11 +115,10 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
|
||||
@Override
|
||||
protected void doBind(MutablePropertyValues propertyValues) {
|
||||
propertyValues = modifyProperties(propertyValues, getTarget());
|
||||
// Harmless additional property editor comes in very handy sometimes...
|
||||
getPropertyEditorRegistry().registerCustomEditor(InetAddress.class,
|
||||
new InetAddressEditor());
|
||||
super.doBind(propertyValues);
|
||||
super.doBind(modifyProperties(propertyValues, getTarget()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,22 +131,52 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
*/
|
||||
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
|
||||
Object target) {
|
||||
|
||||
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
|
||||
|
||||
if (target instanceof MapHolder) {
|
||||
propertyValues = addMapPrefix(propertyValues);
|
||||
}
|
||||
|
||||
BeanWrapper wrapper = new BeanWrapperImpl(target);
|
||||
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
|
||||
wrapper.setAutoGrowNestedPaths(true);
|
||||
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
|
||||
List<String> sortedNames = getSortedPropertyNames(propertyValues);
|
||||
for (String name : sortedNames) {
|
||||
sortedValues.add(modifyProperty(wrapper,
|
||||
propertyValues.getPropertyValue(name)));
|
||||
}
|
||||
return new MutablePropertyValues(sortedValues);
|
||||
}
|
||||
|
||||
List<PropertyValue> list = propertyValues.getPropertyValueList();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
modifyProperty(propertyValues, wrapper, list.get(i), i);
|
||||
private List<String> getSortedPropertyNames(MutablePropertyValues propertyValues) {
|
||||
List<String> names = new LinkedList<String>();
|
||||
for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {
|
||||
names.add(propertyValue.getName());
|
||||
}
|
||||
sortPropertyNames(names);
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by name so that parent properties get processed first (e.g. 'foo.bar' before
|
||||
* 'foo.bar.spam'). Don't use Collections.sort() because the order might be
|
||||
* significant for other property names (it shouldn't be but who knows what people
|
||||
* might be relying on, e.g. HSQL has a JDBCXADataSource where "databaseName" is a
|
||||
* synonym for "url").
|
||||
* @param names the names to sort
|
||||
*/
|
||||
private void sortPropertyNames(List<String> names) {
|
||||
for (String name : new ArrayList<String>(names)) {
|
||||
int propertyIndex = names.indexOf(name);
|
||||
BeanPath path = new BeanPath(name);
|
||||
for (String prefix : path.prefixes()) {
|
||||
int prefixIndex = names.indexOf(prefix);
|
||||
if (prefixIndex >= propertyIndex) {
|
||||
// The child property has a parent in the list in the wrong order
|
||||
names.remove(name);
|
||||
names.add(prefixIndex, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return propertyValues;
|
||||
}
|
||||
|
||||
private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
|
||||
@@ -175,14 +207,13 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
return rtn;
|
||||
}
|
||||
|
||||
private void modifyProperty(MutablePropertyValues propertyValues, BeanWrapper target,
|
||||
PropertyValue propertyValue, int index) {
|
||||
String oldName = propertyValue.getName();
|
||||
String name = normalizePath(target, oldName);
|
||||
if (!name.equals(oldName)) {
|
||||
propertyValues.setPropertyValueAt(
|
||||
new PropertyValue(name, propertyValue.getValue()), index);
|
||||
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
|
||||
String name = propertyValue.getName();
|
||||
String normalizedName = normalizePath(target, name);
|
||||
if (!normalizedName.equals(name)) {
|
||||
return new PropertyValue(normalizedName, propertyValue.getValue());
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,15 +245,9 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
String name = path.prefix(index);
|
||||
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name);
|
||||
if (descriptor == null || descriptor.isMap()) {
|
||||
if (descriptor != null) {
|
||||
TypeDescriptor valueDescriptor = descriptor.getMapValueTypeDescriptor();
|
||||
if (valueDescriptor != null) {
|
||||
Class<?> valueType = valueDescriptor.getObjectType();
|
||||
if (valueType != null
|
||||
&& CharSequence.class.isAssignableFrom(valueType)) {
|
||||
path.collapseKeys(index);
|
||||
}
|
||||
}
|
||||
if (isMapValueStringType(descriptor)
|
||||
|| isBlanked(wrapper, name, path.name(index))) {
|
||||
path.collapseKeys(index);
|
||||
}
|
||||
path.mapIndex(index);
|
||||
extendMapIfNecessary(wrapper, path, index);
|
||||
@@ -231,16 +256,43 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
extendCollectionIfNecessary(wrapper, path, index);
|
||||
}
|
||||
else if (descriptor.getType().equals(Object.class)) {
|
||||
if (isBlanked(wrapper, name, path.name(index))) {
|
||||
path.collapseKeys(index);
|
||||
}
|
||||
path.mapIndex(index);
|
||||
String next = path.prefix(index + 1);
|
||||
if (wrapper.getPropertyValue(next) == null) {
|
||||
wrapper.setPropertyValue(next, new LinkedHashMap<String, Object>());
|
||||
if (path.isLastNode(index)) {
|
||||
wrapper.setPropertyValue(path.toString(), BLANK);
|
||||
}
|
||||
else {
|
||||
String next = path.prefix(index + 1);
|
||||
if (wrapper.getPropertyValue(next) == null) {
|
||||
wrapper.setPropertyValue(next, new LinkedHashMap<String, Object>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initializePath(wrapper, path, index);
|
||||
}
|
||||
|
||||
private boolean isMapValueStringType(TypeDescriptor descriptor) {
|
||||
if (descriptor == null || descriptor.getMapValueTypeDescriptor() == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> valueType = descriptor.getMapValueTypeDescriptor().getObjectType();
|
||||
return (valueType != null && CharSequence.class.isAssignableFrom(valueType));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
|
||||
Object value = (wrapper.isReadableProperty(propertyName) ? wrapper
|
||||
.getPropertyValue(propertyName) : null);
|
||||
if (value instanceof Map) {
|
||||
if (((Map) value).get(key) == BLANK) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
|
||||
String name = path.prefix(index);
|
||||
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name)
|
||||
@@ -282,6 +334,9 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
if (descriptor.isCollection()) {
|
||||
extend = new ArrayList<Object>();
|
||||
}
|
||||
if (descriptor.getType().equals(Object.class) && path.isLastNode(index)) {
|
||||
extend = BLANK;
|
||||
}
|
||||
wrapper.setPropertyValue(extensionName, extend);
|
||||
}
|
||||
|
||||
@@ -382,6 +437,18 @@ public class RelaxedDataBinder extends DataBinder {
|
||||
this.nodes = splitPath(path);
|
||||
}
|
||||
|
||||
public List<String> prefixes() {
|
||||
List<String> prefixes = new ArrayList<String>();
|
||||
for (int index = 1; index < this.nodes.size(); index++) {
|
||||
prefixes.add(prefix(index));
|
||||
}
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
public boolean isLastNode(int index) {
|
||||
return index >= this.nodes.size() - 1;
|
||||
}
|
||||
|
||||
private List<PathNode> splitPath(String path) {
|
||||
List<PathNode> nodes = new ArrayList<PathNode>();
|
||||
for (String name : StringUtils.delimitedListToStringArray(path, ".")) {
|
||||
|
||||
@@ -421,6 +421,48 @@ public class RelaxedDataBinderTests {
|
||||
assertEquals("123", target.get("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindMapWithClashInProperties() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
BindingResult result = bind(target, "vanilla.spam: bar\n"
|
||||
+ "vanilla.spam.value: 123", "vanilla");
|
||||
assertEquals(0, result.getErrorCount());
|
||||
assertEquals(2, target.size());
|
||||
assertEquals("bar", target.get("spam"));
|
||||
assertEquals("123", target.get("spam.value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindMapWithDeepClashInProperties() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
BindingResult result = bind(target, "vanilla.spam.foo: bar\n"
|
||||
+ "vanilla.spam.foo.value: 123", "vanilla");
|
||||
assertEquals(0, result.getErrorCount());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) target.get("spam");
|
||||
assertEquals("123", map.get("foo.value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindMapWithDifferentDeepClashInProperties() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
BindingResult result = bind(target, "vanilla.spam.bar: bar\n"
|
||||
+ "vanilla.spam.bar.value: 123", "vanilla");
|
||||
assertEquals(0, result.getErrorCount());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) target.get("spam");
|
||||
assertEquals("123", map.get("bar.value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindShallowMap() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
BindingResult result = bind(target, "vanilla.spam: bar\n" + "vanilla.value: 123",
|
||||
"vanilla");
|
||||
assertEquals(0, result.getErrorCount());
|
||||
assertEquals("123", target.get("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindMapNestedMap() throws Exception {
|
||||
Map<String, Object> target = new LinkedHashMap<String, Object>();
|
||||
|
||||
Reference in New Issue
Block a user