Ensure descendants are always recalculated on cache refresh
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run Details

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}.
*