[SPR-6025] support for recursive property placeholder replacement in system properties
This commit is contained in:
parent
3fe09d70cd
commit
d16faafc4f
|
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.util;
|
package org.springframework.util;
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
|
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
|
||||||
|
|
@ -63,29 +65,75 @@ public class PropertyPlaceholderUtils {
|
||||||
* @return the supplied value with placeholders replaced inline.
|
* @return the supplied value with placeholders replaced inline.
|
||||||
*/
|
*/
|
||||||
public static String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
|
public static String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
|
||||||
StringBuilder result = new StringBuilder(value);
|
return parseStringValue(value, placeholderResolver, new HashSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
int startIndex = result.indexOf(PLACEHOLDER_PREFIX);
|
protected static String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
|
||||||
|
StringBuilder buf = new StringBuilder(strVal);
|
||||||
|
|
||||||
|
int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
|
||||||
while (startIndex != -1) {
|
while (startIndex != -1) {
|
||||||
int endIndex = result.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
|
int endIndex = findPlaceholderEndIndex(buf, startIndex);
|
||||||
if (endIndex != -1) {
|
if (endIndex != -1) {
|
||||||
String placeholder = result.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||||
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
|
if (!visitedPlaceholders.add(placeholder)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Circular placeholder reference '" + placeholder + "' in property definitions");
|
||||||
|
}
|
||||||
|
// Recursive invocation, parsing placeholders contained in the placeholder key.
|
||||||
|
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
|
||||||
|
|
||||||
|
// Now obtain the value for the fully resolved key...
|
||||||
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
|
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
|
||||||
if (propVal != null) {
|
if (propVal != null) {
|
||||||
result.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
|
// Recursive invocation, parsing placeholders contained in the
|
||||||
nextIndex = startIndex + propVal.length();
|
// previously resolved placeholder value.
|
||||||
|
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
|
||||||
|
buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
|
||||||
|
|
||||||
|
//if (logger.isTraceEnabled()) {
|
||||||
|
// logger.trace("Resolved placeholder '" + placeholder + "'");
|
||||||
|
//}
|
||||||
|
|
||||||
|
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Proceed with unprocessed value.
|
||||||
|
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex = result.indexOf(PLACEHOLDER_PREFIX, nextIndex);
|
visitedPlaceholders.remove(placeholder);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
startIndex = -1;
|
startIndex = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.toString();
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
|
||||||
|
int index = startIndex + PLACEHOLDER_PREFIX.length();
|
||||||
|
int withinNestedPlaceholder = 0;
|
||||||
|
while (index < buf.length()) {
|
||||||
|
if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
|
||||||
|
if (withinNestedPlaceholder > 0) {
|
||||||
|
withinNestedPlaceholder--;
|
||||||
|
index = index + PLACEHOLDER_PREFIX.length() - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (StringUtils.substringMatch(buf, index, PLACEHOLDER_PREFIX)) {
|
||||||
|
withinNestedPlaceholder++;
|
||||||
|
index = index + PLACEHOLDER_PREFIX.length();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,19 @@
|
||||||
|
|
||||||
package org.springframework.util;
|
package org.springframework.util;
|
||||||
|
|
||||||
|
import org.springframework.util.PropertyPlaceholderUtils.PlaceholderResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for resolving placeholders in texts. Usually applied to file paths.
|
* Helper class for resolving placeholders in texts. Usually applied to file paths.
|
||||||
*
|
*
|
||||||
* <p>A text may contain <code>${...}</code> placeholders, to be resolved as
|
* <p>A text may contain <code>${...}</code> placeholders, to be resolved as system properties: e.g.
|
||||||
* system properties: e.g. <code>${user.dir}</code>.
|
* <code>${user.dir}</code>.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @since 1.2.5
|
|
||||||
* @see #PLACEHOLDER_PREFIX
|
* @see #PLACEHOLDER_PREFIX
|
||||||
* @see #PLACEHOLDER_SUFFIX
|
* @see #PLACEHOLDER_SUFFIX
|
||||||
* @see System#getProperty(String)
|
* @see System#getProperty(String)
|
||||||
|
* @since 1.2.5
|
||||||
*/
|
*/
|
||||||
public abstract class SystemPropertyUtils {
|
public abstract class SystemPropertyUtils {
|
||||||
|
|
||||||
|
|
@ -36,51 +38,39 @@ public abstract class SystemPropertyUtils {
|
||||||
/** Suffix for system property placeholders: "}" */
|
/** Suffix for system property placeholders: "}" */
|
||||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve ${...} placeholders in the given text,
|
* Resolve ${...} placeholders in the given text, replacing them with corresponding system property values.
|
||||||
* replacing them with corresponding system property values.
|
*
|
||||||
* @param text the String to resolve
|
* @param text the String to resolve
|
||||||
* @return the resolved String
|
* @return the resolved String
|
||||||
* @see #PLACEHOLDER_PREFIX
|
* @see #PLACEHOLDER_PREFIX
|
||||||
* @see #PLACEHOLDER_SUFFIX
|
* @see #PLACEHOLDER_SUFFIX
|
||||||
*/
|
*/
|
||||||
public static String resolvePlaceholders(String text) {
|
public static String resolvePlaceholders(final String text) {
|
||||||
StringBuilder result = new StringBuilder(text);
|
return PropertyPlaceholderUtils.replacePlaceholders(text, new PlaceholderResolver() {
|
||||||
|
|
||||||
int startIndex = result.indexOf(PLACEHOLDER_PREFIX);
|
public String resolvePlaceholder(String placeholderName) {
|
||||||
while (startIndex != -1) {
|
String propVal = null;
|
||||||
int endIndex = result.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
|
|
||||||
if (endIndex != -1) {
|
|
||||||
String placeholder = result.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
|
||||||
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
|
|
||||||
try {
|
try {
|
||||||
String propVal = System.getProperty(placeholder);
|
propVal = System.getProperty(placeholderName);
|
||||||
if (propVal == null) {
|
if (propVal == null) {
|
||||||
// Fall back to searching the system environment.
|
// Fall back to searching the system environment.
|
||||||
propVal = System.getenv(placeholder);
|
propVal = System.getenv(placeholderName);
|
||||||
}
|
}
|
||||||
if (propVal != null) {
|
|
||||||
result.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
|
if (propVal == null) {
|
||||||
nextIndex = startIndex + propVal.length();
|
System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + text +
|
||||||
}
|
|
||||||
else {
|
|
||||||
System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
|
|
||||||
"] as system property: neither system property nor environment variable found");
|
"] as system property: neither system property nor environment variable found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
|
System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + text +
|
||||||
"] as system property: " + ex);
|
"] as system property: " + ex);
|
||||||
}
|
|
||||||
startIndex = result.indexOf(PLACEHOLDER_PREFIX, nextIndex);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
startIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString();
|
}
|
||||||
|
return propVal;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,26 @@ public class PropertyPlaceholderUtilsTests {
|
||||||
assertEquals("foo=bar,bar=baz", PropertyPlaceholderUtils.replacePlaceholders(text, props));
|
assertEquals("foo=bar,bar=baz", PropertyPlaceholderUtils.replacePlaceholders(text, props));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecurseInProperty() {
|
||||||
|
String text = "foo=${bar}";
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty("bar", "${baz}");
|
||||||
|
props.setProperty("baz", "bar");
|
||||||
|
|
||||||
|
assertEquals("foo=bar", PropertyPlaceholderUtils.replacePlaceholders(text, props));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecurseInPlaceholder() {
|
||||||
|
String text = "foo=${b${inner}}";
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty("bar", "bar");
|
||||||
|
props.setProperty("inner", "ar");
|
||||||
|
|
||||||
|
assertEquals("foo=bar", PropertyPlaceholderUtils.replacePlaceholders(text, props));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWithResolver() {
|
public void testWithResolver() {
|
||||||
String text = "foo=${foo}";
|
String text = "foo=${foo}";
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ public class SystemPropertyUtilsTests {
|
||||||
assertEquals("bar", resolved);
|
assertEquals("bar", resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecursiveFromSystemProperty() {
|
||||||
|
System.setProperty("test.prop", "foo=${bar}");
|
||||||
|
System.setProperty("bar", "baz");
|
||||||
|
String resolved = SystemPropertyUtils.resolvePlaceholders("${test.prop}");
|
||||||
|
assertEquals("foo=baz", resolved);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReplaceFromEnv() {
|
public void testReplaceFromEnv() {
|
||||||
Map<String,String> env = System.getenv();
|
Map<String,String> env = System.getenv();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue