Skip to content

Commit 3d46b06

Browse files
committed
Fix config data placeholder resolution active document logic
Update `ConfigDataEnvironmentContributor.isActive` so that unbound imports are no longer considered active. Prior to this commit, any `ConfigDataEnvironmentContributor` that had `null` properties was considered active. This is incorrect for `Kind.UNBOUND_IMPORT` contributors since we haven't yet bound the `spring.config.*` properties. The `ConfigDataEnvironmentContributorPlaceholdersResolver` has been updated to handle the refined logic. A placeholder can now be resolved from the current contributor, or from an unbound contributor by binding it on the fly. Fixes gh-29386
1 parent 1c6471e commit 3d46b06

12 files changed

+81
-33
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -286,7 +286,7 @@ private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributo
286286
private Collection<? extends String> getIncludedProfiles(ConfigDataEnvironmentContributors contributors,
287287
ConfigDataActivationContext activationContext) {
288288
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
289-
contributors, activationContext, true);
289+
contributors, activationContext, null, true);
290290
Set<String> result = new LinkedHashSet<>();
291291
for (ConfigDataEnvironmentContributor contributor : contributors) {
292292
ConfigurationPropertySource source = contributor.getConfigurationPropertySource();

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import java.util.stream.StreamSupport;
2828

2929
import org.springframework.boot.context.properties.bind.Binder;
30+
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
3031
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
3132
import org.springframework.core.env.Environment;
3233
import org.springframework.core.env.PropertySource;
@@ -120,6 +121,9 @@ ConfigDataLocation getLocation() {
120121
* @return if the contributor is active
121122
*/
122123
boolean isActive(ConfigDataActivationContext activationContext) {
124+
if (this.kind == Kind.UNBOUND_IMPORT) {
125+
return false;
126+
}
123127
return this.properties == null || this.properties.isActive(activationContext);
124128
}
125129

@@ -223,10 +227,16 @@ public Iterator<ConfigDataEnvironmentContributor> iterator() {
223227
/**
224228
* Create an new {@link ConfigDataEnvironmentContributor} with bound
225229
* {@link ConfigDataProperties}.
226-
* @param binder the binder to use
230+
* @param contributors the contributors used for binding
231+
* @param activationContext the activation context
227232
* @return a new contributor instance
228233
*/
229-
ConfigDataEnvironmentContributor withBoundProperties(Binder binder) {
234+
ConfigDataEnvironmentContributor withBoundProperties(Iterable<ConfigDataEnvironmentContributor> contributors,
235+
ConfigDataActivationContext activationContext) {
236+
Iterable<ConfigurationPropertySource> sources = Collections.singleton(getConfigurationPropertySource());
237+
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
238+
contributors, activationContext, this, true);
239+
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
230240
UseLegacyConfigProcessingException.throwIfRequested(binder);
231241
ConfigDataProperties properties = ConfigDataProperties.get(binder);
232242
if (properties != null && this.configDataOptions.contains(ConfigData.Option.IGNORE_IMPORTS)) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.context.config;
1818

19+
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
1920
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
2021
import org.springframework.boot.origin.Origin;
2122
import org.springframework.boot.origin.OriginLookup;
@@ -40,10 +41,14 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde
4041

4142
private final PropertyPlaceholderHelper helper;
4243

44+
private final ConfigDataEnvironmentContributor activeContributor;
45+
4346
ConfigDataEnvironmentContributorPlaceholdersResolver(Iterable<ConfigDataEnvironmentContributor> contributors,
44-
ConfigDataActivationContext activationContext, boolean failOnResolveFromInactiveContributor) {
47+
ConfigDataActivationContext activationContext, ConfigDataEnvironmentContributor activeContributor,
48+
boolean failOnResolveFromInactiveContributor) {
4549
this.contributors = contributors;
4650
this.activationContext = activationContext;
51+
this.activeContributor = activeContributor;
4752
this.failOnResolveFromInactiveContributor = failOnResolveFromInactiveContributor;
4853
this.helper = new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
4954
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
@@ -62,7 +67,7 @@ private String resolvePlaceholder(String placeholder) {
6267
for (ConfigDataEnvironmentContributor contributor : this.contributors) {
6368
PropertySource<?> propertySource = contributor.getPropertySource();
6469
Object value = (propertySource != null) ? propertySource.getProperty(placeholder) : null;
65-
if (value != null && !contributor.isActive(this.activationContext)) {
70+
if (value != null && !isActive(contributor)) {
6671
if (this.failOnResolveFromInactiveContributor) {
6772
ConfigDataResource resource = contributor.getResource();
6873
Origin origin = OriginLookup.getOrigin(propertySource, placeholder);
@@ -75,4 +80,15 @@ private String resolvePlaceholder(String placeholder) {
7580
return (result != null) ? String.valueOf(result) : null;
7681
}
7782

83+
private boolean isActive(ConfigDataEnvironmentContributor contributor) {
84+
if (contributor == this.activeContributor) {
85+
return true;
86+
}
87+
if (contributor.getKind() != Kind.UNBOUND_IMPORT) {
88+
return contributor.isActive(this.activationContext);
89+
}
90+
return contributor.withBoundProperties(this.contributors, this.activationContext)
91+
.isActive(this.activationContext);
92+
}
93+
7894
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -103,12 +103,7 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
103103
return result;
104104
}
105105
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
106-
Iterable<ConfigurationPropertySource> sources = Collections
107-
.singleton(contributor.getConfigurationPropertySource());
108-
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
109-
result, activationContext, true);
110-
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
111-
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
106+
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
112107
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
113108
result.getRoot().withReplacement(contributor, bound));
114109
continue;
@@ -220,7 +215,7 @@ private Binder getBinder(ConfigDataActivationContext activationContext,
220215
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(
221216
filter.and((contributor) -> failOnInactiveSource || contributor.isActive(activationContext)));
222217
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
223-
activationContext, failOnInactiveSource);
218+
activationContext, null, failOnInactiveSource);
224219
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);
225220
return new Binder(sources, placeholdersResolver, null, null, bindHandler);
226221
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,14 +44,14 @@ class ConfigDataEnvironmentContributorPlaceholdersResolverTests {
4444
@Test
4545
void resolvePlaceholdersWhenNotStringReturnsResolved() {
4646
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
47-
Collections.emptyList(), null, false);
47+
Collections.emptyList(), null, null, false);
4848
assertThat(resolver.resolvePlaceholders(123)).isEqualTo(123);
4949
}
5050

5151
@Test
5252
void resolvePlaceholdersWhenNotFoundReturnsOriginal() {
5353
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
54-
Collections.emptyList(), null, false);
54+
Collections.emptyList(), null, null, false);
5555
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("${test}");
5656
}
5757

@@ -62,7 +62,7 @@ void resolvePlaceholdersWhenFoundReturnsFirstMatch() {
6262
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
6363
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), true));
6464
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
65-
contributors, null, true);
65+
contributors, null, null, true);
6666
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("t2");
6767
}
6868

