Allow placeholders to be used in imports

Allow `${..}` property placeholders to be used in `spring.config.import`
properties. Prior to this commit, placeholders were not resolved when
binding the `ConfigDataProperty` instance. Furthermore, binding happened
too early for all placeholders to be resolved correctly. The
`ConfigDataEnvironmentContributor` class now has two states for imported
contributors, `UNBOUND_IMPORT` has the initial unbound state and is
eventually replaced with a `BOUND_IMPORT`.

Closes gh-23020
This commit is contained in:
Phillip Webb 2020-08-22 19:59:22 -07:00
parent 00cb5bbd86
commit 758df17c7d
11 changed files with 217 additions and 138 deletions

View File

@ -235,7 +235,7 @@ class ConfigDataEnvironment {
MutablePropertySources propertySources = this.environment.getPropertySources(); MutablePropertySources propertySources = this.environment.getPropertySources();
this.logger.trace("Applying config data environment contributions"); this.logger.trace("Applying config data environment contributions");
for (ConfigDataEnvironmentContributor contributor : contributors) { for (ConfigDataEnvironmentContributor contributor : contributors) {
if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.IMPORTED if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT
&& contributor.getPropertySource() != null) { && contributor.getPropertySource() != null) {
if (!contributor.isActive(activationContext)) { if (!contributor.isActive(activationContext)) {
this.logger.trace(LogMessage.format("Skipping inactive property source '%s'", this.logger.trace(LogMessage.format("Skipping inactive property source '%s'",

View File

@ -59,6 +59,8 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
private final ConfigDataProperties properties; private final ConfigDataProperties properties;
private final boolean ignoreImports;
private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children; private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;
private final Kind kind; private final Kind kind;
@ -71,16 +73,18 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* @param configurationPropertySource the configuration property source for the data * @param configurationPropertySource the configuration property source for the data
* or {@code null} * or {@code null}
* @param properties the config data properties or {@code null} * @param properties the config data properties or {@code null}
* @param ignoreImports if import properties should be ignored
* @param children the children of this contributor at each {@link ImportPhase} * @param children the children of this contributor at each {@link ImportPhase}
*/ */
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, PropertySource<?> propertySource, ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) { boolean ignoreImports, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind; this.kind = kind;
this.location = location; this.location = location;
this.properties = properties; this.properties = properties;
this.propertySource = propertySource; this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource; this.configurationPropertySource = configurationPropertySource;
this.ignoreImports = ignoreImports;
this.children = (children != null) ? children : Collections.emptyMap(); this.children = (children != null) ? children : Collections.emptyMap();
} }
@ -175,6 +179,16 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
return new ContributorIterator(); return new ContributorIterator();
} }
ConfigDataEnvironmentContributor withBoundProperties(Binder binder) {
UseLegacyConfigProcessingException.throwIfRequested(binder);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
if (this.ignoreImports) {
properties = properties.withoutImports();
}
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.propertySource,
this.configurationPropertySource, properties, this.ignoreImports, null);
}
/** /**
* Create a new {@link ConfigDataEnvironmentContributor} instance with a new set of * Create a new {@link ConfigDataEnvironmentContributor} instance with a new set of
* children for the given phase. * children for the given phase.
@ -187,7 +201,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children); Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
updatedChildren.put(importPhase, children); updatedChildren.put(importPhase, children);
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource, return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource,
this.configurationPropertySource, this.properties, updatedChildren); this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
} }
/** /**
@ -212,7 +226,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors)); updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
}); });
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource, return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource,
this.configurationPropertySource, this.properties, updatedChildren); this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
} }
/** /**
@ -223,20 +237,20 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) { static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>(); Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors)); children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, null, null, children); return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, null, null, false, children);
} }
/** /**
* Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor. * Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor.
* This contributor is used to trigger initial imports of additional contributors. It * This contributor is used to trigger initial imports of additional contributors. It
* does not contribute any properties itself. * does not contribute any properties itself.
* @param importLocation the initial import location * @param importLocation the initial import location (with placeholder resolved)
* @return a new {@link ConfigDataEnvironmentContributor} instance * @return a new {@link ConfigDataEnvironmentContributor} instance
*/ */
static ConfigDataEnvironmentContributor ofInitialImport(String importLocation) { static ConfigDataEnvironmentContributor ofInitialImport(String importLocation) {
List<String> imports = Collections.singletonList(importLocation); List<String> imports = Collections.singletonList(importLocation);
ConfigDataProperties properties = new ConfigDataProperties(imports, null); ConfigDataProperties properties = new ConfigDataProperties(imports, null);
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, null); return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, false, null);
} }
/** /**
@ -248,31 +262,25 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
*/ */
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) { static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, propertySource, return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, propertySource,
ConfigurationPropertySource.from(propertySource), null, null); ConfigurationPropertySource.from(propertySource), null, false, null);
} }
/** /**
* Factory method to create a {@link Kind#IMPORTED imported} contributor. This * Factory method to create an {@link Kind#UNBOUND_IMPORT unbound import} contributor.
* contributor has been actively imported from another contributor and may itself * This contributor has been actively imported from another contributor and may itself
* import further contributors later. * import further contributors later.
* @param location the location of imported config data * @param location the location of imported config data
* @param configData the config data * @param configData the config data
* @param propertySourceIndex the index of the property source that should be used * @param propertySourceIndex the index of the property source that should be used
* @param activationContext the current activation context
* @return a new {@link ConfigDataEnvironmentContributor} instance * @return a new {@link ConfigDataEnvironmentContributor} instance
*/ */
static ConfigDataEnvironmentContributor ofImported(ConfigDataLocation location, ConfigData configData, static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigData configData,
int propertySourceIndex, ConfigDataActivationContext activationContext) { int propertySourceIndex) {
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex); PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
Binder binder = new Binder(configurationPropertySource); boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS);
UseLegacyConfigProcessingException.throwIfRequested(binder); return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, propertySource,
ConfigDataProperties properties = ConfigDataProperties.get(binder); configurationPropertySource, null, ignoreImports, null);
if (configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS)) {
properties = properties.withoutImports();
}
return new ConfigDataEnvironmentContributor(Kind.IMPORTED, location, propertySource,
configurationPropertySource, properties, null);
} }
/** /**
@ -296,9 +304,16 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
EXISTING, EXISTING,
/** /**
* A contributor with {@link ConfigData} imported from another contributor. * A contributor with {@link ConfigData} imported from another contributor but not
* yet bound.
*/ */
IMPORTED; UNBOUND_IMPORT,
/**
* A contributor with {@link ConfigData} imported from another contributor that
* has been.
*/
BOUND_IMPORT;
} }

