Skip to content

Commit

Permalink
Prevent nested profile-specific resolution
Browse files Browse the repository at this point in the history
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
  • Loading branch information
philwebb committed Jun 4, 2021
1 parent d1b256a commit 0da0d2d
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@ private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
checkForInvalidProperties(contributors);
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
MutablePropertySources propertySources = this.environment.getPropertySources();
applyContributor(contributors, activationContext, propertySources);
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
this.environmentUpdateListener.onSetProfiles(profiles);
}

private void applyContributor(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
this.logger.trace("Applying config data environment contributions");
for (ConfigDataEnvironmentContributor contributor : contributors) {
PropertySource<?> propertySource = contributor.getPropertySource();
Expand All @@ -345,13 +357,6 @@ private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
}
}
}
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
this.environmentUpdateListener.onSetProfiles(profiles);
}

private void checkForInvalidProperties(ConfigDataEnvironmentContributors contributors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<ConfigDataLocation> imports = contributor.getImports();
boolean resolveProfileSpecific = !contributor.isFromProfileSpecificImport();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
locationResolverContext, loaderContext, imports, resolveProfileSpecific);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ class ConfigDataImporter {
* @param locationResolverContext the location resolver context
* @param loaderContext the loader context
* @param locations the locations to resolve
* @param resolveProfileSpecific if profile specific resolution should be attempted
* @return a map of the loaded locations and data
*/
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<ConfigDataLocation> locations) {
List<ConfigDataLocation> locations, boolean resolveProfileSpecific) {
try {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations,
resolveProfileSpecific);
return load(loaderContext, resolved);
}
catch (IOException ex) {
Expand All @@ -91,18 +93,18 @@ Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationC
}

private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, List<ConfigDataLocation> locations) {
Profiles profiles, List<ConfigDataLocation> locations, boolean resolveProfileSpecific) {
List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
for (ConfigDataLocation location : locations) {
resolved.addAll(resolve(locationResolverContext, profiles, location));
resolved.addAll(resolve(locationResolverContext, profiles, location, resolveProfileSpecific));
}
return Collections.unmodifiableList(resolved);
}

private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, ConfigDataLocation location) {
Profiles profiles, ConfigDataLocation location, boolean resolveProfileSpecific) {
try {
return this.resolvers.resolve(locationResolverContext, location, profiles);
return this.resolvers.resolve(locationResolverContext, location, profiles, resolveProfileSpecific);
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,23 @@ private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResol
}

List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
Profiles profiles) {
Profiles profiles, boolean resolveProfileSpecific) {
if (location == null) {
return Collections.emptyList();
}
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) {
return resolve(resolver, context, location, profiles);
return resolve(resolver, context, location, profiles, resolveProfileSpecific);
}
}
throw new UnsupportedConfigDataLocationException(location);
}

private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles,
boolean resolveProfileSpecific) {
List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
if (profiles == null) {
if (profiles == null || !resolveProfileSpecific) {
return resolved;
}
List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -119,7 +120,7 @@ void withProcessedImportsResolvesAndLoads() {
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Expand All @@ -142,14 +143,14 @@ void withProcessedImportsResolvesAndLoadsChainedImports() {
Map<ConfigDataResolutionResult, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
.willReturn(initialImported);
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
anyBoolean())).willReturn(initialImported);
List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataResolutionResult, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Expand All @@ -176,13 +177,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBinder() {
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBinder().bind("test", String.class).get()).isEqualTo("springboot");
}
Expand All @@ -196,20 +197,21 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() {
Map<ConfigDataResolutionResult, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a"), false),
new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
.willReturn(initialImported);
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations),
anyBoolean())).willReturn(initialImported);
List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataResolutionResult, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new ConfigDataResolutionResult(LOCATION_2, new TestConfigDataResource("b"), false),
new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations), anyBoolean()))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations));
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations),
anyBoolean());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getParent()).hasToString("a");
}
Expand All @@ -226,13 +228,13 @@ void withProcessedImportsProvidesLocationResolverContextWithAccessToBootstrapReg
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any());
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any(), anyBoolean());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
Expand All @@ -249,13 +251,13 @@ void withProcessedImportsProvidesLoaderContextWithAccessToBootstrapRegistry() {
Map<ConfigDataResolutionResult, ConfigData> imported = new LinkedHashMap<>();
imported.put(new ConfigDataResolutionResult(LOCATION_1, new TestConfigDataResource("a'"), false),
new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations), anyBoolean()))
.willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any());
verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any(), anyBoolean());
ConfigDataLoaderContext context = this.loaderContext.getValue();
assertThat(context.getBootstrapContext()).isSameAs(this.bootstrapContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -59,6 +60,7 @@
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

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

@Test // gh-26753
void runWhenHasProfileSpecificImportWithCustomImportDoesNotResolveProfileSpecific() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=application-profile-specific-import-with-custom-import");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.containsProperty("test:boot")).isTrue();
assertThat(environment.containsProperty("test:boot:ps")).isFalse();
}

private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {

Expand Down Expand Up @@ -798,7 +809,14 @@ public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDat
public List<TestConfigDataResource> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location)
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
return Collections.singletonList(new TestConfigDataResource(location));
return Collections.singletonList(new TestConfigDataResource(location, false));
}

@Override
public List<TestConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
ConfigDataLocation location, org.springframework.boot.context.config.Profiles profiles)
throws ConfigDataLocationNotFoundException {
return Collections.singletonList(new TestConfigDataResource(location, true));
}

}
Expand All @@ -811,17 +829,55 @@ public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource r
if (resource.isOptional()) {
return null;
}
MapPropertySource propertySource = new MapPropertySource("loaded",
Collections.singletonMap("spring", "boot"));
Map<String, Object> map = new LinkedHashMap<>();
if (!resource.isProfileSpecific()) {
map.put("spring", "boot");
}
String suffix = (!resource.isProfileSpecific()) ? "" : ":ps";
map.put(resource.toString() + suffix, "true");
MapPropertySource propertySource = new MapPropertySource("loaded" + suffix, map);
return new ConfigData(Collections.singleton(propertySource));
}

}

static class TestConfigDataResource extends ConfigDataResource {

TestConfigDataResource(ConfigDataLocation location) {
private final ConfigDataLocation location;

private boolean profileSpecific;

TestConfigDataResource(ConfigDataLocation location, boolean profileSpecific) {
super(location.toString().contains("optionalresult"));
this.location = location;
this.profileSpecific = profileSpecific;
}

boolean isProfileSpecific() {
return this.profileSpecific;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TestConfigDataResource other = (TestConfigDataResource) obj;
return ObjectUtils.nullSafeEquals(this.location, other.location)
&& this.profileSpecific == other.profileSpecific;
}

@Override
public int hashCode() {
return 0;
}

@Override
public String toString() {
return this.location.toString();
}

}
Expand Down

0 comments on commit 0da0d2d

Please sign in to comment.