Skip to content

Commit 0da0d2d

Browse files
committed
Prevent nested profile-specific resolution
Update the `ConfigDataEnvironment` so that the `resolveProfileSpecific` method of `ConfigDataLocationResolver` is no longer called when resolving imports declared in a profile-specific file. Fixes gh-26753
1 parent d1b256a commit 0da0d2d

10 files changed

+118
-48
lines changed

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,18 @@ private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
328328
checkForInvalidProperties(contributors);
329329
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
330330
MutablePropertySources propertySources = this.environment.getPropertySources();
331+
applyContributor(contributors, activationContext, propertySources);
332+
DefaultPropertiesPropertySource.moveToEnd(propertySources);
333+
Profiles profiles = activationContext.getProfiles();
334+
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
335+
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
336+
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
337+
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
338+
this.environmentUpdateListener.onSetProfiles(profiles);
339+
}
340+
341+
private void applyContributor(ConfigDataEnvironmentContributors contributors,
342+
ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
331343
this.logger.trace("Applying config data environment contributions");
332344
for (ConfigDataEnvironmentContributor contributor : contributors) {
333345
PropertySource<?> propertySource = contributor.getPropertySource();
@@ -345,13 +357,6 @@ private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
345357
}
346358
}
347359
}
348-
DefaultPropertiesPropertySource.moveToEnd(propertySources);
349-
Profiles profiles = activationContext.getProfiles();
350-
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
351-
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
352-
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
353-
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
354-
this.environmentUpdateListener.onSetProfiles(profiles);
355360
}
356361

357362
private void checkForInvalidProperties(ConfigDataEnvironmentContributors contributors) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
117117
result, contributor, activationContext);
118118
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
119119
List<ConfigDataLocation> imports = contributor.getImports();
120+
boolean resolveProfileSpecific = !contributor.isFromProfileSpecificImport();
120121
this.logger.trace(LogMessage.format("Processing imports %s", imports));
121122
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
122-
locationResolverContext, loaderContext, imports);
123+
locationResolverContext, loaderContext, imports, resolveProfileSpecific);
123124
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
124125
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
125126
asContributors(imported));

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,16 @@ class ConfigDataImporter {
7575
* @param locationResolverContext the location resolver context
7676
* @param loaderContext the loader context
7777
* @param locations the locations to resolve
78+
* @param resolveProfileSpecific if profile specific resolution should be attempted
7879
* @return a map of the loaded locations and data
7980
*/
8081
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
8182
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
82-
List<ConfigDataLocation> locations) {
83+
List<ConfigDataLocation> locations, boolean resolveProfileSpecific) {
8384
try {
8485
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
85-
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
86+
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations,
87+
resolveProfileSpecific);
8688
return load(loaderContext, resolved);
8789
}
8890
catch (IOException ex) {
@@ -91,18 +93,18 @@ Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationC
9193
}
9294

9395
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
94-
Profiles profiles, List<ConfigDataLocation> locations) {
96+
Profiles profiles, List<ConfigDataLocation> locations, boolean resolveProfileSpecific) {
9597
List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
9698
for (ConfigDataLocation location : locations) {
97-
resolved.addAll(resolve(locationResolverContext, profiles, location));
99+
resolved.addAll(resolve(locationResolverContext, profiles, location, resolveProfileSpecific));
98100
}
99101
return Collections.unmodifiableList(resolved);
100102
}
101103

102104
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
103-
Profiles profiles, ConfigDataLocation location) {
105+
Profiles profiles, ConfigDataLocation location, boolean resolveProfileSpecific) {
104106
try {
105-
return this.resolvers.resolve(locationResolverContext, location, profiles);
107+
return this.resolvers.resolve(locationResolverContext, location, profiles, resolveProfileSpecific);
106108
}
107109
catch (ConfigDataNotFoundException ex) {
108110
handle(ex, location, null);

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,22 +98,23 @@ private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResol
9898
}
9999

100100
List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
101-
Profiles profiles) {
101+
Profiles profiles, boolean resolveProfileSpecific) {
102102
if (location == null) {
103103
return Collections.emptyList();
104104
}
105105
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
106106
if (resolver.isResolvable(context, location)) {
107-
return resolve(resolver, context, location, profiles);
107+
return resolve(resolver, context, location, profiles, resolveProfileSpecific);
108108
}
109109
}
110110
throw new UnsupportedConfigDataLocationException(location);
111111
}
112112

