Skip to content

Commit ebb44a8

Browse files
committed
Restore support for non-EnumerablePropertySource in PropertySourcesPlaceholderConfigurer
Commit 3295289 fixed a number issues with placeholder resolution in PropertySourcesPlaceholderConfigurer. However, in doing so, it replaced a raw PropertySource with a CompositePropertySource which implements EnumerablePropertySource. Consequently, all property sources registered in the Environment must now implement EnumerablePropertySource (which is not an actual requirement). Otherwise, invocations of getPropertyNames() on the CompositePropertySource result in an IllegalStateException, and that is a breaking change which resulted in numerous build failures within the Spring portfolio. To address that regression, this commit introduces a private ConfigurableEnvironmentPropertySource in PropertySourcesPlaceholderConfigurer which is a "raw" PropertySource that delegates directly to the PropertySources in a ConfigurableEnvironment. This commit also extracts the raw PropertySource for direct Environment delegation into a new FallbackEnvironmentPropertySource. See gh-17385 Closes gh-34861
1 parent 3096bb6 commit ebb44a8

File tree

2 files changed

+133
-18
lines changed

2 files changed

+133
-18
lines changed

spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2525
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
2626
import org.springframework.context.EnvironmentAware;
27-
import org.springframework.core.env.CompositePropertySource;
2827
import org.springframework.core.env.ConfigurableEnvironment;
2928
import org.springframework.core.env.ConfigurablePropertyResolver;
3029
import org.springframework.core.env.Environment;
@@ -133,23 +132,10 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
133132
if (this.propertySources == null) {
134133
this.propertySources = new MutablePropertySources();
135134
if (this.environment != null) {
136-
PropertySource<?> environmentPropertySource;
137-
if (this.environment instanceof ConfigurableEnvironment configurableEnvironment) {
138-
environmentPropertySource = new CompositePropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME,
139-
configurableEnvironment.getPropertySources());
140-
}
141-
else {
142-
// Fallback code path that should never apply in a regular scenario, since the
143-
// Environment in the ApplicationContext should always be a ConfigurableEnvironment.
144-
environmentPropertySource =
145-
new PropertySource<>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
146-
@Override
147-
@Nullable
148-
public Object getProperty(String key) {
149-
return super.source.getProperty(key);
150-
}
151-
};
152-
}
135+
PropertySource<?> environmentPropertySource =
136+
(this.environment instanceof ConfigurableEnvironment configurableEnvironment ?
137+
new ConfigurableEnvironmentPropertySource(configurableEnvironment) :
138+
new FallbackEnvironmentPropertySource(this.environment));
153139
this.propertySources.addLast(environmentPropertySource);
154140
}
155141
try {
@@ -232,4 +218,75 @@ public PropertySources getAppliedPropertySources() throws IllegalStateException
232218
return this.appliedPropertySources;
233219
}
234220

221+
222+
/**
223+
* Custom {@link PropertySource} that delegates to the
224+
* {@link ConfigurableEnvironment#getPropertySources() PropertySources} in a
225+
* {@link ConfigurableEnvironment}.
226+
* @since 6.2.7
227+
*/
228+
private static class ConfigurableEnvironmentPropertySource extends PropertySource<ConfigurableEnvironment> {
229+
230+
private final PropertySources propertySources;
231+
232+
233+
ConfigurableEnvironmentPropertySource(ConfigurableEnvironment environment) {
234+
super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment);
235+
this.propertySources = environment.getPropertySources();
236+
}
237+
238+
239+
@Override
240+
@Nullable
241+
public Object getProperty(String name) {
242+
for (PropertySource<?> propertySource : this.propertySources) {
243+
Object candidate = propertySource.getProperty(name);
244+
if (candidate != null) {
245+
return candidate;
246+
}
247+
}
248+
return null;
249+
}
250+
251+
@Override
252+
public boolean containsProperty(String name) {
253+
for (PropertySource<?> propertySource : this.propertySources) {
254+
if (propertySource.containsProperty(name)) {
255+
return true;
256+
}
257+
}
258+
return false;
259+
}
260+
261+
@Override
262+
public String toString() {
263+
return "ConfigurableEnvironmentPropertySource {propertySources=" + this.propertySources + "}";
264+
}
265+
}
266+
267+
/**
268+
* Fallback {@link PropertySource} that delegates to a raw {@link Environment}.
269+
* <p>Should never apply in a regular scenario, since the {@code Environment}
270+
* in an {@code ApplicationContext} should always be a {@link ConfigurableEnvironment}.
271+
* @since 6.2.7
272+
*/
273+
private static class FallbackEnvironmentPropertySource extends PropertySource<Environment> {
274+
275+
FallbackEnvironmentPropertySource(Environment environment) {
276+
super(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, environment);
277+
}
278+
279+
280+
@Override
281+
@Nullable
282+
public Object getProperty(String name) {
283+
return super.source.getProperty(name);
284+
}
285+
286+
@Override
287+
public String toString() {
288+
return "FallbackEnvironmentPropertySource {environment=" + super.source + "}";
289+
}
290+
}
291+
235292
}

spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.context.support;
1818

19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
1922
import java.util.Optional;
2023
import java.util.Properties;
2124

@@ -34,6 +37,7 @@
3437
import org.springframework.context.annotation.Bean;
3538
import org.springframework.context.annotation.Configuration;
3639
import org.springframework.core.convert.support.DefaultConversionService;
40+
import org.springframework.core.env.EnumerablePropertySource;
3741
import org.springframework.core.env.MutablePropertySources;
3842
import org.springframework.core.env.PropertySource;
3943
import org.springframework.core.env.StandardEnvironment;
@@ -300,6 +304,60 @@ public Object getProperty(String key) {
300304
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("bar");
301305
}
302306

307+
@Test // gh-34861
308+
void withEnumerableAndNonEnumerablePropertySourcesInTheEnvironmentAndLocalProperties() {
309+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
310+
bf.registerBeanDefinition("testBean",
311+
genericBeanDefinition(TestBean.class)
312+
.addPropertyValue("name", "${foo:bogus}")
313+
.addPropertyValue("jedi", "${local:false}")
314+
.getBeanDefinition());
315+
316+
// 1) MockPropertySource is an EnumerablePropertySource.
317+
MockPropertySource mockPropertySource = new MockPropertySource("mockPropertySource")
318+
.withProperty("foo", "${bar}");
319+
320+
// 2) PropertySource is not an EnumerablePropertySource.
321+
PropertySource<?> rawPropertySource = new PropertySource<>("rawPropertySource", new Object()) {
322+
@Override
323+
public Object getProperty(String key) {
324+
return ("bar".equals(key) ? "quux" : null);
325+
}
326+
};
327+
328+
MockEnvironment env = new MockEnvironment();
329+
env.getPropertySources().addFirst(mockPropertySource);
330+
env.getPropertySources().addLast(rawPropertySource);
331+
332+
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
333+
ppc.setEnvironment(env);
334+
// 3) Local properties are stored in a PropertiesPropertySource which is an EnumerablePropertySource.
335+
ppc.setProperties(new Properties() {{
336+
setProperty("local", "true");
337+
}});
338+
ppc.postProcessBeanFactory(bf);
339+
340+
// Verify all properties can be resolved via the Environment.
341+
assertThat(env.getProperty("foo")).isEqualTo("quux");
342+
assertThat(env.getProperty("bar")).isEqualTo("quux");
343+
344+
// Verify that placeholder resolution works.
345+
TestBean testBean = bf.getBean(TestBean.class);
346+
assertThat(testBean.getName()).isEqualTo("quux");
347+
assertThat(testBean.isJedi()).isTrue();
348+
349+
// Verify that the presence of a non-EnumerablePropertySource does not prevent
350+
// accessing EnumerablePropertySources via getAppliedPropertySources().
351+
List<String> propertyNames = new ArrayList<>();
352+
for (PropertySource<?> propertySource : ppc.getAppliedPropertySources()) {
353+
if (propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource) {
354+
Collections.addAll(propertyNames, enumerablePropertySource.getPropertyNames());
355+
}
356+
}
357+
// Should not contain "foo" or "bar" from the Environment.
358+
assertThat(propertyNames).containsOnly("local");
359+
}
360+
303361
@Test
304362
void customPlaceholderPrefixAndSuffix() {
305363
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();

0 commit comments

Comments
 (0)