From 569519285046967a85f20cefe4200fcfc35a21c8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 22 May 2025 09:55:22 -0700 Subject: [PATCH] Ensure descendants are always recalculated on cache refresh Fix `SpringIterableConfigurationPropertySource` to ensure that cached descendants are always updated on a refresh. The cache implementation assumes that it is safe to reuse previous `mappings` and `reverseMappings` data since it doesn't matter if superfluous values are included. For `descendants` however, we always want to recalculate values so that we don't get false positives. Unfortunately, prior to this commit, we only updated the descendants if a reverseMapping was added. The meant that on a cache refresh, existing descendants were removed. Fixes gh-45639 --- ...SpringIterableConfigurationPropertySource.java | 6 ++++-- ...gIterableConfigurationPropertySourceTests.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) 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 8e484ef3ca..44bd2e09ca 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 @@ -274,11 +274,13 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope if (configurationPropertyName != null && !configurationPropertyName.isEmpty()) { add(mappings, configurationPropertyName, propertyName); reverseMappings.put(propertyName, configurationPropertyName); - addParents(descendants, configurationPropertyName); } } } } + for (String propertyName : propertyNames) { + addParents(descendants, reverseMappings.get(propertyName)); + } ConfigurationPropertyName[] configurationPropertyNames = this.immutable ? reverseMappings.values().toArray(new ConfigurationPropertyName[0]) : null; lastUpdated = this.immutable ? null : propertyNames; @@ -296,7 +298,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } private void addParents(Set descendants, ConfigurationPropertyName name) { - if (descendants == null || name.isEmpty()) { + if (descendants == null || name == null || name.isEmpty()) { return; } ConfigurationPropertyName parent = name.getParent(); 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 0d44809834..6526034802 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 @@ -239,6 +239,21 @@ class SpringIterableConfigurationPropertySourceTests { "test.map.bravo", "test.map.charlie", "test.map.delta"); } + @Test + void cacheRefreshRecalculatesDescendants() { + // gh-45639 + Map map = new LinkedHashMap<>(); + map.put("one.two.three", "test"); + EnumerablePropertySource source = new OriginTrackedMapPropertySource("test", map, false); + SpringIterableConfigurationPropertySource propertySource = new SpringIterableConfigurationPropertySource(source, + false, DefaultPropertyMapper.INSTANCE); + assertThat(propertySource.containsDescendantOf(ConfigurationPropertyName.of("one.two"))) + .isEqualTo(ConfigurationPropertyState.PRESENT); + map.put("new", "value"); + assertThat(propertySource.containsDescendantOf(ConfigurationPropertyName.of("one.two"))) + .isEqualTo(ConfigurationPropertyState.PRESENT); + } + /** * Test {@link PropertySource} that's also an {@link OriginLookup}. *