Merge branch '3.5.x'

Fixes gh-46197
This commit is contained in:
Phillip Webb 2025-06-24 11:36:09 -07:00
commit fe049e6570
2 changed files with 43 additions and 7 deletions

View File

@ -107,7 +107,6 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
AggregateElementBinder elementBinder, IndexedCollectionSupplier collection, ResolvableType elementType) {
Set<String> knownIndexedChildren = Collections.emptySet();
if (source instanceof IterableConfigurationPropertySource iterableSource) {
source = iterableSource.filter(root::isAncestorOf);
knownIndexedChildren = getKnownIndexedChildren(iterableSource, root);
}
for (int i = 0; i < Integer.MAX_VALUE; i++) {
@ -124,10 +123,10 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
}
}
private Set<String> getKnownIndexedChildren(IterableConfigurationPropertySource filteredSource,
private Set<String> getKnownIndexedChildren(IterableConfigurationPropertySource source,
ConfigurationPropertyName root) {
Set<String> knownIndexedChildren = new HashSet<>();
for (ConfigurationPropertyName name : filteredSource) {
for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
ConfigurationPropertyName choppedName = name.chop(root.getNumberOfElements() + 1);
if (choppedName.isLastElementIndexed()) {
knownIndexedChildren.add(choppedName.getLastElement(Form.UNIFORM));
@ -136,17 +135,17 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
return knownIndexedChildren;
}
private void assertNoUnboundChildren(Set<String> unboundIndexedChildren,
IterableConfigurationPropertySource filteredSource, ConfigurationPropertyName root) {
private void assertNoUnboundChildren(Set<String> unboundIndexedChildren, IterableConfigurationPropertySource source,
ConfigurationPropertyName root) {
if (unboundIndexedChildren.isEmpty()) {
return;
}
Set<ConfigurationProperty> unboundProperties = new TreeSet<>();
for (ConfigurationPropertyName name : filteredSource) {
for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
ConfigurationPropertyName choppedName = name.chop(root.getNumberOfElements() + 1);
if (choppedName.isLastElementIndexed()
&& unboundIndexedChildren.contains(choppedName.getLastElement(Form.UNIFORM))) {
unboundProperties.add(filteredSource.getConfigurationProperty(name));
unboundProperties.add(source.getConfigurationProperty(name));
}
}
if (!unboundProperties.isEmpty()) {

View File

@ -19,9 +19,11 @@ package org.springframework.boot.context.properties.bind;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
@ -33,6 +35,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -465,6 +468,36 @@ class CollectionBinderTests {
assertThat(result.getValues().get(0)).containsExactly(ExampleEnum.FOO_BAR, ExampleEnum.BAR_BAZ);
}
@Test
void bindToWellFormedSystemEnvironmentVariableProperty() {
// gh-46184
Map<String, Object> map = new LinkedHashMap<>();
map.put("FOO_THENAMES_0_FIRST", "spring");
map.put("FOO_THENAMES_0_LAST", "boot");
map.put("FOO_THENAMES_1_FIRST", "binding");
map.put("FOO_THENAMES_1_LAST", "test");
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map);
this.sources.add(ConfigurationPropertySource.from(propertySource));
BeanWithCamelCaseNameList result = this.binder.bind("foo", BeanWithCamelCaseNameList.class).get();
assertThat(result.theNames()).containsExactly(new Name("spring", "boot"), new Name("binding", "test"));
}
@Test
void bindToLegacySystemEnvironmentVariableProperty() {
// gh-46184
Map<String, Object> map = new LinkedHashMap<>();
map.put("FOO_THE_NAMES_0_FIRST", "spring");
map.put("FOO_THE_NAMES_0_LAST", "boot");
map.put("FOO_THE_NAMES_1_FIRST", "binding");
map.put("FOO_THE_NAMES_1_LAST", "test");
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map);
this.sources.add(ConfigurationPropertySource.from(propertySource));
BeanWithCamelCaseNameList result = this.binder.bind("foo", BeanWithCamelCaseNameList.class).get();
assertThat(result.theNames()).containsExactly(new Name("spring", "boot"), new Name("binding", "test"));
}
static class ExampleCollectionBean {
private final List<String> items = new ArrayList<>();
@ -607,6 +640,10 @@ class CollectionBinderTests {
}
record BeanWithCamelCaseNameList(List<Name> theNames) {
}
record Name(String first, String last) {
}