Ignore unbound collection properties if collection bound

As of Spring Boot 2.0, if Collection properties are specified in
multiple property sources, only the elements from the property source
with the highest precedence are used for binding. This caused an
`UnboundConfigurationPropertiesException` if the size of the collection
from the higher order property source was smaller and `ignoreUnknownFields`
was set to true.

This commit ignores unbound collection properties if the
collection was properly bound.

Fixes gh-16290
pull/16308/head
Madhura Bhave 6 years ago
parent 0c2e71cd08
commit 91c1fc3d97

@ -106,7 +106,23 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler {
private boolean isUnbound(ConfigurationPropertyName name, private boolean isUnbound(ConfigurationPropertyName name,
ConfigurationPropertyName candidate) { ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) && !this.boundNames.contains(candidate); if (name.isAncestorOf(candidate)) {
if (!this.boundNames.contains(candidate)
&& !isOverriddenCollectionElement(candidate)) {
return true;
}
}
return false;
}
private boolean isOverriddenCollectionElement(ConfigurationPropertyName candidate) {
int length = candidate.getNumberOfElements();
if (candidate.isNumericIndex(length - 1)) {
ConfigurationPropertyName propertyName = candidate
.chop(candidate.getNumberOfElements() - 1);
return this.boundNames.contains(propertyName);
}
return false;
} }
} }

@ -108,6 +108,35 @@ public class NoUnboundElementsBindHandlerTests {
assertThat(bound.getFoo()).isEqualTo("bar"); assertThat(bound.getFoo()).isEqualTo("bar");
} }
@Test
public void bindWhenUsingNoUnboundElementsHandlerShouldBindIfUnboundCollectionProperties() {
MockConfigurationPropertySource source1 = new MockConfigurationPropertySource();
source1.put("example.foo[0]", "bar");
MockConfigurationPropertySource source2 = new MockConfigurationPropertySource();
source2.put("example.foo[0]", "bar");
source2.put("example.foo[1]", "baz");
this.sources.add(source1);
this.sources.add(source2);
this.binder = new Binder(this.sources);
NoUnboundElementsBindHandler handler = new NoUnboundElementsBindHandler();
ExampleWithList bound = this.binder
.bind("example", Bindable.of(ExampleWithList.class), handler).get();
assertThat(bound.getFoo()).containsExactly("bar");
}
@Test
public void bindWhenUsingNoUnboundElementsHandlerAndUnboundListElementsShouldThrowException() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("example.foo[0]", "bar");
this.sources.add(source);
this.binder = new Binder(this.sources);
assertThatExceptionOfType(BindException.class)
.isThrownBy(() -> this.binder.bind("example", Bindable.of(Example.class),
new NoUnboundElementsBindHandler()))
.satisfies((ex) -> assertThat(ex.getCause().getMessage())
.contains("The elements [example.foo[0]] were left unbound"));
}
public static class Example { public static class Example {
private String foo; private String foo;
@ -122,4 +151,18 @@ public class NoUnboundElementsBindHandlerTests {
} }
public static class ExampleWithList {
private List<String> foo;
public List<String> getFoo() {
return this.foo;
}
public void setFoo(List<String> foo) {
this.foo = foo;
}
}
} }

Loading…
Cancel
Save