Skip to content

Commit d16faaf

Browse files
author
Rob Harrop
committed
[SPR-6025] support for recursive property placeholder replacement in system properties
1 parent 3fe09d7 commit d16faaf

File tree

4 files changed

+105
-39
lines changed

4 files changed

+105
-39
lines changed

org.springframework.core/src/main/java/org/springframework/util/PropertyPlaceholderUtils.java

+57-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.util;
1818

1919
import java.util.Properties;
20+
import java.util.Set;
21+
import java.util.HashSet;
2022

2123
/**
2224
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
@@ -63,29 +65,75 @@ public String resolvePlaceholder(String placeholderName) {
6365
* @return the supplied value with placeholders replaced inline.
6466
*/
6567
public static String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
66-
StringBuilder result = new StringBuilder(value);
68+
return parseStringValue(value, placeholderResolver, new HashSet<String>());
69+
}
70+
71+
protected static String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
72+
StringBuilder buf = new StringBuilder(strVal);
6773

68-
int startIndex = result.indexOf(PLACEHOLDER_PREFIX);
74+
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
6975
while (startIndex != -1) {
70-
int endIndex = result.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
76+
int endIndex = findPlaceholderEndIndex(buf, startIndex);
7177
if (endIndex != -1) {
72-
String placeholder = result.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
73-
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
78+
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
79+
if (!visitedPlaceholders.add(placeholder)) {
80+
throw new IllegalArgumentException(
81+
"Circular placeholder reference '" + placeholder + "' in property definitions");
82+
}
83+
// Recursive invocation, parsing placeholders contained in the placeholder key.
84+
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
7485

86+
// Now obtain the value for the fully resolved key...
7587
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
7688
if (propVal != null) {
77-
result.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
78-
nextIndex = startIndex + propVal.length();
89+
// Recursive invocation, parsing placeholders contained in the
90+
// previously resolved placeholder value.
91+
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
92+
buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
93+
94+
//if (logger.isTraceEnabled()) {
95+
// logger.trace("Resolved placeholder '" + placeholder + "'");
96+
//}
97+
98+
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
99+
}
100+
else {
101+
// Proceed with unprocessed value.
102+
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
79103
}
80104

81-
startIndex = result.indexOf(PLACEHOLDER_PREFIX, nextIndex);
105+
visitedPlaceholders.remove(placeholder);
82106
}
83107
else {
84108
startIndex = -1;
85109
}
86110
}
87111

88-
return result.toString();
112+
return buf.toString();
113+
}
114+
115+
private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
116+
int index = startIndex + PLACEHOLDER_PREFIX.length();
117+
int withinNestedPlaceholder = 0;
118+
while (index < buf.length()) {
119+
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
120+
if (withinNestedPlaceholder > 0) {
121+
withinNestedPlaceholder--;
122+
index = index + PLACEHOLDER_PREFIX.length() - 1;
123+
}
124+
else {
125+
return index;
126+
}
127+
}
128+
else if (StringUtils.substringMatch(buf, index, PLACEHOLDER_PREFIX)) {
129+
withinNestedPlaceholder++;
130+
index = index + PLACEHOLDER_PREFIX.length();
131+
}
132+
else {
133+
index++;
134+
}
135+
}
136+
return -1;
89137
}
90138

