From 6f022565a5e823166c65efc3d35386c17ad407fa Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Fri, 26 May 2017 10:01:34 -0700 Subject: [PATCH] Make SystemEnvironmentPropertySource origin aware Closes gh-9252 --- .../OriginTrackedSystemPropertySource.java | 63 +++++++++++++++ ...ropertySourceEnvironmentPostProcessor.java | 81 +++++++++++++++++++ .../boot/origin/SystemEnvironmentOrigin.java | 67 +++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- ...riginTrackedSystemPropertySourceTests.java | 78 ++++++++++++++++++ ...tySourceEnvironmentPostProcessorTests.java | 70 ++++++++++++++++ .../origin/SystemEnvironmentOriginTests.java | 58 +++++++++++++ 7 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedSystemPropertySource.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/origin/SystemEnvironmentOrigin.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedSystemPropertySourceTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/origin/SystemEnvironmentOriginTests.java diff --git a/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedSystemPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedSystemPropertySource.java new file mode 100644 index 0000000000..24f9b52fba --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedSystemPropertySource.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.env; + +import java.util.Map; + +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginLookup; +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.core.env.SystemEnvironmentPropertySource; + +/** + * {@link SystemEnvironmentPropertySource} and {@link OriginLookup} backed by a map + * containing {@link OriginTrackedValue OriginTrackedValues}. + * + * @author Madhura Bhave + * @since 2.0.0 + */ +public class OriginTrackedSystemPropertySource extends SystemEnvironmentPropertySource implements OriginLookup { + + /** + * Create a new {@code OriginTrackedSystemPropertySource} with the given name and + * delegating to the given {@code MapPropertySource}. + * @param name the property name + * @param source the underlying map + */ + public OriginTrackedSystemPropertySource(String name, Map source) { + super(name, source); + } + + @Override + public Object getProperty(String name) { + Object value = super.getProperty(name); + if (value instanceof OriginTrackedValue) { + return ((OriginTrackedValue) value).getValue(); + } + return value; + } + + @Override + public Origin getOrigin(String key) { + Object value = super.getProperty(key); + if (value instanceof OriginTrackedValue) { + return ((OriginTrackedValue) value).getOrigin(); + } + return null; + } +} + diff --git a/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java new file mode 100644 index 0000000000..00a776c27f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.env; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.boot.origin.SystemEnvironmentOrigin; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.env.SystemEnvironmentPropertySource; + +/** + * An {@link EnvironmentPostProcessor} that replaces the systemEnvironment {@link SystemEnvironmentPropertySource} + * with an {@link OriginTrackedSystemPropertySource} that can track the {@link SystemEnvironmentOrigin} for + * every system environment property. + * + * @author Madhura Bhave + * @since 2.0.0 + */ +public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { + + /** + * The default order for the processor. + */ + public static final int DEFAULT_ORDER = SpringApplicationJsonEnvironmentPostProcessor.DEFAULT_ORDER - 1; + + private int order = DEFAULT_ORDER; + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; + PropertySource propertySource = environment.getPropertySources().get(sourceName); + if (propertySource != null) { + replacePropertySource(environment, sourceName, propertySource); + } + } + + @SuppressWarnings("unchecked") + private void replacePropertySource(ConfigurableEnvironment environment, String sourceName, PropertySource propertySource) { + if (propertySource.getSource() instanceof Map) { + Map originalSource = (Map) propertySource.getSource(); + Map originTrackedSource = new LinkedHashMap<>(originalSource); + originTrackedSource.entrySet().forEach(e -> e.setValue(OriginTrackedValue.of(e.getValue(), + new SystemEnvironmentOrigin(e.getKey())))); + OriginTrackedSystemPropertySource source = new OriginTrackedSystemPropertySource(sourceName, + Collections.unmodifiableMap(originTrackedSource)); + environment.getPropertySources().replace(sourceName, source); + } + } + + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + +} + diff --git a/spring-boot/src/main/java/org/springframework/boot/origin/SystemEnvironmentOrigin.java b/spring-boot/src/main/java/org/springframework/boot/origin/SystemEnvironmentOrigin.java new file mode 100644 index 0000000000..08d4593265 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/origin/SystemEnvironmentOrigin.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.origin; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * {@link Origin} for an item loaded from the system environment. Provides access to the + * original property name. + * + * @author Madhura Bhave + * @since 2.0.0 + */ +public class SystemEnvironmentOrigin implements Origin { + + private final String property; + + public SystemEnvironmentOrigin(String property) { + Assert.notNull(property, "Property name must not be null"); + Assert.hasText(property, "Property name must not be empty"); + this.property = property; + } + + public String getProperty() { + return this.property; + } + + @Override + public String toString() { + return "System Environment Property \"" + this.property + "\""; + } + + @Override + public int hashCode() { + int result = ObjectUtils.nullSafeHashCode(this.property); + result = 31 * result + ObjectUtils.nullSafeHashCode(this.property); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SystemEnvironmentOrigin other = (SystemEnvironmentOrigin) obj; + return ObjectUtils.nullSafeEquals(this.property, other.property); + } +} + diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index f3fdef0dcb..1207599081 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -33,7 +33,8 @@ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ -org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor +org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ +org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ diff --git a/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedSystemPropertySourceTests.java b/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedSystemPropertySourceTests.java new file mode 100644 index 0000000000..75e4d1c9f2 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedSystemPropertySourceTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.env; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.boot.origin.Origin; +import org.springframework.boot.origin.OriginTrackedValue; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OriginTrackedSystemPropertySource}. + * + * @author Madhura Bhave + */ +public class OriginTrackedSystemPropertySourceTests { + + private Map map = new LinkedHashMap<>(); + + private OriginTrackedSystemPropertySource source = new OriginTrackedSystemPropertySource( + "test", this.map); + + private Origin origin = mock(Origin.class); + + @Test + public void getPropertyWhenMissingShouldReturnNull() throws Exception { + assertThat(this.source.getProperty("test")).isNull(); + } + + @Test + public void getPropertyWhenNonTrackedShouldReturnValue() throws Exception { + this.map.put("test", "foo"); + assertThat(this.source.getProperty("test")).isEqualTo("foo"); + } + + @Test + public void getPropertyWhenTrackedShouldReturnValue() throws Exception { + this.map.put("test", OriginTrackedValue.of("foo", this.origin)); + assertThat(this.source.getProperty("test")).isEqualTo("foo"); + } + + @Test + public void getPropertyOriginWhenMissingShouldReturnNull() throws Exception { + assertThat(this.source.getOrigin("test")).isNull(); + } + + @Test + public void getPropertyOriginWhenNonTrackedShouldReturnNull() throws Exception { + this.map.put("test", "foo"); + assertThat(this.source.getOrigin("test")).isNull(); + } + + @Test + public void getPropertyOriginWhenTrackedShouldReturnOrigin() throws Exception { + this.map.put("test", OriginTrackedValue.of("foo", this.origin)); + assertThat(this.source.getOrigin("test")).isEqualTo(this.origin); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java new file mode 100644 index 0000000000..5f711e5964 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.env; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.origin.OriginTrackedValue; +import org.springframework.boot.origin.SystemEnvironmentOrigin; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SystemEnvironmentPropertySourceEnvironmentPostProcessor}. + * + * @author Madhura Bhave + */ +public class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { + + private ConfigurableEnvironment environment; + + @Before + public void setUp() throws Exception { + this.environment = new StandardEnvironment(); + } + + @Test + public void postProcessShouldReplaceSystemEnviromentPropertySource() throws Exception { + SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); + postProcessor.postProcessEnvironment(this.environment, null); + PropertySource replaced = this.environment.getPropertySources().get("systemEnvironment"); + assertThat(replaced).isInstanceOf(OriginTrackedSystemPropertySource.class); + } + + @Test + @SuppressWarnings("unchecked") + public void replacedPropertySourceShouldHaveOriginTrackedValues() throws Exception { + SystemEnvironmentPropertySourceEnvironmentPostProcessor postProcessor = new SystemEnvironmentPropertySourceEnvironmentPostProcessor(); + PropertySource original = this.environment.getPropertySources().get("systemEnvironment"); + postProcessor.postProcessEnvironment(this.environment, null); + PropertySource replaced = this.environment.getPropertySources().get("systemEnvironment"); + Map originalMap = (Map) original.getSource(); + Map replacedMap = (Map) replaced.getSource(); + for (Map.Entry entry : originalMap.entrySet()) { + OriginTrackedValue actual = replacedMap.get(entry.getKey()); + assertThat(actual.getValue()).isEqualTo(entry.getValue()); + assertThat(actual.getOrigin()).isEqualTo(new SystemEnvironmentOrigin(entry.getKey())); + } + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/origin/SystemEnvironmentOriginTests.java b/spring-boot/src/test/java/org/springframework/boot/origin/SystemEnvironmentOriginTests.java new file mode 100644 index 0000000000..8b4b769cdb --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/origin/SystemEnvironmentOriginTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.origin; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SystemEnvironmentOrigin}. + * + * @author Madhura Bhave + */ +public class SystemEnvironmentOriginTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void createWhenPropertyIsNullShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + new SystemEnvironmentOrigin(null); + } + + @Test + public void createWhenPropertyNameIsEmptyShouldThrowException() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + new SystemEnvironmentOrigin(""); + } + + @Test + public void getPropertyShouldReturnProperty() throws Exception { + SystemEnvironmentOrigin origin = new SystemEnvironmentOrigin("FOO_BAR"); + assertThat(origin.getProperty()).isEqualTo("FOO_BAR"); + } + + @Test + public void toStringShouldReturnStringWithDetails() throws Exception { + SystemEnvironmentOrigin origin = new SystemEnvironmentOrigin("FOO_BAR"); + assertThat(origin.toString()).isEqualTo("System Environment Property \"FOO_BAR\""); + } +}