113113
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
114-
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
114+
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles,
115+
boolean resolveProfileSpecific) {
115116
List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
116-
if (profiles == null) {
117+
if (profiles == null || !resolveProfileSpecific) {
117118
return resolved;
118119
}
119120
List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static org.assertj.core.api.Assertions.assertThat;
4444
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4545
import static org.mockito.ArgumentMatchers.any;
46+
import static org.mockito.ArgumentMatchers.anyBoolean;
4647
import static org.mockito.ArgumentMatchers.eq;
4748
import static org.mockito.BDDMockito.given;
4849
import static org.mockito.Mockito.mock;
@@ -119,7 +120,7 @@ void withProcessedImportsResolvesAndLoads() {
119120
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
120121
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
121122
new ConfigData(Arrays.asList(propertySource)));
122-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
123+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
123124
.willReturn(imported);
124125
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
125126
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
@@ -142,14 +143,14 @@ void withProcessedImportsResolvesAndLoadsChainedImports() {
142143
Map<ConfigDataResolutionResult, ConfigData> initialImported = new LinkedHashMap<>();
143144
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
144145
new ConfigData(Arrays.asList(initialPropertySource)));
145-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
146-
.willReturn(initialImported);
146+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
147+
anyBoolean())).willReturn(initialImported);
147148
List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
148149
MockPropertySource secondPropertySource = new MockPropertySource();
149150
Map<ConfigDataResolutionResult, ConfigData> secondImported = new LinkedHashMap<>();
150151
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
151152
new ConfigData(Arrays.asList(secondPropertySource)));
152-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
153+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
153154
.willReturn(secondImported);
154155
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
155156
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
@@ -176,13 +177,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBinder() {
176177
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
177178
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
178179
new ConfigData(Arrays.asList(propertySource)));
179-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
180+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
180181
.willReturn(imported);
181182
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
182183
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
183184
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
184185
contributors.withProcessedImports(this.importer, this.activationContext);
185-
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
186+
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
186187
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
187188
assertThat(context.getBinder().bind("test", String.class).get()).isEqualTo("springboot");
188189
}
@@ -196,20 +197,21 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() {
196197
Map<ConfigDataResolutionResult, ConfigData> initialImported = new LinkedHashMap<>();
197198
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
198199
new ConfigData(Arrays.asList(initialPropertySource)));
199-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
200-
.willReturn(initialImported);
200+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
201+
anyBoolean())).willReturn(initialImported);
201202
List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
202203
MockPropertySource secondPropertySource = new MockPropertySource();
203204
Map<ConfigDataResolutionResult, ConfigData> secondImported = new LinkedHashMap<>();
204205
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
205206
new ConfigData(Arrays.asList(secondPropertySource)));
206-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
207+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
207208
.willReturn(secondImported);
208209
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
209210
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
210211
this.bootstrapContext, Arrays.asList(contributor));
211212
contributors.withProcessedImports(this.importer, this.activationContext);
212-
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations));
213+
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations),
214+
anyBoolean());
213215
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
214216
assertThat(context.getParent()).hasToString("a");
215217
}
@@ -226,13 +228,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBootstrapReg
226228
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
227229
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
228230
new ConfigData(Arrays.asList(propertySource)));
229-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
231+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
230232
.willReturn(imported);
231233
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
232234
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
233235
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
234236
contributors.withProcessedImports(this.importer, this.activationContext);
235-
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
237+
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
236238
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
237239
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
238240
}
@@ -249,13 +251,13 @@ void withProcessedImportsProvidesLoaderContextWithAccessToBootstrapRegistry() {
249251
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
250252
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
251253
new ConfigData(Arrays.asList(propertySource)));
252-
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
254+
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
253255
.willReturn(imported);
254256
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
255257
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
256258
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
257259
contributors.withProcessedImports(this.importer, this.activationContext);
258-
verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any());
260+
verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any(), anyBoolean());
259261
ConfigDataLoaderContext context = this.loaderContext.getValue();
260262
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
261263
}

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

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Arrays;
2525
import java.util.Collections;
2626
import java.util.HashMap;
27+
import java.util.LinkedHashMap;
2728
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Properties;
@@ -59,6 +60,7 @@
5960
import org.springframework.core.io.Resource;
6061
import org.springframework.core.io.ResourceLoader;
6162
import org.springframework.util.FileCopyUtils;
63+
import org.springframework.util.ObjectUtils;
6264
import org.springframework.util.StringUtils;
6365

