Skip to content

Commit 86d5c19

Browse files
committed
Don't mix collection values from different sources
Update PropertySourcesPropertyValues so that collection values are only added from a single PropertySource. Prior to this commit, given the following: PropertySource-A list[0]=x PropertySource-B list[0]=y list[1]=z PropertySourcesPropertyValues would take `x` from A and `z` from B, resulting in a binding of `[x,z]`. The updated code now returns the more logical `[x]`. Fixes gh-2611
1 parent 16a1bd0 commit 86d5c19

File tree

2 files changed

+77
-13
lines changed

2 files changed

+77
-13
lines changed

spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.Collections;
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import java.util.regex.Pattern;
2426

2527
import org.springframework.beans.MutablePropertyValues;
2628
import org.springframework.beans.PropertyValue;
@@ -39,17 +41,22 @@
3941
* used with the latter.
4042
*
4143
* @author Dave Syer
44+
* @author Phillip Webb
4245
*/
4346
public class PropertySourcesPropertyValues implements PropertyValues {
4447

45-
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<String, PropertyValue>();
46-
47-
private final PropertySources propertySources;
48-
4948
private static final Collection<String> PATTERN_MATCHED_PROPERTY_SOURCES = Arrays
5049
.asList(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
5150
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
5251

52+
private static final Pattern COLLECTION_PROPERTY = Pattern.compile("\\[(\\d+)\\]");
53+
54+
private final PropertySources propertySources;
55+
56+
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<String, PropertyValue>();
57+
58+
private final ConcurrentHashMap<String, PropertySource<?>> collectionOwners = new ConcurrentHashMap<String, PropertySource<?>>();
59+
5360
/**
5461
* Create a new PropertyValues from the given PropertySources
5562
* @param propertySources a PropertySources instance
@@ -122,10 +129,7 @@ private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
122129
continue;
123130
}
124131
Object value = getEnumerableProperty(source, resolver, propertyName);
125-
if (!this.propertyValues.containsKey(propertyName)) {
126-
this.propertyValues.put(propertyName, new PropertyValue(propertyName,
127-
value));
128-
}
132+
putIfAbsent(propertyName, value, source);
129133
}
130134
}
131135
}
@@ -166,9 +170,7 @@ private void processDefaultPropertySource(PropertySource<?> source,
166170
if (value == null) {
167171
value = source.getProperty(propertyName.toUpperCase());
168172
}
169-
if (value != null && !this.propertyValues.containsKey(propertyName)) {
170-
this.propertyValues.put(propertyName, new PropertyValue(propertyName,
171-
value));
173+
if (putIfAbsent(propertyName, value, source) != null) {
172174
continue;
173175
}
174176
}
@@ -188,8 +190,22 @@ public PropertyValue getPropertyValue(String propertyName) {
188190
}
189191
for (PropertySource<?> source : this.propertySources) {
190192
Object value = source.getProperty(propertyName);
191-
if (value != null) {
192-
propertyValue = new PropertyValue(propertyName, value);
193+
propertyValue = putIfAbsent(propertyName, value, source);
194+
if (propertyValue != null) {
195+
return propertyValue;
196+
}
197+
}
198+
return null;
199+
}
200+
201+
private PropertyValue putIfAbsent(String propertyName, Object value,
202+
PropertySource<?> source) {
203+
if (value != null && !this.propertyValues.containsKey(propertyName)) {
204+
PropertySource<?> collectionOwner = this.collectionOwners.putIfAbsent(
205+
COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source);
206+
if (collectionOwner == null || collectionOwner == source) {
207+
this.collectionOwners.get(this.collectionOwners);
208+
PropertyValue propertyValue = new PropertyValue(propertyName, value);
193209
this.propertyValues.put(propertyName, propertyValue);
194210
return propertyValue;
195211
}

spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package org.springframework.boot.bind;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.LinkedHashMap;
24+
import java.util.List;
2325
import java.util.Map;
2426

2527
import org.junit.Before;
@@ -31,12 +33,15 @@
3133
import org.springframework.core.env.PropertySource;
3234
import org.springframework.validation.DataBinder;
3335

36+
import static org.hamcrest.Matchers.equalTo;
3437
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertThat;
3539

3640
/**
3741
* Tests for {@link PropertySourcesPropertyValues}.
3842
*
3943
* @author Dave Syer
44+
* @author Phillip Webb
4045
*/
4146
public class PropertySourcesPropertyValuesTests {
4247

@@ -192,7 +197,35 @@ public Object getProperty(String name) {
192197
assertEquals(null, target.getName());
193198
}
194199

200+
@Test
201+
public void testCollectionProperty() throws Exception {
202+
ListBean target = new ListBean();
203+
DataBinder binder = new DataBinder(target);
204+
Map<String, Object> map = new LinkedHashMap<String, Object>();
205+
map.put("list[0]", "v0");
206+
map.put("list[1]", "v1");
207+
this.propertySources.addFirst(new MapPropertySource("values", map));
208+
binder.bind(new PropertySourcesPropertyValues(this.propertySources));
209+
assertThat(target.getList(), equalTo(Arrays.asList("v0", "v1")));
210+
}
211+
212+
@Test
213+
public void testFirstCollectionPropertyWins() throws Exception {
214+
ListBean target = new ListBean();
215+
DataBinder binder = new DataBinder(target);
216+
Map<String, Object> first = new LinkedHashMap<String, Object>();
217+
first.put("list[0]", "f0");
218+
Map<String, Object> second = new LinkedHashMap<String, Object>();
219+
second.put("list[0]", "s0");
220+
second.put("list[1]", "s1");
221+
this.propertySources.addFirst(new MapPropertySource("s", second));
222+
this.propertySources.addFirst(new MapPropertySource("f", first));
223+
binder.bind(new PropertySourcesPropertyValues(this.propertySources));
224+
assertThat(target.getList(), equalTo(Collections.singletonList("f0")));
225+
}
226+
195227
public static class TestBean {
228+
196229
private String name;
197230

198231
public String getName() {
@@ -205,6 +238,7 @@ public void setName(String name) {
205238
}
206239

207240
public static class FooBean {
241+
208242
private String foo;
209243

210244
public String getFoo() {
@@ -214,6 +248,20 @@ public String getFoo() {
214248
public void setFoo(String foo) {
215249
this.foo = foo;
216250
}
251+
252+
}
253+
254+
public static class ListBean {
255+
256+
private List<String> list = new ArrayList<String>();
257+
258+
public List<String> getList() {
259+
return this.list;
260+
}
261+
262+
public void setList(List<String> list) {
263+
this.list = list;
264+
}
217265
}
218266

219267
}

0 commit comments

Comments
 (0)