From a8592f36d425fa6b9faff59a45e1936e87b2ec9d Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 25 Feb 2021 09:15:08 -0800 Subject: [PATCH] Add prefix support for property source We configure the `SystemEnvironmentPropertySource` as a `Prefixed` property source. When adapting this to a `ConfigurationPropertySource, a `PrefixedConfigurationPropertySource` will be created for it. A `PrefixedConfigurationPropertySource` will resolve property such as `foo.bar` to `my.foo.bar` for a prefix of `my`. Closes gh-3450 --- .../docs/asciidoc/spring-boot-features.adoc | 8 ++ .../boot/SpringApplication.java | 13 +++ .../source/ConfigurationPropertySource.java | 9 ++ .../IterableConfigurationPropertySource.java | 5 ++ .../PrefixedConfigurationPropertySource.java | 72 +++++++++++++++ ...edIterableConfigurationPropertySource.java | 50 +++++++++++ .../SpringConfigurationPropertySources.java | 4 + .../springframework/boot/env/Prefixed.java | 31 +++++++ ...ropertySourceEnvironmentPostProcessor.java | 47 ++++++++-- .../boot/SpringApplicationTests.java | 15 ++++ .../ConfigurationPropertiesTests.java | 17 ++++ ...fixedConfigurationPropertySourceTests.java | 89 +++++++++++++++++++ ...rableConfigurationPropertySourceTests.java | 41 +++++++++ ...tySourceEnvironmentPostProcessorTests.java | 32 +++++-- 14 files changed, 416 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySource.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/Prefixed.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySourceTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySourceTests.java diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index cfd45fd3f34..17649eb7b4b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -996,6 +996,14 @@ The `+random.int*+` syntax is `OPEN value (,max) CLOSE` where the `OPEN,CLOSE` a If `max` is provided, then `value` is the minimum value and `max` is the maximum value (exclusive). +[[boot-features-external-config-system-environment]] +=== Configuring System Environment Properties +Spring Boot supports setting a prefix for environment properties. +This is useful if the system environment is shared by multiple Spring Boot applications with different configuration requirements. +The prefix for system environment properties can be set directly on `SpringApplication`. + +For example, if you set the prefix to `input`, a property such as `foo.bar` will also be resolved as `input.foo.bar` in the system environment. + [[boot-features-external-config-typesafe-configuration-properties]] === Type-safe Configuration Properties diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 9d6bb30bb54..9b2b0c9b7c1 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -246,6 +246,8 @@ public class SpringApplication { private boolean lazyInitialization = false; + private String environmentPrefix; + private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT; private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT; @@ -362,6 +364,9 @@ public class SpringApplication { listeners.environmentPrepared(bootstrapContext, environment); DefaultPropertiesPropertySource.moveToEnd(environment); configureAdditionalProfiles(environment); + if (environment.getProperty("spring.main.environment-prefix") != null) { + throw new IllegalStateException("Environment prefix cannot be set via properties."); + } bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, @@ -1174,6 +1179,14 @@ public class SpringApplication { this.resourceLoader = resourceLoader; } + public String getEnvironmentPrefix() { + return this.environmentPrefix; + } + + public void setEnvironmentPrefix(String environmentPrefix) { + this.environmentPrefix = environmentPrefix; + } + /** * Sets the type of Spring {@link ApplicationContext} that will be created. If not * specified defaults to {@link #DEFAULT_SERVLET_WEB_CONTEXT_CLASS} for web based diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java index cc4d1cc1061..1f87dc55267 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java @@ -74,6 +74,15 @@ public interface ConfigurationPropertySource { return new AliasedConfigurationPropertySource(this, aliases); } + /** + * Return a variant of this source that supports a prefix. + * @param prefix the prefix for properties in the source + * @return a {@link ConfigurationPropertySource} instance supporting a prefix + */ + default ConfigurationPropertySource withPrefix(String prefix) { + return new PrefixedConfigurationPropertySource(this, prefix); + } + /** * Return the underlying source that is actually providing the properties. * @return the underlying property source or {@code null}. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java index f60ac9ba7ab..1f07a5d3e42 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java @@ -73,4 +73,9 @@ public interface IterableConfigurationPropertySource return new AliasedIterableConfigurationPropertySource(this, aliases); } + @Override + default IterableConfigurationPropertySource withPrefix(String prefix) { + return new PrefixedIterableConfigurationPropertySource(this, prefix); + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java new file mode 100644 index 00000000000..e6f5db2db03 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.source; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A {@link ConfigurationPropertySource} supporting a prefix. + * + * @author Madhura Bhave + */ +class PrefixedConfigurationPropertySource implements ConfigurationPropertySource { + + private final ConfigurationPropertySource source; + + private final String prefix; + + PrefixedConfigurationPropertySource(ConfigurationPropertySource source, String prefix) { + Assert.notNull(source, "Source must not be null"); + Assert.notNull(prefix, "Prefix must not be null"); + this.source = source; + this.prefix = prefix; + } + + @Override + public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + ConfigurationProperty configurationProperty = this.source.getConfigurationProperty(getPrefixedName(name)); + if (configurationProperty == null) { + return null; + } + return ConfigurationProperty.of(name, configurationProperty.getValue(), configurationProperty.getOrigin()); + } + + private ConfigurationPropertyName getPrefixedName(ConfigurationPropertyName name) { + String prefix = (StringUtils.hasText(this.prefix)) ? this.prefix + "." : ""; + return ConfigurationPropertyName.of(prefix + name); + } + + @Override + public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) { + return this.source.containsDescendantOf(getPrefixedName(name)); + } + + @Override + public Object getUnderlyingSource() { + return this.source.getUnderlyingSource(); + } + + protected ConfigurationPropertySource getSource() { + return this.source; + } + + protected String getPrefix() { + return this.prefix; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySource.java new file mode 100644 index 00000000000..dc82761d226 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.source; + +import java.util.stream.Stream; + +/** + * An iterable {@link PrefixedConfigurationPropertySource}. + * + * @author Madhura Bhave + */ +class PrefixedIterableConfigurationPropertySource extends PrefixedConfigurationPropertySource + implements IterableConfigurationPropertySource { + + PrefixedIterableConfigurationPropertySource(IterableConfigurationPropertySource source, String prefix) { + super(source, prefix); + } + + @Override + public Stream stream() { + ConfigurationPropertyName prefix = ConfigurationPropertyName.of(getPrefix()); + return getSource().stream().map((propertyName) -> { + if (prefix.isAncestorOf(propertyName)) { + String name = propertyName.toString(); + return ConfigurationPropertyName.of(name.substring(getPrefix().length() + 1)); + } + return propertyName; + }); + } + + @Override + protected IterableConfigurationPropertySource getSource() { + return (IterableConfigurationPropertySource) super.getSource(); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java index 6f9051965f5..117b615bfff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java @@ -24,6 +24,7 @@ import java.util.NoSuchElementException; import java.util.Random; import java.util.function.Function; +import org.springframework.boot.env.Prefixed; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; @@ -63,6 +64,9 @@ class SpringConfigurationPropertySources implements Iterable propertySource = environment.getPropertySources().get(sourceName); if (propertySource != null) { - replacePropertySource(environment, sourceName, propertySource); + replacePropertySource(environment, sourceName, propertySource, application.getEnvironmentPrefix()); } } @SuppressWarnings("unchecked") private void replacePropertySource(ConfigurableEnvironment environment, String sourceName, - PropertySource propertySource) { + PropertySource propertySource, String environmentPrefix) { Map originalSource = (Map) propertySource.getSource(); - SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName, - originalSource); + SystemEnvironmentPropertySource source = new OriginAndPrefixAwareSystemEnvironmentPropertySource(sourceName, + originalSource, environmentPrefix); environment.getPropertySources().replace(sourceName, source); } @@ -76,11 +77,36 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements /** * {@link SystemEnvironmentPropertySource} that also tracks {@link Origin}. */ - protected static class OriginAwareSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource - implements OriginLookup { + protected static class OriginAndPrefixAwareSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource + implements OriginLookup, Prefixed { - OriginAwareSystemEnvironmentPropertySource(String name, Map source) { + private final String environmentPrefix; + + OriginAndPrefixAwareSystemEnvironmentPropertySource(String name, Map source, + String environmentPrefix) { super(name, source); + this.environmentPrefix = getEnvironmentPrefix(environmentPrefix); + } + + private String getEnvironmentPrefix(String environmentPrefix) { + String prefix = environmentPrefix; + if (!StringUtils.hasText(environmentPrefix)) { + return ""; + } + if (environmentPrefix.endsWith(".") || environmentPrefix.endsWith("_") || environmentPrefix.endsWith("-")) { + prefix = environmentPrefix.substring(0, environmentPrefix.length() - 1); + } + return prefix; + } + + @Override + public boolean containsProperty(String name) { + return super.containsProperty(name); + } + + @Override + public Object getProperty(String name) { + return super.getProperty(name); } @Override @@ -92,6 +118,11 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements return null; } + @Override + public String getPrefix() { + return this.environmentPrefix; + } + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index b6cf0af4ece..1b04c70fdcf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -1233,6 +1233,21 @@ class SpringApplicationTests { assertThat(applicationContext.getBean("test")).isEqualTo("boot"); } + @Test + void settingEnvironmentPrefixViaPropertiesThrowsException() { + assertThatIllegalStateException() + .isThrownBy(() -> new SpringApplication().run("--spring.main.environment-prefix=my")); + } + + @Test + void bindsEnvironmentPrefixToSpringApplication() { + SpringApplication application = new SpringApplication(ExampleConfig.class); + application.setEnvironmentPrefix("my"); + application.setWebApplicationType(WebApplicationType.NONE); + this.context = application.run(); + assertThat(application.getEnvironmentPrefix()).isEqualTo("my"); + } + private ArgumentMatcher isAvailabilityChangeEventWithState( S state) { return (argument) -> (argument instanceof AvailabilityChangeEvent) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 10f764cf0eb..4304eaaf208 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -53,6 +53,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.SpringApplication; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.validation.BindValidationException; @@ -473,6 +474,22 @@ class ConfigurationPropertiesTests { assertThat(bean.getBar()).isEqualTo("baz"); } + @Test + @SuppressWarnings("unchecked") + void loadWhenEnvironmentPrefixSetShouldBind() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + sources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, + new SystemEnvironmentPropertySource(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, + Collections.singletonMap("MY_SPRING_FOO_NAME", "Jane"))); + SpringApplication application = new SpringApplication(PrefixConfiguration.class); + application.setApplicationContextFactory((webApplicationType) -> ConfigurationPropertiesTests.this.context); + application.setEnvironmentPrefix("my"); + application.setEnvironment(this.context.getEnvironment()); + application.run(); + BasicProperties bean = this.context.getBean(BasicProperties.class); + assertThat(bean.name).isEqualTo("Jane"); + } + @Test void loadWhenOverridingPropertiesShouldBind() { MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySourceTests.java new file mode 100644 index 00000000000..ecaa6772d60 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySourceTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.source; + +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PrefixedConfigurationPropertySource}. + * + * @author Madhura Bhave + */ +class PrefixedConfigurationPropertySourceTests { + + @Test + void getConfigurationPropertyShouldConsiderPrefix() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("my.foo.bar", "bing"); + source.put("my.foo.baz", "biff"); + ConfigurationPropertySource prefixed = source.nonIterable().withPrefix("my"); + assertThat(getName(prefixed, "foo.bar").toString()).isEqualTo("foo.bar"); + assertThat(getValue(prefixed, "foo.bar")).isEqualTo("bing"); + assertThat(getName(prefixed, "foo.baz").toString()).isEqualTo("foo.baz"); + assertThat(getValue(prefixed, "foo.baz")).isEqualTo("biff"); + } + + @Test + void containsDescendantOfWhenSourceReturnsUnknownShouldReturnUnknown() { + ConfigurationPropertyName name = ConfigurationPropertyName.of("my.foo"); + ConfigurationPropertySource source = mock(ConfigurationPropertySource.class, Answers.CALLS_REAL_METHODS); + given(source.containsDescendantOf(name)).willReturn(ConfigurationPropertyState.UNKNOWN); + ConfigurationPropertySource prefixed = source.withPrefix("my"); + assertThat(prefixed.containsDescendantOf(ConfigurationPropertyName.of("foo"))) + .isEqualTo(ConfigurationPropertyState.UNKNOWN); + } + + @Test + void containsDescendantOfWhenSourceReturnsPresentShouldReturnPresent() { + ConfigurationPropertyName name = ConfigurationPropertyName.of("my.foo"); + ConfigurationPropertySource source = mock(ConfigurationPropertySource.class, Answers.CALLS_REAL_METHODS); + given(source.containsDescendantOf(name)).willReturn(ConfigurationPropertyState.PRESENT); + given(source.containsDescendantOf(ConfigurationPropertyName.of("bar"))) + .willReturn(ConfigurationPropertyState.UNKNOWN); + ConfigurationPropertySource prefixed = source.withPrefix("my"); + assertThat(prefixed.containsDescendantOf(ConfigurationPropertyName.of("foo"))) + .isEqualTo(ConfigurationPropertyState.PRESENT); + } + + @Test + void containsDescendantOfWhenSourceReturnsAbsentShouldReturnAbsent() { + ConfigurationPropertyName name = ConfigurationPropertyName.of("my.foo"); + ConfigurationPropertySource source = mock(ConfigurationPropertySource.class, Answers.CALLS_REAL_METHODS); + given(source.containsDescendantOf(name)).willReturn(ConfigurationPropertyState.ABSENT); + given(source.containsDescendantOf(ConfigurationPropertyName.of("bar"))) + .willReturn(ConfigurationPropertyState.ABSENT); + ConfigurationPropertySource prefixed = source.withPrefix("my"); + assertThat(prefixed.containsDescendantOf(ConfigurationPropertyName.of("foo"))) + .isEqualTo(ConfigurationPropertyState.ABSENT); + } + + private ConfigurationPropertyName getName(ConfigurationPropertySource source, String name) { + ConfigurationProperty property = source.getConfigurationProperty(ConfigurationPropertyName.of(name)); + return (property != null) ? property.getName() : null; + } + + private Object getValue(ConfigurationPropertySource source, String name) { + ConfigurationProperty property = source.getConfigurationProperty(ConfigurationPropertyName.of(name)); + return (property != null) ? property.getValue() : null; + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySourceTests.java new file mode 100644 index 00000000000..cf91c994d01 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PrefixedIterableConfigurationPropertySourceTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.properties.source; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PrefixedIterableConfigurationPropertySource}. + * + * @author Madhura Bhave + */ +class PrefixedIterableConfigurationPropertySourceTests { + + @Test + void streamShouldConsiderPrefix() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("my.foo.bar", "bing"); + source.put("my.foo.baz", "biff"); + source.put("hello.bing", "blah"); + IterableConfigurationPropertySource prefixed = source.withPrefix("my"); + assertThat(prefixed.stream()).containsExactly(ConfigurationPropertyName.of("foo.bar"), + ConfigurationPropertyName.of("foo.baz"), ConfigurationPropertyName.of("hello.bing")); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java index 982340fdb7d..b0ab37c04d9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java @@ -21,7 +21,8 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor.OriginAwareSystemEnvironmentPropertySource; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor.OriginAndPrefixAwareSystemEnvironmentPropertySource; import org.springframework.boot.origin.SystemEnvironmentOrigin; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; @@ -39,12 +40,14 @@ class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { private final ConfigurableEnvironment environment = new StandardEnvironment(); + private final SpringApplication application = new SpringApplication(); + @Test void postProcessShouldReplaceSystemEnvironmentPropertySource() { SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); - postProcessor.postProcessEnvironment(this.environment, null); + postProcessor.postProcessEnvironment(this.environment, this.application); PropertySource replaced = this.environment.getPropertySources().get("systemEnvironment"); - assertThat(replaced).isInstanceOf(OriginAwareSystemEnvironmentPropertySource.class); + assertThat(replaced).isInstanceOf(OriginAndPrefixAwareSystemEnvironmentPropertySource.class); } @Test @@ -52,8 +55,8 @@ class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { void replacedPropertySourceShouldBeOriginAware() { SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); PropertySource original = this.environment.getPropertySources().get("systemEnvironment"); - postProcessor.postProcessEnvironment(this.environment, null); - OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment + postProcessor.postProcessEnvironment(this.environment, this.application); + OriginAndPrefixAwareSystemEnvironmentPropertySource replaced = (OriginAndPrefixAwareSystemEnvironmentPropertySource) this.environment .getPropertySources().get("systemEnvironment"); Map originalMap = (Map) original.getSource(); Map replacedMap = replaced.getSource(); @@ -67,8 +70,8 @@ class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { @Test void replacedPropertySourceWhenPropertyAbsentShouldReturnNullOrigin() { SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); - postProcessor.postProcessEnvironment(this.environment, null); - OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment + postProcessor.postProcessEnvironment(this.environment, this.application); + OriginAndPrefixAwareSystemEnvironmentPropertySource replaced = (OriginAndPrefixAwareSystemEnvironmentPropertySource) this.environment .getPropertySources().get("systemEnvironment"); assertThat(replaced.getOrigin("NON_EXISTENT")).isNull(); } @@ -79,12 +82,23 @@ class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { Map source = Collections.singletonMap("FOO_BAR_BAZ", "hello"); this.environment.getPropertySources().replace("systemEnvironment", new SystemEnvironmentPropertySource("systemEnvironment", source)); - postProcessor.postProcessEnvironment(this.environment, null); - OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) this.environment + postProcessor.postProcessEnvironment(this.environment, this.application); + OriginAndPrefixAwareSystemEnvironmentPropertySource replaced = (OriginAndPrefixAwareSystemEnvironmentPropertySource) this.environment .getPropertySources().get("systemEnvironment"); SystemEnvironmentOrigin origin = (SystemEnvironmentOrigin) replaced.getOrigin("foo.bar.baz"); assertThat(origin.getProperty()).isEqualTo("FOO_BAR_BAZ"); assertThat(replaced.getProperty("foo.bar.baz")).isEqualTo("hello"); } + @Test + void propertySourceShouldBePrefixed() { + SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); + SpringApplication application = new SpringApplication(); + application.setEnvironmentPrefix("my"); + postProcessor.postProcessEnvironment(this.environment, application); + OriginAndPrefixAwareSystemEnvironmentPropertySource replaced = (OriginAndPrefixAwareSystemEnvironmentPropertySource) this.environment + .getPropertySources().get("systemEnvironment"); + assertThat(replaced.getPrefix()).isEqualTo("my"); + } + }