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
This commit is contained in:
Phillip Webb
2025-05-22 09:55:22 -07:00
parent 31f549efc6
commit 5695192850
2 changed files with 19 additions and 2 deletions

View File

@@ -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<ConfigurationPropertyName> descendants, ConfigurationPropertyName name) {
if (descendants == null || name.isEmpty()) {
if (descendants == null || name == null || name.isEmpty()) {
return;
}
ConfigurationPropertyName parent = name.getParent();

View File

@@ -239,6 +239,21 @@ class SpringIterableConfigurationPropertySourceTests {
"test.map.bravo", "test.map.charlie", "test.map.delta");
}
@Test
void cacheRefreshRecalculatesDescendants() {
// gh-45639
Map<String, Object> 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}.
*