View File

@ -29,6 +29,7 @@ import java.util.stream.Stream;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
@ -91,27 +92,38 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase, this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context")); (activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this; ConfigDataEnvironmentContributors result = this;
int processedCount = 0; int processed = 0;
while (true) { while (true) {
ConfigDataEnvironmentContributor unprocessed = getFirstUnprocessed(result, activationContext, importPhase); ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (unprocessed == null) { if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processedCount)); this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result; return result;
} }
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
Iterable<ConfigurationPropertySource> sources = Collections
.singleton(contributor.getConfigurationPropertySource());
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
result, activationContext, true);
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry,
result.getRoot().withReplacement(contributor, bound));
continue;
}
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext( ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, unprocessed, activationContext); result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this); ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<String> imports = unprocessed.getImports(); List<String> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports)); this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataLocation, ConfigData> imported = importer.resolveAndLoad(activationContext, Map<ConfigDataLocation, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports); locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported " this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported "
+ imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet())); + imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet()));
ConfigDataEnvironmentContributor processed = unprocessed.withChildren(importPhase, ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(activationContext, imported)); asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry, result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry,
result.getRoot().withReplacement(unprocessed, processed)); result.getRoot().withReplacement(contributor, contributorAndChildren));
processedCount++; processed++;
} }
} }
@ -119,22 +131,27 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
return this.bootstrapRegistry; return this.bootstrapRegistry;
} }
private ConfigDataEnvironmentContributor getFirstUnprocessed(ConfigDataEnvironmentContributors contributors, private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, ImportPhase importPhase) { ConfigDataActivationContext activationContext, ImportPhase importPhase) {
for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) { for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) {
if (contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase)) { if (contributor.getKind() == Kind.UNBOUND_IMPORT
|| isActiveWithUnprocessedImports(activationContext, importPhase, contributor)) {
return contributor; return contributor;
} }
} }
return null; return null;
} }
private List<ConfigDataEnvironmentContributor> asContributors(ConfigDataActivationContext activationContext, private boolean isActiveWithUnprocessedImports(ConfigDataActivationContext activationContext,
Map<ConfigDataLocation, ConfigData> imported) { ImportPhase importPhase, ConfigDataEnvironmentContributor contributor) {
return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase);
}
private List<ConfigDataEnvironmentContributor> asContributors(Map<ConfigDataLocation, ConfigData> imported) {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5); List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
imported.forEach((location, data) -> { imported.forEach((location, data) -> {
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) { for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
contributors.add(ConfigDataEnvironmentContributor.ofImported(location, data, i, activationContext)); contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(location, data, i));
} }
}); });
return Collections.unmodifiableList(contributors); return Collections.unmodifiableList(contributors);
@ -155,14 +172,18 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
* @return a binder instance * @return a binder instance
*/ */
Binder getBinder(ConfigDataActivationContext activationContext, BinderOption... options) { Binder getBinder(ConfigDataActivationContext activationContext, BinderOption... options) {
return getBinder(activationContext, ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class) return getBinder(activationContext, asBinderOptionsSet(options));
: EnumSet.copyOf(Arrays.asList(options))); }
private Set<BinderOption> asBinderOptionsSet(BinderOption... options) {
return ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class)
: EnumSet.copyOf(Arrays.asList(options));
} }
private Binder getBinder(ConfigDataActivationContext activationContext, Set<BinderOption> options) { private Binder getBinder(ConfigDataActivationContext activationContext, Set<BinderOption> options) {
boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(activationContext, Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(activationContext,
!failOnInactiveSource); !options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root, PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
activationContext, failOnInactiveSource); activationContext, failOnInactiveSource);
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext); BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);

