diff --git a/org.springframework.core/src/main/java/org/springframework/util/PropertyPlaceholderUtils.java b/org.springframework.core/src/main/java/org/springframework/util/PropertyPlaceholderUtils.java index 4a0da6004a8..51d50f10de1 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/PropertyPlaceholderUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/PropertyPlaceholderUtils.java @@ -17,6 +17,8 @@ package org.springframework.util; 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 @@ -63,29 +65,75 @@ public class PropertyPlaceholderUtils { * @return the supplied value with placeholders replaced inline. */ public static String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { - StringBuilder result = new StringBuilder(value); + return parseStringValue(value, placeholderResolver, new HashSet()); + } - int startIndex = result.indexOf(PLACEHOLDER_PREFIX); + protected static String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) { + StringBuilder buf = new StringBuilder(strVal); + + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); while (startIndex != -1) { - int endIndex = result.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length()); + int endIndex = findPlaceholderEndIndex(buf, startIndex); if (endIndex != -1) { - String placeholder = result.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); - int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length(); + String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + 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); if (propVal != null) { - result.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal); - nextIndex = startIndex + propVal.length(); + // Recursive invocation, parsing placeholders contained in the + // 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 { 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; } /** diff --git a/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java index 61c2c90f10a..9cd11537ac5 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java @@ -16,17 +16,19 @@ package org.springframework.util; +import org.springframework.util.PropertyPlaceholderUtils.PlaceholderResolver; + /** * Helper class for resolving placeholders in texts. Usually applied to file paths. * - *

A text may contain ${...} placeholders, to be resolved as - * system properties: e.g. ${user.dir}. + *

A text may contain ${...} placeholders, to be resolved as system properties: e.g. + * ${user.dir}. * * @author Juergen Hoeller - * @since 1.2.5 * @see #PLACEHOLDER_PREFIX * @see #PLACEHOLDER_SUFFIX * @see System#getProperty(String) + * @since 1.2.5 */ public abstract class SystemPropertyUtils { @@ -36,51 +38,39 @@ public abstract class SystemPropertyUtils { /** Suffix for system property placeholders: "}" */ public static final String PLACEHOLDER_SUFFIX = "}"; - /** - * Resolve ${...} placeholders in the given text, - * replacing them with corresponding system property values. + * Resolve ${...} placeholders in the given text, replacing them with corresponding system property values. + * * @param text the String to resolve * @return the resolved String * @see #PLACEHOLDER_PREFIX * @see #PLACEHOLDER_SUFFIX */ - public static String resolvePlaceholders(String text) { - StringBuilder result = new StringBuilder(text); + public static String resolvePlaceholders(final String text) { + return PropertyPlaceholderUtils.replacePlaceholders(text, new PlaceholderResolver() { - int startIndex = result.indexOf(PLACEHOLDER_PREFIX); - while (startIndex != -1) { - 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(); + public String resolvePlaceholder(String placeholderName) { + String propVal = null; try { - String propVal = System.getProperty(placeholder); + propVal = System.getProperty(placeholderName); if (propVal == null) { // 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); - nextIndex = startIndex + propVal.length(); - } - else { - System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text + + + if (propVal == null) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + text + "] as system property: neither system property nor environment variable found"); } } 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); - } - startIndex = result.indexOf(PLACEHOLDER_PREFIX, nextIndex); - } - else { - startIndex = -1; - } - } - return result.toString(); + } + return propVal; + } + }); } } diff --git a/org.springframework.core/src/test/java/org/springframework/util/PropertyPlaceholderUtilsTests.java b/org.springframework.core/src/test/java/org/springframework/util/PropertyPlaceholderUtilsTests.java index a1ee3be521d..50ca7f0eed4 100644 --- a/org.springframework.core/src/test/java/org/springframework/util/PropertyPlaceholderUtilsTests.java +++ b/org.springframework.core/src/test/java/org/springframework/util/PropertyPlaceholderUtilsTests.java @@ -43,6 +43,26 @@ public class PropertyPlaceholderUtilsTests { 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 public void testWithResolver() { String text = "foo=${foo}"; diff --git a/org.springframework.core/src/test/java/org/springframework/util/SystemPropertyUtilsTests.java b/org.springframework.core/src/test/java/org/springframework/util/SystemPropertyUtilsTests.java index e5ad34b6b1c..e02e2d6a6bc 100644 --- a/org.springframework.core/src/test/java/org/springframework/util/SystemPropertyUtilsTests.java +++ b/org.springframework.core/src/test/java/org/springframework/util/SystemPropertyUtilsTests.java @@ -31,6 +31,14 @@ public class SystemPropertyUtilsTests { 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 public void testReplaceFromEnv() { Map env = System.getenv();