6466
import static org.assertj.core.api.Assertions.assertThat;
@@ -761,6 +763,15 @@ void runWhenHasProfileSpecificImportWithImportDoesNotImportSecondProfileSpecific
761763
assertThat(environment.containsProperty("application-profile-specific-import-with-import-import-p2")).isFalse();
762764
}
763765

766+
@Test // gh-26753
767+
void runWhenHasProfileSpecificImportWithCustomImportDoesNotResolveProfileSpecific() {
768+
ConfigurableApplicationContext context = this.application
769+
.run("--spring.config.name=application-profile-specific-import-with-custom-import");
770+
ConfigurableEnvironment environment = context.getEnvironment();
771+
assertThat(environment.containsProperty("test:boot")).isTrue();
772+
assertThat(environment.containsProperty("test:boot:ps")).isFalse();
773+
}
774+
764775
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
765776
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {
766777

@@ -798,7 +809,14 @@ public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDat
798809
public List<TestConfigDataResource> resolve(ConfigDataLocationResolverContext context,
799810
ConfigDataLocation location)
800811
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
801-
return Collections.singletonList(new TestConfigDataResource(location));
812+
return Collections.singletonList(new TestConfigDataResource(location, false));
813+
}
814+
815+
@Override
816+
public List<TestConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
817+
ConfigDataLocation location, org.springframework.boot.context.config.Profiles profiles)
818+
throws ConfigDataLocationNotFoundException {
819+
return Collections.singletonList(new TestConfigDataResource(location, true));
802820
}
803821

804822
}
@@ -811,17 +829,55 @@ public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource r
811829
if (resource.isOptional()) {
812830
return null;
813831
}
814-
MapPropertySource propertySource = new MapPropertySource("loaded",
815-
Collections.singletonMap("spring", "boot"));
832+
Map<String, Object> map = new LinkedHashMap<>();
833+
if (!resource.isProfileSpecific()) {
834+
map.put("spring", "boot");
835+
}
836+
String suffix = (!resource.isProfileSpecific()) ? "" : ":ps";
837+
map.put(resource.toString() + suffix, "true");
838+
MapPropertySource propertySource = new MapPropertySource("loaded" + suffix, map);
816839
return new ConfigData(Collections.singleton(propertySource));
817840
}
818841

819842
}
820843

821844
static class TestConfigDataResource extends ConfigDataResource {
822845

823-
TestConfigDataResource(ConfigDataLocation location) {
846+
private final ConfigDataLocation location;
847+
848+
private boolean profileSpecific;
849+
850+
TestConfigDataResource(ConfigDataLocation location, boolean profileSpecific) {
824851
super(location.toString().contains("optionalresult"));
852+
this.location = location;
853+
this.profileSpecific = profileSpecific;
854+
}
855+
856+
boolean isProfileSpecific() {
857+
return this.profileSpecific;
858+
}
859+
860+
@Override
861+
public boolean equals(Object obj) {
862+
if (this == obj) {
863+
return true;
864+
}
865+
if (obj == null || getClass() != obj.getClass()) {
866+
return false;
867+
}
868+
TestConfigDataResource other = (TestConfigDataResource) obj;
869+
return ObjectUtils.nullSafeEquals(this.location, other.location)
870+
&& this.profileSpecific == other.profileSpecific;
871+
}
872+
873+
@Override
874+
public int hashCode() {
875+
return 0;
876+
}
877+
878+
@Override
879+
public String toString() {
880+
return this.location.toString();
825881
}
826882

827883
}

0 commit comments

Comments
 (0)