@@ -73,7 +73,7 @@ void resolvePlaceholdersWhenFoundInInactiveThrowsException() {
7373
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
7474
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), false));
7575
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
76-
contributors, null, true);
76+
contributors, null, null, true);
7777
assertThatExceptionOfType(InactiveConfigDataAccessException.class)
7878
.isThrownBy(() -> resolver.resolvePlaceholders("${test}"))
7979
.satisfies(propertyNameAndOriginOf("test", "s3"));
@@ -86,7 +86,7 @@ void resolvePlaceholderWhenFoundInInactiveAndIgnoringReturnsResolved() {
8686
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
8787
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), false));
8888
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
89-
contributors, null, false);
89+
contributors, null, null, false);
9090
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("t2");
9191
}
9292

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
3030
import org.springframework.boot.context.config.ConfigData.PropertySourceOptions;
3131
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
3232
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
33-
import org.springframework.boot.context.properties.bind.Binder;
3433
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
3534
import org.springframework.mock.env.MockPropertySource;
3635

@@ -316,7 +315,7 @@ void ofUnboundImportCreatesImportedContributor() {
316315
assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT);
317316
assertThat(contributor.getResource()).isSameAs(resource);
318317
assertThat(contributor.getImports()).isEmpty();
319-
assertThat(contributor.isActive(this.activationContext)).isTrue();
318+
assertThat(contributor.isActive(this.activationContext)).isFalse();
320319
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
321320
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
322321
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
@@ -368,8 +367,8 @@ void withBoundPropertiesWhenIgnoringImportsAndNothingBound() {
368367
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()), Option.IGNORE_IMPORTS);
369368
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(TEST_LOCATION,
370369
resource, false, configData, 0);
371-
Binder binder = new Binder(contributor.getConfigurationPropertySource());
372-
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
370+
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(Collections.singleton(contributor),
371+
null);
373372
assertThat(bound).isNotNull();
374373
}
375374