91139
/**

org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java

+20-30
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@
1616

1717
package org.springframework.util;
1818

19+
import org.springframework.util.PropertyPlaceholderUtils.PlaceholderResolver;
20+
1921
/**
2022
* Helper class for resolving placeholders in texts. Usually applied to file paths.
2123
*
22-
* <p>A text may contain <code>${...}</code> placeholders, to be resolved as
23-
* system properties: e.g. <code>${user.dir}</code>.
24+
* <p>A text may contain <code>${...}</code> placeholders, to be resolved as system properties: e.g.
25+
* <code>${user.dir}</code>.
2426
*
2527
* @author Juergen Hoeller
26-
* @since 1.2.5
2728
* @see #PLACEHOLDER_PREFIX
2829
* @see #PLACEHOLDER_SUFFIX
2930
* @see System#getProperty(String)
31+
* @since 1.2.5
3032
*/
3133
public abstract class SystemPropertyUtils {
3234

@@ -36,51 +38,39 @@ public abstract class SystemPropertyUtils {
3638
/** Suffix for system property placeholders: "}" */
3739
public static final String PLACEHOLDER_SUFFIX = "}";
3840

39-
4041
/**
41-
* Resolve ${...} placeholders in the given text,
42-
* replacing them with corresponding system property values.
42+
* Resolve ${...} placeholders in the given text, replacing them with corresponding system property values.
43+
*
4344
* @param text the String to resolve
4445
* @return the resolved String
4546
* @see #PLACEHOLDER_PREFIX
4647
* @see #PLACEHOLDER_SUFFIX
4748
*/
48-
public static String resolvePlaceholders(String text) {
49-
StringBuilder result = new StringBuilder(text);
49+
public static String resolvePlaceholders(final String text) {
50+
return PropertyPlaceholderUtils.replacePlaceholders(text, new PlaceholderResolver() {
5051

51-
int startIndex = result.indexOf(PLACEHOLDER_PREFIX);
52-
while (startIndex != -1) {
53-
int endIndex = result.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
54-
if (endIndex != -1) {
55-
String placeholder = result.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
56-
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
52+
public String resolvePlaceholder(String placeholderName) {
53+
String propVal = null;
5754
try {
58-
String propVal = System.getProperty(placeholder);
55+
propVal = System.getProperty(placeholderName);
5956
if (propVal == null) {
6057
// Fall back to searching the system environment.
61-
propVal = System.getenv(placeholder);
62-
}
63-
if (propVal != null) {
64-
result.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
65-
nextIndex = startIndex + propVal.length();
58+
propVal = System.getenv(placeholderName);
6659
}
67-
else {
68-
System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
60+
61+
if (propVal == null) {
62+
System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + text +
6963
"] as system property: neither system property nor environment variable found");
7064
}
7165
}
7266
catch (Throwable ex) {
73-
System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
67+
System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + text +
7468
"] as system property: " + ex);
69+
7570
}
76-
startIndex = result.indexOf(PLACEHOLDER_PREFIX, nextIndex);
71+
return propVal;
7772
}
78-
else {
79-
startIndex = -1;
80-
}
81-
}
82-
83-
return result.toString();
73+
});
8474
}
8575

8676
}

org.springframework.core/src/test/java/org/springframework/util/PropertyPlaceholderUtilsTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,26 @@ public void testWithMultipleProperties() {
4343
assertEquals("foo=bar,bar=baz", PropertyPlaceholderUtils.replacePlaceholders(text, props));
4444
}
4545

46+
@Test
47+
public void testRecurseInProperty() {
48+
String text = "foo=${bar}";
49+
Properties props = new Properties();
50+
props.setProperty("bar", "${baz}");
51+
props.setProperty("baz", "bar");
52+
53+
assertEquals("foo=bar", PropertyPlaceholderUtils.replacePlaceholders(text, props));
54+
}
55+
56+
@Test
57+
public void testRecurseInPlaceholder() {
58+
String text = "foo=${b${inner}}";
59+
Properties props = new Properties();
60+
props.setProperty("bar", "bar");
61+
props.setProperty("inner", "ar");
62+
63+
assertEquals("foo=bar", PropertyPlaceholderUtils.replacePlaceholders(text, props));
64+
}
65+
4666
@Test
4767
public void testWithResolver() {
4868
String text = "foo=${foo}";

org.springframework.core/src/test/java/org/springframework/util/SystemPropertyUtilsTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ public void testReplaceFromSystemProperty() {
3131
assertEquals("bar", resolved);
3232
}
3333

34+
@Test
35+
public void testRecursiveFromSystemProperty() {
36+
System.setProperty("test.prop", "foo=${bar}");
37+
System.setProperty("bar", "baz");
38+
String resolved = SystemPropertyUtils.resolvePlaceholders("${test.prop}");
39+
assertEquals("foo=baz", resolved);
40+
}
41+
3442
@Test
3543
public void testReplaceFromEnv() {
3644
Map<String,String> env = System.getenv();

0 commit comments

Comments
 (0)