Skip to content

Commit 00430ac

Browse files
committed
Ignore unbound env variables and system props
Closes gh-3832
1 parent b58923a commit 00430ac

12 files changed

+201
-3
lines changed

spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
4444
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
4545
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
46+
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
4647
import org.springframework.boot.validation.MessageInterpolatorFactory;
4748
import org.springframework.context.ApplicationContext;
4849
import org.springframework.context.ApplicationContextAware;
@@ -420,7 +421,8 @@ private BindHandler getBindHandler(ConfigurationProperties annotation,
420421
handler = new IgnoreErrorsBindHandler(handler);
421422
}
422423
if (!annotation.ignoreUnknownFields()) {
423-
handler = new NoUnboundElementsBindHandler(handler);
424+
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
425+
handler = new NoUnboundElementsBindHandler(handler, filter);
424426
}
425427
if (validator != null) {
426428
handler = new ValidationBindHandler(handler, validator);

spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.HashSet;
2020
import java.util.Set;
2121
import java.util.TreeSet;
22+
import java.util.function.Function;
2223

2324
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
2425
import org.springframework.boot.context.properties.bind.BindContext;
@@ -42,12 +43,19 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler {
4243

4344
private final Set<ConfigurationPropertyName> boundNames = new HashSet<>();
4445

46+
private final Function<ConfigurationPropertySource, Boolean> filter;
47+
4548
NoUnboundElementsBindHandler() {
46-
super();
49+
this(BindHandler.DEFAULT, (configurationPropertySource) -> true);
4750
}
4851

4952
public NoUnboundElementsBindHandler(BindHandler parent) {
53+
this(parent, (configurationPropertySource) -> true);
54+
}
55+
56+
public NoUnboundElementsBindHandler(BindHandler parent, Function<ConfigurationPropertySource, Boolean> filter) {
5057
super(parent);
58+
this.filter = filter;
5159
}
5260

5361
@Override
@@ -69,7 +77,7 @@ private void checkNoUnboundElements(ConfigurationPropertyName name,
6977
BindContext context) {
7078
Set<ConfigurationProperty> unbound = new TreeSet<>();
7179
for (ConfigurationPropertySource source : context.getSources()) {
72-
if (source instanceof IterableConfigurationPropertySource) {
80+
if (this.filter.apply(source)) {
7381
collectUnbound(name, unbound,
7482
(IterableConfigurationPropertySource) source);
7583
}

spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public ConfigurationPropertyState containsDescendantOf(
6868
return ConfigurationPropertyState.ABSENT;
6969
}
7070

71+
@Override
72+
public Object getUnderlyingSource() {
73+
return this.source.getUnderlyingSource();
74+
}
75+
7176
protected ConfigurationPropertySource getSource() {
7277
return this.source;
7378
}

spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,10 @@ default ConfigurationPropertySource withAliases(
7272
return new AliasedConfigurationPropertySource(this, aliases);
7373
}
7474

75+
/**
76+
* Return the underlying {@PropertySource}.
77+
* @return the underlying property source.
78+
*/
79+
Object getUnderlyingSource();
80+
7581
}

spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ public ConfigurationPropertyState containsDescendantOf(
5858
return result;
5959
}
6060

61+
@Override
62+
public Object getUnderlyingSource() {
63+
return this.source.getUnderlyingSource();
64+
}
65+
6166
protected ConfigurationPropertySource getSource() {
6267
return this.source;
6368
}

spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ public void put(Object name, Object value) {
7878
this.source.put((name == null ? null : name.toString()), value);
7979
}
8080

81+
@Override
82+
public Object getUnderlyingSource() {
83+
return this.source;
84+
}
85+
8186
@Override
8287
public ConfigurationProperty getConfigurationProperty(
8388
ConfigurationPropertyName name) {

spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public ConfigurationPropertyState containsDescendantOf(
9494
return this.containsDescendantOfMethod.apply(name);
9595
}
9696

97+
@Override
98+
public Object getUnderlyingSource() {
99+
return this.propertySource;
100+
}
101+
97102
protected final ConfigurationProperty find(List<PropertyMapping> mappings,
98103
ConfigurationPropertyName name) {
99104
return mappings.stream().filter((m) -> m.isApplicable(name)).map(this::find)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.properties.source;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
import java.util.function.Function;
24+
25+
import org.springframework.core.env.PropertySource;
26+
import org.springframework.core.env.StandardEnvironment;
27+
28+
/**
29+
* Function used to determine if a {@link ConfigurationPropertySource} should be
30+
* included when determining unbound elements. If the underlying {@link PropertySource}
31+
* is a systemEnvironment or systemProperties property source , it will not be considered
32+
* for unbound element failures.
33+
*
34+
* @author Madhura Bhave
35+
* @since 2.0.0
36+
*/
37+
public class UnboundElementsSourceFilter implements Function<ConfigurationPropertySource, Boolean> {
38+
39+
private static final Set<String> BENIGN_PROPERTY_SOURCE_NAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
40+
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
41+
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)));
42+
43+
@Override
44+
public Boolean apply(ConfigurationPropertySource configurationPropertySource) {
45+
Object underlyingSource = configurationPropertySource.getUnderlyingSource();
46+
if (underlyingSource instanceof PropertySource) {
47+
String name = ((PropertySource) underlyingSource).getName();
48+
return !BENIGN_PROPERTY_SOURCE_NAMES.contains(name);
49+
50+
}
51+
return true;
52+
}
53+
54+
}
55+

spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,21 @@ public void overridingPropertiesWithPlaceholderResolutionInEnvShouldOverride()
419419
assertThat(foo).isEqualTo(10);
420420
}
421421

422+
@Test
423+
public void unboundElementsFromSystemEnvironmentShouldNotThrowException() throws Exception {
424+
this.context = new AnnotationConfigApplicationContext();
425+
ConfigurableEnvironment env = this.context.getEnvironment();
426+
MutablePropertySources propertySources = env.getPropertySources();
427+
propertySources.addFirst(new MapPropertySource("test",
428+
Collections.singletonMap("com.example.foo", 5)));
429+
propertySources.addLast(new SystemEnvironmentPropertySource("system",
430+
Collections.singletonMap("COM_EXAMPLE_OTHER", "10")));
431+
this.context.register(TestConfiguration.class);
432+
this.context.refresh();
433+
int foo = this.context.getBean(TestConfiguration.class).getFoo();
434+
assertThat(foo).isEqualTo(5);
435+
}
436+
422437
@Test
423438
public void rebindableConfigurationProperties() throws Exception {
424439
// gh-9160

spring-boot/src/test/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandlerTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.junit.rules.ExpectedException;
2525

2626
import org.springframework.boot.context.properties.bind.BindException;
27+
import org.springframework.boot.context.properties.bind.BindHandler;
2728
import org.springframework.boot.context.properties.bind.Bindable;
2829
import org.springframework.boot.context.properties.bind.Binder;
2930
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
@@ -103,6 +104,20 @@ public void bindWhenUsingNoUnboundElementsHandlerShouldBindIfPrefixDifferent()
103104
assertThat(bound.getFoo()).isEqualTo("bar");
104105
}
105106

107+
@Test
108+
public void bindWhenUsingNoUnboundElementsHandlerShouldBindIfUnboundSystemProperties()
109+
throws Exception {
110+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
111+
source.put("example.foo", "bar");
112+
source.put("example.other", "baz");
113+
this.sources.add(source);
114+
this.binder = new Binder(this.sources);
115+
NoUnboundElementsBindHandler handler = new NoUnboundElementsBindHandler(BindHandler.DEFAULT, (configurationPropertySource -> false));
116+
Example bound = this.binder.bind("example", Bindable.of(Example.class),
117+
handler).get();
118+
assertThat(bound.getFoo()).isEqualTo("bar");
119+
}
120+
106121
public static class Example {
107122

108123
private String foo;

spring-boot/src/test/java/org/springframework/boot/context/properties/source/MockConfigurationPropertySource.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ public Stream<ConfigurationPropertyName> stream() {
7575
return this.map.keySet().stream();
7676
}
7777

78+
@Override
79+
public Object getUnderlyingSource() {
80+
return this.map;
81+
}
82+
7883
@Override
7984
public ConfigurationProperty getConfigurationProperty(
8085
ConfigurationPropertyName name) {
@@ -91,6 +96,11 @@ private OriginTrackedValue findValue(ConfigurationPropertyName name) {
9196

9297
private class NonIterable implements ConfigurationPropertySource {
9398

99+
@Override
100+
public Object getUnderlyingSource() {
101+
return MockConfigurationPropertySource.this.map;
102+
}
103+
94104
@Override
95105
public ConfigurationProperty getConfigurationProperty(
96106
ConfigurationPropertyName name) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.properties.source;
18+
19+
import org.junit.Before;
20+
import org.junit.Test;
21+
22+
import org.springframework.core.env.StandardEnvironment;
23+
import org.springframework.mock.env.MockPropertySource;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.mockito.BDDMockito.given;
27+
import static org.mockito.Mockito.mock;
28+
29+
/**
30+
* Tests for {@link UnboundElementsSourceFilter}.
31+
*
32+
* @author Madhura Bhave
33+
*/
34+
public class UnboundElementsSourceFilterTests {
35+
36+
private UnboundElementsSourceFilter filter;
37+
38+
private ConfigurationPropertySource source;
39+
40+
@Before
41+
public void setUp() throws Exception {
42+
this.filter = new UnboundElementsSourceFilter();
43+
this.source = mock(ConfigurationPropertySource.class);
44+
}
45+
46+
@Test
47+
public void filterWhenSourceIsSystemEnvironmentPropertySourceShouldReturnFalse() throws Exception {
48+
MockPropertySource propertySource = new MockPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
49+
given(this.source.getUnderlyingSource()).willReturn(propertySource);
50+
assertThat(this.filter.apply(this.source)).isFalse();
51+
}
52+
53+
@Test
54+
public void filterWhenSourceIsSystemPropertiesPropertySourceShouldReturnTrue() throws Exception {
55+
MockPropertySource propertySource = new MockPropertySource(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
56+
given(this.source.getUnderlyingSource()).willReturn(propertySource);
57+
assertThat(this.filter.apply(this.source)).isFalse();
58+
}
59+
60+
@Test
61+
public void filterWhenSourceIsNotSystemShouldReturnTrue() throws Exception {
62+
MockPropertySource propertySource = new MockPropertySource("test");
63+
given(this.source.getUnderlyingSource()).willReturn(propertySource);
64+
assertThat(this.filter.apply(this.source)).isTrue();
65+
}
66+
67+
}

0 commit comments

Comments
 (0)