@@ -382,8 +381,7 @@ private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataResour
382381
int propertySourceIndex) {
383382
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(TEST_LOCATION,
384383
resource, false, configData, propertySourceIndex);
385-
Binder binder = new Binder(contributor.getConfigurationPropertySource());
386-
return contributor.withBoundProperties(binder);
384+
return contributor.withBoundProperties(Collections.singleton(contributor), null);
387385
}
388386

389387
private List<String> asLocationsList(Iterator<ConfigDataEnvironmentContributor> iterator) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.context.config;
1818

1919
import java.util.Arrays;
20+
import java.util.Collections;
2021
import java.util.Iterator;
2122
import java.util.LinkedHashMap;
2223
import java.util.List;
@@ -389,8 +390,7 @@ private ConfigDataEnvironmentContributor createBoundImportContributor(ConfigData
389390
int propertySourceIndex) {
390391
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null, null,
391392
false, configData, propertySourceIndex);
392-
Binder binder = new Binder(contributor.getConfigurationPropertySource());
393-
return contributor.withBoundProperties(binder);
393+
return contributor.withBoundProperties(Collections.singleton(contributor), null);
394394
}
395395

396396
private static class TestConfigDataResource extends ConfigDataResource {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -636,6 +636,21 @@ void runWhenHasPropertyInProfileDocumentThrowsException() {
636636
.withCauseInstanceOf(InactiveConfigDataAccessException.class);
637637
}
638638

639+
@Test // gh-29386
640+
void runWhenHasPropertyInEarlierProfileDocumentThrowsException() {
641+
assertThatExceptionOfType(BindException.class).isThrownBy(() -> this.application.run(
642+
"--spring.config.location=classpath:application-import-with-placeholder-in-earlier-profile-document.properties"))
643+
.withCauseInstanceOf(InactiveConfigDataAccessException.class);
644+
}
645+
646+
@Test // gh-29386
647+
void runWhenHasPropertyInEarlierDocumentLoads() {
648+
ConfigurableApplicationContext context = this.application.run(
649+
"--spring.config.location=classpath:application-import-with-placeholder-in-earlier-document.properties");
650+
assertThat(context.getEnvironment().getProperty("my.value"))
651+
.isEqualTo("application-import-with-placeholder-in-earlier-document-imported");
652+
}
653+
639654
@Test
640655
void runWhenHasNonOptionalImportThrowsException() {
641656
assertThatExceptionOfType(ConfigDataResourceNotFoundException.class).isThrownBy(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
my.value=application-import-with-placeholder-in-earlier-document-imported
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
my.import=application-import-with-placeholder-in-earlier-document-imported
2+
#---
3+
my.value=should-be-ignored
4+
spring.config.activate.on-profile=missing
5+
#---
6+
spring.config.import=classpath:${my.import}.properties

0 commit comments

Comments
 (0)