diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java index 04ee92ff8b..f300fc045d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java @@ -18,8 +18,10 @@ package org.springframework.boot.context.properties.source; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.springframework.core.env.EnumerablePropertySource; @@ -129,7 +131,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } private Cache getCache() { - Object cacheKey = getCacheKey(); + CacheKey cacheKey = CacheKey.get(getPropertySource()); if (cacheKey == null) { return null; } @@ -137,17 +139,10 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope return this.cache; } this.cache = new Cache(); - this.cacheKey = cacheKey; + this.cacheKey = cacheKey.copy(); return this.cache; } - private Object getCacheKey() { - if (getPropertySource() instanceof MapPropertySource) { - return ((MapPropertySource) getPropertySource()).getSource().keySet(); - } - return getPropertySource().getPropertyNames(); - } - @Override protected EnumerablePropertySource getPropertySource() { return (EnumerablePropertySource) super.getPropertySource(); @@ -177,4 +172,48 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } + private static final class CacheKey { + + private Object key; + + private CacheKey(Object key) { + this.key = key; + } + + public CacheKey copy() { + return new CacheKey(copyKey(this.key)); + } + + private Object copyKey(Object key) { + if (key instanceof Set) { + return new HashSet((Set) key); + } + return ((String[]) key).clone(); + } + + @Override + public int hashCode() { + return this.key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return ObjectUtils.nullSafeEquals(this.key, ((CacheKey) obj).key); + } + + public static CacheKey get(EnumerablePropertySource source) { + if (source instanceof MapPropertySource) { + return new CacheKey(((MapPropertySource) source).getSource().keySet()); + } + return new CacheKey(source.getPropertyNames()); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java index f05cb1f5ec..276fd8ac68 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/logging/LoggingApplicationListenerTests.java @@ -76,6 +76,7 @@ import static org.hamcrest.Matchers.not; * @author Andy Wilkinson * @author Stephane Nicoll * @author Ben Hale + * @author Fahim Farook */ @RunWith(ModifiedClassPathRunner.class) @ClassPathExclusions("log4j*.jar") @@ -500,12 +501,23 @@ public class LoggingApplicationListenerTests { @Test public void environmentPropertiesIgnoreUnresolvablePlaceholders() { // gh-7719 + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, + "logging.pattern.console=console ${doesnotexist}"); + this.initializer.initialize(this.context.getEnvironment(), + this.context.getClassLoader()); + assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)) + .isEqualTo("console ${doesnotexist}"); + } + + @Test + public void environmentPropertiesResolvePlaceholders() { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "logging.pattern.console=console ${pid}"); this.initializer.initialize(this.context.getEnvironment(), this.context.getClassLoader()); assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)) - .isEqualTo("console ${pid}"); + .isEqualTo(this.context.getEnvironment() + .getProperty("logging.pattern.console")); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java index fd9a9d59b0..f75fe647f4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.mock; * * @author Phillip Webb * @author Madhura Bhave + * @author Fahim Farook */ public class SpringIterableConfigurationPropertySourceTests { @@ -157,6 +158,20 @@ public class SpringIterableConfigurationPropertySourceTests { .isEqualTo(ConfigurationPropertyState.ABSENT); } + @Test + public void propertySourceKeyDataChangeInvalidatesCache() { + // gh-13344 + Map map = new LinkedHashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + EnumerablePropertySource source = new MapPropertySource("test", map); + SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource( + source, DefaultPropertyMapper.INSTANCE); + assertThat(adapter.stream().count()).isEqualTo(2); + map.put("key3", "value3"); + assertThat(adapter.stream().count()).isEqualTo(3); + } + /** * Test {@link PropertySource} that's also an {@link OriginLookup}. */