[SPR-6025] support for recursive property placeholder replacement in system properties

This commit is contained in:
Rob Harrop 2009-09-08 17:13:02 +00:00
parent 3fe09d70cd
commit d16faafc4f
4 changed files with 106 additions and 40 deletions

View File

@ -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;
} }
/** /**

View File

@ -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;
}
});
} }
} }

View File

@ -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}";

View File

@ -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();