Restore support of exact match property

This commit fixes a regression in property placeholder resolution where
the original key was no longer considered for an exact match before
processing the placeholder itself.

By default, property resolution uses ':' as the separator between the
key and the fallback value.

Consider a request to resolve ${prefix://service}. Previously,
placeholder resolution would first attempt to resolve the raw text, that
is 'prefix://service', before attempting to resolve the 'prefix' key and
then use '//service' if the key did not resolve.

This commit restores that behaviour purely for backward compatible
reason.

Closes gh-34124
This commit is contained in:
Stéphane Nicoll
2024-12-23 12:13:24 +01:00
parent ebae02a92b
commit 5ce5647d09
2 changed files with 43 additions and 4 deletions

View File

@@ -519,7 +519,7 @@ final class PlaceholderParser {
@Override
public String resolve(PartResolutionContext resolutionContext) {
String value = resolveRecursively(resolutionContext, this.key);
String value = resolveRecursively(resolutionContext);
if (value != null) {
return value;
}
@@ -528,6 +528,17 @@ final class PlaceholderParser {
}
return resolutionContext.handleUnresolvablePlaceholder(this.key, text());
}
@Nullable
private String resolveRecursively(PartResolutionContext resolutionContext) {
if (!this.text().equals(this.key)) {
String value = resolveRecursively(resolutionContext, this.text());
if (value != null) {
return value;
}
}
return resolveRecursively(resolutionContext, this.key);
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InOrder;
import org.springframework.util.PlaceholderParser.ParsedValue;
import org.springframework.util.PlaceholderParser.TextPart;
@@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -220,12 +222,37 @@ class PlaceholderParserTests {
);
}
@ParameterizedTest(name = "{0} -> {1}")
@MethodSource("exactMatchPlaceholders")
void placeholdersWithExactMatchAreConsidered(String text, String expected) {
Properties properties = new Properties();
properties.setProperty("prefix://my-service", "example-service");
properties.setProperty("px", "prefix");
properties.setProperty("p1", "${prefix://my-service}");
assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected);
}
static Stream<Arguments> exactMatchPlaceholders() {
return Stream.of(
Arguments.of("${prefix://my-service}", "example-service"),
Arguments.of("${p1}", "example-service")
);
}
@Test
void parseWithKeyEqualsToText() {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve");
assertThat(this.parser.replacePlaceholders("${firstName}", resolver))
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName");
}
@Test
void parseWithHardcodedFallback() {
PlaceholderResolver resolver = mockPlaceholderResolver();
assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver))
.isEqualTo("Steve");
verifyPlaceholderResolutions(resolver, "firstName");
verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName");
}
@Test
@@ -233,7 +260,7 @@ class PlaceholderParserTests {
PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John");
assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver))
.isEqualTo("John");
verifyPlaceholderResolutions(resolver, "invalid", "firstName");
verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName");
}
@Test
@@ -348,8 +375,9 @@ class PlaceholderParserTests {
}
void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) {
InOrder ordered = inOrder(mock);
for (String placeholder : placeholders) {
verify(mock).resolvePlaceholder(placeholder);
ordered.verify(mock).resolvePlaceholder(placeholder);
}
verifyNoMoreInteractions(mock);
}