diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index f826441a4cc..d181f9edd4c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -235,7 +235,7 @@ class ConfigDataEnvironment { MutablePropertySources propertySources = this.environment.getPropertySources(); this.logger.trace("Applying config data environment contributions"); for (ConfigDataEnvironmentContributor contributor : contributors) { - if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.IMPORTED + if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT && contributor.getPropertySource() != null) { if (!contributor.isActive(activationContext)) { this.logger.trace(LogMessage.format("Skipping inactive property source '%s'", diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index 9cfaa2a4ded..b9f28ea082f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -59,6 +59,8 @@ class ConfigDataEnvironmentContributor implements Iterable> children; private final Kind kind; @@ -71,16 +73,18 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource, ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, - Map> children) { + boolean ignoreImports, Map> children) { this.kind = kind; this.location = location; this.properties = properties; this.propertySource = propertySource; this.configurationPropertySource = configurationPropertySource; + this.ignoreImports = ignoreImports; this.children = (children != null) ? children : Collections.emptyMap(); } @@ -175,6 +179,16 @@ class ConfigDataEnvironmentContributor implements Iterable> updatedChildren = new LinkedHashMap<>(this.children); updatedChildren.put(importPhase, children); 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 contributors) { Map> children = new LinkedHashMap<>(); 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. * This contributor is used to trigger initial imports of additional contributors. It * 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 */ static ConfigDataEnvironmentContributor ofInitialImport(String importLocation) { List imports = Collections.singletonList(importLocation); 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 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 - * contributor has been actively imported from another contributor and may itself + * Factory method to create an {@link Kind#UNBOUND_IMPORT unbound import} contributor. + * This contributor has been actively imported from another contributor and may itself * import further contributors later. * @param location the location of imported config data * @param configData the config data * @param propertySourceIndex the index of the property source that should be used - * @param activationContext the current activation context * @return a new {@link ConfigDataEnvironmentContributor} instance */ - static ConfigDataEnvironmentContributor ofImported(ConfigDataLocation location, ConfigData configData, - int propertySourceIndex, ConfigDataActivationContext activationContext) { + static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigData configData, + int propertySourceIndex) { PropertySource propertySource = configData.getPropertySources().get(propertySourceIndex); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); - Binder binder = new Binder(configurationPropertySource); - UseLegacyConfigProcessingException.throwIfRequested(binder); - ConfigDataProperties properties = ConfigDataProperties.get(binder); - if (configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS)) { - properties = properties.withoutImports(); - } - return new ConfigDataEnvironmentContributor(Kind.IMPORTED, location, propertySource, - configurationPropertySource, properties, null); + boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS); + return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, propertySource, + configurationPropertySource, null, ignoreImports, null); } /** @@ -296,9 +304,16 @@ class ConfigDataEnvironmentContributor implements Iterable 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( - result, unprocessed, activationContext); + result, contributor, activationContext); ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this); - List imports = unprocessed.getImports(); + List imports = contributor.getImports(); this.logger.trace(LogMessage.format("Processing imports %s", imports)); Map imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports); this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported " + imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet())); - ConfigDataEnvironmentContributor processed = unprocessed.withChildren(importPhase, - asContributors(activationContext, imported)); + ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, + asContributors(imported)); result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry, - result.getRoot().withReplacement(unprocessed, processed)); - processedCount++; + result.getRoot().withReplacement(contributor, contributorAndChildren)); + processed++; } } @@ -119,22 +131,27 @@ class ConfigDataEnvironmentContributors implements Iterable asContributors(ConfigDataActivationContext activationContext, - Map imported) { + private boolean isActiveWithUnprocessedImports(ConfigDataActivationContext activationContext, + ImportPhase importPhase, ConfigDataEnvironmentContributor contributor) { + return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase); + } + + private List asContributors(Map imported) { List contributors = new ArrayList<>(imported.size() * 5); imported.forEach((location, data) -> { 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); @@ -155,14 +172,18 @@ class ConfigDataEnvironmentContributors implements Iterable asBinderOptionsSet(BinderOption... options) { + return ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class) + : EnumSet.copyOf(Arrays.asList(options)); } private Binder getBinder(ConfigDataActivationContext activationContext, Set options) { boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); Iterable sources = () -> getBinderSources(activationContext, - !failOnInactiveSource); + !options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE)); PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root, activationContext, failOnInactiveSource); BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java index 6fa91e38f76..27168cdff64 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java @@ -122,7 +122,7 @@ class ConfigDataEnvironmentContributorPlaceholdersResolverTests { private final 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; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java index 80111a87ef6..7c680de4ae6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; 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.mock.env.MockPropertySource; @@ -63,8 +64,7 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.activate.on-cloud-platform", "kubernetes"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.isActive(this.activationContext)).isTrue(); } @@ -73,8 +73,7 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.activate.on-cloud-platform", "heroku"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.isActive(this.activationContext)).isFalse(); } @@ -82,8 +81,8 @@ class ConfigDataEnvironmentContributorTests { void getLocationReturnsLocation() { ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigDataLocation location = mock(ConfigDataLocation.class); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData, - 0, this.activationContext); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, + configData, 0); assertThat(contributor.getLocation()).isSameAs(location); } @@ -99,8 +98,8 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring", "boot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null, + configData, 0); assertThat(contributor.getConfigurationPropertySource() .getConfigurationProperty(ConfigurationPropertyName.of("spring")).getValue()).isEqualTo("boot"); } @@ -108,8 +107,7 @@ class ConfigDataEnvironmentContributorTests { @Test void getImportsWhenPropertiesIsNullReturnsEmptyList() { ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.getImports()).isEmpty(); } @@ -118,16 +116,14 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "spring,boot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.getImports()).containsExactly("spring", "boot"); } @Test void hasUnprocessedImportsWhenNoImportsReturnsFalse() { ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse(); } @@ -136,8 +132,7 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "springboot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isTrue(); } @@ -146,11 +141,9 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "springboot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null, - childConfigData, 0, this.activationContext); + ConfigDataEnvironmentContributor childContributor = createBoundContributor(null, childConfigData, 0); ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(childContributor)); assertThat(withChildren.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse(); @@ -160,8 +153,7 @@ class ConfigDataEnvironmentContributorTests { @Test void getChildrenWhenHasNoChildrenReturnsEmptyList() { ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty(); assertThat(contributor.getChildren(ImportPhase.AFTER_PROFILE_ACTIVATION)).isEmpty(); } @@ -171,11 +163,9 @@ class ConfigDataEnvironmentContributorTests { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "springboot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0, - this.activationContext); + ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null, - childConfigData, 0, this.activationContext); + ConfigDataEnvironmentContributor childContributor = createBoundContributor(null, childConfigData, 0); ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(childContributor)); assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(childContributor); @@ -184,35 +174,36 @@ class ConfigDataEnvironmentContributorTests { @Test void streamReturnsStream() { - ConfigDataEnvironmentContributor contributor = createContributor("a"); + ConfigDataEnvironmentContributor contributor = createBoundContributor("a"); Stream stream = contributor.stream().map(this::getLocationName); assertThat(stream).containsExactly("a"); } @Test void iteratorWhenSingleContributorReturnsSingletonIterator() { - ConfigDataEnvironmentContributor contributor = createContributor("a"); + ConfigDataEnvironmentContributor contributor = createBoundContributor("a"); assertThat(asLocationsList(contributor.iterator())).containsExactly("a"); } @Test void iteratorWhenTypicalStructureReturnsCorrectlyOrderedIterator() { - ConfigDataEnvironmentContributor fileApplication = createContributor("file:application.properties"); - ConfigDataEnvironmentContributor fileProfile = createContributor("file:application-profile.properties"); - ConfigDataEnvironmentContributor fileImports = createContributor("file:./"); + ConfigDataEnvironmentContributor fileApplication = createBoundContributor("file:application.properties"); + ConfigDataEnvironmentContributor fileProfile = createBoundContributor("file:application-profile.properties"); + ConfigDataEnvironmentContributor fileImports = createBoundContributor("file:./"); fileImports = fileImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(fileApplication)); fileImports = fileImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION, Collections.singletonList(fileProfile)); - ConfigDataEnvironmentContributor classpathApplication = createContributor("classpath:application.properties"); - ConfigDataEnvironmentContributor classpathProfile = createContributor( + ConfigDataEnvironmentContributor classpathApplication = createBoundContributor( + "classpath:application.properties"); + ConfigDataEnvironmentContributor classpathProfile = createBoundContributor( "classpath:application-profile.properties"); - ConfigDataEnvironmentContributor classpathImports = createContributor("classpath:/"); + ConfigDataEnvironmentContributor classpathImports = createBoundContributor("classpath:/"); classpathImports = classpathImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Arrays.asList(classpathApplication)); classpathImports = classpathImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION, Arrays.asList(classpathProfile)); - ConfigDataEnvironmentContributor root = createContributor("root"); + ConfigDataEnvironmentContributor root = createBoundContributor("root"); root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Arrays.asList(fileImports, classpathImports)); assertThat(asLocationsList(root.iterator())).containsExactly("file:application-profile.properties", "file:application.properties", "file:./", "classpath:application-profile.properties", @@ -221,8 +212,8 @@ class ConfigDataEnvironmentContributorTests { @Test void withChildrenReturnsNewInstanceWithChildren() { - ConfigDataEnvironmentContributor root = createContributor("root"); - ConfigDataEnvironmentContributor child = createContributor("child"); + ConfigDataEnvironmentContributor root = createBoundContributor("root"); + ConfigDataEnvironmentContributor child = createBoundContributor("child"); ConfigDataEnvironmentContributor withChildren = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child)); assertThat(root.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty(); @@ -231,12 +222,12 @@ class ConfigDataEnvironmentContributorTests { @Test void withReplacementReplacesChild() { - ConfigDataEnvironmentContributor root = createContributor("root"); - ConfigDataEnvironmentContributor child = createContributor("child"); - ConfigDataEnvironmentContributor grandchild = createContributor("grandchild"); + ConfigDataEnvironmentContributor root = createBoundContributor("root"); + ConfigDataEnvironmentContributor child = createBoundContributor("child"); + ConfigDataEnvironmentContributor grandchild = createBoundContributor("grandchild"); child = child.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(grandchild)); root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child)); - ConfigDataEnvironmentContributor updated = createContributor("updated"); + ConfigDataEnvironmentContributor updated = createBoundContributor("updated"); ConfigDataEnvironmentContributor withReplacement = root.withReplacement(grandchild, updated); assertThat(asLocationsList(root.iterator())).containsExactly("grandchild", "child", "root"); assertThat(asLocationsList(withReplacement.iterator())).containsExactly("updated", "child", "root"); @@ -244,8 +235,8 @@ class ConfigDataEnvironmentContributorTests { @Test void ofCreatesRootContributor() { - ConfigDataEnvironmentContributor one = createContributor("one"); - ConfigDataEnvironmentContributor two = createContributor("two"); + ConfigDataEnvironmentContributor one = createBoundContributor("one"); + ConfigDataEnvironmentContributor two = createBoundContributor("two"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two)); assertThat(contributor.getKind()).isEqualTo(Kind.ROOT); assertThat(contributor.getLocation()).isNull(); @@ -284,31 +275,14 @@ class ConfigDataEnvironmentContributorTests { } @Test - void ofImportedCreatesImportedContributor() { + void ofUnboundImportCreatesImportedContributor() { TestLocation location = new TestLocation("test"); MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "test"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData, - 0, this.activationContext); - assertThat(contributor.getKind()).isEqualTo(Kind.IMPORTED); - 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); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, + configData, 0); + assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT); assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.isActive(this.activationContext)).isTrue(); @@ -318,17 +292,56 @@ class ConfigDataEnvironmentContributorTests { } @Test - void ofImportedWhenHasUseLegacyPropertyThrowsException() { + void bindCreatesImportedContributor() { + TestLocation location = new TestLocation("test"); MockPropertySource propertySource = new MockPropertySource(); - propertySource.setProperty("spring.config.use-legacy-processing", "true"); - assertThatExceptionOfType(UseLegacyConfigProcessingException.class) - .isThrownBy(() -> ConfigDataEnvironmentContributor.ofImported(null, - new ConfigData(Collections.singleton(propertySource)), 0, this.activationContext)); + propertySource.setProperty("spring.config.import", "test"); + ConfigData configData = new ConfigData(Collections.singleton(propertySource)); + ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0); + 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) { - return ConfigDataEnvironmentContributor.ofImported(new TestLocation(location), - new ConfigData(Collections.singleton(new MockPropertySource())), 0, this.activationContext); + @Test + void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() { + 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 asLocationsList(Iterator iterator) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java index 5242572bf15..012fff99126 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java @@ -268,10 +268,8 @@ class ConfigDataEnvironmentContributorsTests { MockPropertySource secondPropertySource = new MockPropertySource(); secondPropertySource.setProperty("test", "two"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); @@ -286,10 +284,8 @@ class ConfigDataEnvironmentContributorsTests { MockPropertySource secondPropertySource = new MockPropertySource(); secondPropertySource.setProperty("test", "two"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); @@ -317,10 +313,8 @@ class ConfigDataEnvironmentContributorsTests { secondPropertySource.setProperty("other", "two"); secondPropertySource.setProperty("test", "${other}"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); @@ -335,10 +329,8 @@ class ConfigDataEnvironmentContributorsTests { MockPropertySource secondPropertySource = new MockPropertySource(); secondPropertySource.setProperty("test", "two"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); 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("test", "two"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); 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("other", "one"); ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource)); - ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 0, this.activationContext); - ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, - configData, 1, this.activationContext); + ConfigDataEnvironmentContributor firstContributor = createBoundImportContributor(configData, 0); + ConfigDataEnvironmentContributor secondContributor = createBoundImportContributor(configData, 1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); 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)); } + 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 final String value; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index 6941f90b5bc..09f6d2277fc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; +import org.springframework.boot.context.properties.bind.BindException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; @@ -514,6 +515,27 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { () -> 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 matchingPropertySource(final String sourceName) { return new Condition("environment containing property source " + sourceName) { diff --git a/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-imported.properties b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-imported.properties new file mode 100644 index 00000000000..e80c76a4399 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-imported.properties @@ -0,0 +1 @@ +my.value=iwasimported \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-document.properties b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-document.properties new file mode 100644 index 00000000000..556143a7720 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-document.properties @@ -0,0 +1,3 @@ +my.import=application-import-with-placeholder-imported +#--- +spring.config.import=classpath:${my.import}.properties diff --git a/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-profile-document.properties b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-profile-document.properties new file mode 100644 index 00000000000..890f961e5f1 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder-in-profile-document.properties @@ -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 \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder.properties b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder.properties new file mode 100644 index 00000000000..de35cff6240 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-import-with-placeholder.properties @@ -0,0 +1,2 @@ +my.import=application-import-with-placeholder-imported +spring.config.import=classpath:${my.import}.properties \ No newline at end of file