View File

@ -122,7 +122,7 @@ class ConfigDataEnvironmentContributorPlaceholdersResolverTests {
private final boolean active; private final boolean active;
protected TestConfigDataEnvironmentContributor(PropertySource<?> propertySource, boolean active) { protected TestConfigDataEnvironmentContributor(PropertySource<?> propertySource, boolean active) {
super(Kind.ROOT, null, propertySource, null, null, null); super(Kind.ROOT, null, propertySource, null, null, false, null);
this.active = active; this.active = active;
} }

View File

@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.env.MockPropertySource;
@ -63,8 +64,7 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.activate.on-cloud-platform", "kubernetes"); propertySource.setProperty("spring.config.activate.on-cloud-platform", "kubernetes");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
} }
@ -73,8 +73,7 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.activate.on-cloud-platform", "heroku"); propertySource.setProperty("spring.config.activate.on-cloud-platform", "heroku");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.isActive(this.activationContext)).isFalse(); assertThat(contributor.isActive(this.activationContext)).isFalse();
} }
@ -82,8 +81,8 @@ class ConfigDataEnvironmentContributorTests {
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataLocation location = mock(ConfigDataLocation.class); ConfigDataLocation location = mock(ConfigDataLocation.class);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location,
0, this.activationContext); configData, 0);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getLocation()).isSameAs(location);
} }
@ -99,8 +98,8 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring", "boot"); propertySource.setProperty("spring", "boot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null,
this.activationContext); configData, 0);
assertThat(contributor.getConfigurationPropertySource() assertThat(contributor.getConfigurationPropertySource()
.getConfigurationProperty(ConfigurationPropertyName.of("spring")).getValue()).isEqualTo("boot"); .getConfigurationProperty(ConfigurationPropertyName.of("spring")).getValue()).isEqualTo("boot");
} }
@ -108,8 +107,7 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void getImportsWhenPropertiesIsNullReturnsEmptyList() { void getImportsWhenPropertiesIsNullReturnsEmptyList() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.getImports()).isEmpty();
} }
@ -118,16 +116,14 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "spring,boot"); propertySource.setProperty("spring.config.import", "spring,boot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.getImports()).containsExactly("spring", "boot"); assertThat(contributor.getImports()).containsExactly("spring", "boot");
} }
@Test @Test
void hasUnprocessedImportsWhenNoImportsReturnsFalse() { void hasUnprocessedImportsWhenNoImportsReturnsFalse() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse(); assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse();
} }
@ -136,8 +132,7 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot"); propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isTrue(); assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isTrue();
} }
@ -146,11 +141,9 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot"); propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor childContributor = createBoundContributor(null, childConfigData, 0);
childConfigData, 0, this.activationContext);
ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(childContributor)); Collections.singletonList(childContributor));
assertThat(withChildren.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse(); assertThat(withChildren.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse();
@ -160,8 +153,7 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void getChildrenWhenHasNoChildrenReturnsEmptyList() { void getChildrenWhenHasNoChildrenReturnsEmptyList() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty(); assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
assertThat(contributor.getChildren(ImportPhase.AFTER_PROFILE_ACTIVATION)).isEmpty(); assertThat(contributor.getChildren(ImportPhase.AFTER_PROFILE_ACTIVATION)).isEmpty();
} }
@ -171,11 +163,9 @@ class ConfigDataEnvironmentContributorTests {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot"); propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
this.activationContext);
ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor childContributor = createBoundContributor(null, childConfigData, 0);
childConfigData, 0, this.activationContext);
ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(childContributor)); Collections.singletonList(childContributor));
assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(childContributor); assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(childContributor);
@ -184,35 +174,36 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void streamReturnsStream() { void streamReturnsStream() {
ConfigDataEnvironmentContributor contributor = createContributor("a"); ConfigDataEnvironmentContributor contributor = createBoundContributor("a");
Stream<String> stream = contributor.stream().map(this::getLocationName); Stream<String> stream = contributor.stream().map(this::getLocationName);
assertThat(stream).containsExactly("a"); assertThat(stream).containsExactly("a");
} }
@Test @Test
void iteratorWhenSingleContributorReturnsSingletonIterator() { void iteratorWhenSingleContributorReturnsSingletonIterator() {
ConfigDataEnvironmentContributor contributor = createContributor("a"); ConfigDataEnvironmentContributor contributor = createBoundContributor("a");
assertThat(asLocationsList(contributor.iterator())).containsExactly("a"); assertThat(asLocationsList(contributor.iterator())).containsExactly("a");
} }
@Test @Test
void iteratorWhenTypicalStructureReturnsCorrectlyOrderedIterator() { void iteratorWhenTypicalStructureReturnsCorrectlyOrderedIterator() {
ConfigDataEnvironmentContributor fileApplication = createContributor("file:application.properties"); ConfigDataEnvironmentContributor fileApplication = createBoundContributor("file:application.properties");
ConfigDataEnvironmentContributor fileProfile = createContributor("file:application-profile.properties"); ConfigDataEnvironmentContributor fileProfile = createBoundContributor("file:application-profile.properties");
ConfigDataEnvironmentContributor fileImports = createContributor("file:./"); ConfigDataEnvironmentContributor fileImports = createBoundContributor("file:./");
fileImports = fileImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, fileImports = fileImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(fileApplication)); Collections.singletonList(fileApplication));
fileImports = fileImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION, fileImports = fileImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION,
Collections.singletonList(fileProfile)); Collections.singletonList(fileProfile));
ConfigDataEnvironmentContributor classpathApplication = createContributor("classpath:application.properties"); ConfigDataEnvironmentContributor classpathApplication = createBoundContributor(
ConfigDataEnvironmentContributor classpathProfile = createContributor( "classpath:application.properties");
ConfigDataEnvironmentContributor classpathProfile = createBoundContributor(
"classpath:application-profile.properties"); "classpath:application-profile.properties");
ConfigDataEnvironmentContributor classpathImports = createContributor("classpath:/"); ConfigDataEnvironmentContributor classpathImports = createBoundContributor("classpath:/");
classpathImports = classpathImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, classpathImports = classpathImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Arrays.asList(classpathApplication)); Arrays.asList(classpathApplication));
classpathImports = classpathImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION, classpathImports = classpathImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION,
Arrays.asList(classpathProfile)); Arrays.asList(classpathProfile));
ConfigDataEnvironmentContributor root = createContributor("root"); ConfigDataEnvironmentContributor root = createBoundContributor("root");
root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Arrays.asList(fileImports, classpathImports)); root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Arrays.asList(fileImports, classpathImports));
assertThat(asLocationsList(root.iterator())).containsExactly("file:application-profile.properties", assertThat(asLocationsList(root.iterator())).containsExactly("file:application-profile.properties",
"file:application.properties", "file:./", "classpath:application-profile.properties", "file:application.properties", "file:./", "classpath:application-profile.properties",
@ -221,8 +212,8 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void withChildrenReturnsNewInstanceWithChildren() { void withChildrenReturnsNewInstanceWithChildren() {
ConfigDataEnvironmentContributor root = createContributor("root"); ConfigDataEnvironmentContributor root = createBoundContributor("root");
ConfigDataEnvironmentContributor child = createContributor("child"); ConfigDataEnvironmentContributor child = createBoundContributor("child");
ConfigDataEnvironmentContributor withChildren = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, ConfigDataEnvironmentContributor withChildren = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(child)); Collections.singletonList(child));
assertThat(root.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty(); assertThat(root.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
@ -231,12 +222,12 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void withReplacementReplacesChild() { void withReplacementReplacesChild() {
ConfigDataEnvironmentContributor root = createContributor("root"); ConfigDataEnvironmentContributor root = createBoundContributor("root");
ConfigDataEnvironmentContributor child = createContributor("child"); ConfigDataEnvironmentContributor child = createBoundContributor("child");
ConfigDataEnvironmentContributor grandchild = createContributor("grandchild"); ConfigDataEnvironmentContributor grandchild = createBoundContributor("grandchild");
child = child.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(grandchild)); child = child.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(grandchild));
root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child)); root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child));
ConfigDataEnvironmentContributor updated = createContributor("updated"); ConfigDataEnvironmentContributor updated = createBoundContributor("updated");
ConfigDataEnvironmentContributor withReplacement = root.withReplacement(grandchild, updated); ConfigDataEnvironmentContributor withReplacement = root.withReplacement(grandchild, updated);
assertThat(asLocationsList(root.iterator())).containsExactly("grandchild", "child", "root"); assertThat(asLocationsList(root.iterator())).containsExactly("grandchild", "child", "root");
assertThat(asLocationsList(withReplacement.iterator())).containsExactly("updated", "child", "root"); assertThat(asLocationsList(withReplacement.iterator())).containsExactly("updated", "child", "root");
@ -244,8 +235,8 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void ofCreatesRootContributor() { void ofCreatesRootContributor() {
ConfigDataEnvironmentContributor one = createContributor("one"); ConfigDataEnvironmentContributor one = createBoundContributor("one");
ConfigDataEnvironmentContributor two = createContributor("two"); ConfigDataEnvironmentContributor two = createBoundContributor("two");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two)); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two));
assertThat(contributor.getKind()).isEqualTo(Kind.ROOT); assertThat(contributor.getKind()).isEqualTo(Kind.ROOT);
assertThat(contributor.getLocation()).isNull(); assertThat(contributor.getLocation()).isNull();
@ -284,31 +275,14 @@ class ConfigDataEnvironmentContributorTests {
} }
@Test @Test
void ofImportedCreatesImportedContributor() { void ofUnboundImportCreatesImportedContributor() {
TestLocation location = new TestLocation("test"); TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test"); propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location,
0, this.activationContext); configData, 0);
assertThat(contributor.getKind()).isEqualTo(Kind.IMPORTED); assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).containsExactly("test");
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void ofImportedWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() {
TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData,
0, this.activationContext);
assertThat(contributor.getKind()).isEqualTo(Kind.IMPORTED);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
@ -318,17 +292,56 @@ class ConfigDataEnvironmentContributorTests {
} }
@Test @Test
void ofImportedWhenHasUseLegacyPropertyThrowsException() { void bindCreatesImportedContributor() {
TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.use-legacy-processing", "true"); propertySource.setProperty("spring.config.import", "test");
assertThatExceptionOfType(UseLegacyConfigProcessingException.class) ConfigData configData = new ConfigData(Collections.singleton(propertySource));
.isThrownBy(() -> ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
new ConfigData(Collections.singleton(propertySource)), 0, this.activationContext)); assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).containsExactly("test");
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
} }
private ConfigDataEnvironmentContributor createContributor(String location) { @Test
return ConfigDataEnvironmentContributor.ofImported(new TestLocation(location), void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() {
new ConfigData(Collections.singleton(new MockPropertySource())), 0, this.activationContext); TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS);
ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void bindWhenHasUseLegacyPropertyThrowsException() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.use-legacy-processing", "true");
assertThatExceptionOfType(UseLegacyConfigProcessingException.class).isThrownBy(
() -> createBoundContributor(null, new ConfigData(Collections.singleton(propertySource)), 0));
}
private ConfigDataEnvironmentContributor createBoundContributor(String location) {
return createBoundContributor(new TestLocation(location),
new ConfigData(Collections.singleton(new MockPropertySource())), 0);
}
private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataLocation location, ConfigData configData,
int propertySourceIndex) {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location,
configData, propertySourceIndex);
Binder binder = new Binder(contributor.getConfigurationPropertySource());
return contributor.withBoundProperties(binder);
} }
private List<String> asLocationsList(Iterator<ConfigDataEnvironmentContributor> iterator) { private List<String> asLocationsList(Iterator<ConfigDataEnvironmentContributor> iterator) {

View File

@ -268,10 +268,8 @@ class ConfigDataEnvironmentContributorsTests {
MockPropertySource secondPropertySource = new MockPropertySource(); MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two"); secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext); Binder binder = contributors.getBinder(this.activationContext);
@ -286,10 +284,8 @@ class ConfigDataEnvironmentContributorsTests {
MockPropertySource secondPropertySource = new MockPropertySource(); MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two"); secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext); Binder binder = contributors.getBinder(this.activationContext);
@ -317,10 +313,8 @@ class ConfigDataEnvironmentContributorsTests {
secondPropertySource.setProperty("other", "two"); secondPropertySource.setProperty("other", "two");
secondPropertySource.setProperty("test", "${other}"); secondPropertySource.setProperty("test", "${other}");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext); Binder binder = contributors.getBinder(this.activationContext);
@ -335,10 +329,8 @@ class ConfigDataEnvironmentContributorsTests {
MockPropertySource secondPropertySource = new MockPropertySource(); MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two"); secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
@ -354,10 +346,8 @@ class ConfigDataEnvironmentContributorsTests {
secondPropertySource.setProperty("spring.config.activate.on-profile", "production"); secondPropertySource.setProperty("spring.config.activate.on-profile", "production");
secondPropertySource.setProperty("test", "two"); secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
@ -374,10 +364,8 @@ class ConfigDataEnvironmentContributorsTests {
secondPropertySource.setProperty("test", "${other}"); secondPropertySource.setProperty("test", "${other}");
secondPropertySource.setProperty("other", "one"); secondPropertySource.setProperty("other", "one");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0);
configData, 0, this.activationContext); ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
@ -385,6 +373,14 @@ class ConfigDataEnvironmentContributorsTests {
.satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class));
} }
private ConfigDataEnvironmentContributor createBoundImportContributor(ConfigData configData,
int propertySourceIndex) {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null,
configData, propertySourceIndex);
Binder binder = new Binder(contributor.getConfigurationPropertySource());
return contributor.withBoundProperties(binder);
}
private static class TestConfigDataLocation extends ConfigDataLocation { private static class TestConfigDataLocation extends ConfigDataLocation {
private final String value; private final String value;

View File

@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
@ -514,6 +515,27 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
() -> this.application.run("--spring.config.location=classpath:invalidproperty.properties")); () -> this.application.run("--spring.config.location=classpath:invalidproperty.properties"));
} }
@Test
void runWhenImportUsesPlaceholder() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.location=classpath:application-import-with-placeholder.properties");
assertThat(context.getEnvironment().getProperty("my.value")).isEqualTo("iwasimported");
}
@Test
void runWhenImportFromEarlierDocumentUsesPlaceholder() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.location=classpath:application-import-with-placeholder-in-document.properties");
assertThat(context.getEnvironment().getProperty("my.value")).isEqualTo("iwasimported");
}
@Test
void runWhenHasPropertyInProfileDocumentThrowsException() {
assertThatExceptionOfType(BindException.class).isThrownBy(() -> this.application.run(
"--spring.config.location=classpath:application-import-with-placeholder-in-profile-document.properties"))
.withCauseInstanceOf(InactiveConfigDataAccessException.class);
}
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) { private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) { return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {

View File

@ -0,0 +1,3 @@
my.import=application-import-with-placeholder-imported
#---
spring.config.import=classpath:${my.import}.properties

View File

@ -0,0 +1,6 @@
my.import=application-import-with-placeholder-imported
#---
spring.config.import=classpath:${my.import}.properties
#---
my.import=badbadbad
spring.config.activate.on-profile=missing

View File

@ -0,0 +1,2 @@
my.import=application-import-with-placeholder-imported
spring.config.import=classpath:${my.import}.properties