package com.revolsys.spring.util; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; /** * BeanDefinitionVisitor that resolves placeholders in String values, delegating * to the <code>parseStringValue</code> method of the containing class. */ public class PlaceholderResolvingStringValueResolver implements StringValueResolver { private final Map<String, Object> attributes; private final boolean ignoreUnresolvablePlaceholders; private final String nullValue; private final String placeholderPrefix; private final String placeholderSuffix; public PlaceholderResolvingStringValueResolver(final String placeholderPrefix, final String placeholderSuffix, final boolean ignoreUnresolvablePlaceholders, final String nullValue, final Map<String, Object> attributes) { super(); this.placeholderPrefix = placeholderPrefix; this.placeholderSuffix = placeholderSuffix; this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; this.nullValue = nullValue; this.attributes = attributes; } private int findPlaceholderEndIndex(final CharSequence buf, final int startIndex) { int index = startIndex + this.placeholderPrefix.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, this.placeholderPrefix)) { withinNestedPlaceholder++; index = index + this.placeholderPrefix.length(); } else { index++; } } return -1; } /** * Parse the given String value recursively, to be able to resolve nested * placeholders (when resolved property values in turn contain placeholders * again). * * @param strVal the String value to parse * @param props the Properties to resolve placeholders against * @param visitedPlaceholders the placeholders that have already been visited * during the current resolution attempt (used to detect circular * references between placeholders). Only non-null if we're parsing a * nested placeholder. * @throws BeanDefinitionStoreException if invalid values are encountered * @see #resolvePlaceholder(String, java.util.Properties, int) */ protected String parseStringValue(final String strVal, final Map<String, Object> attributes, final Set<String> visitedPlaceholders) throws BeanDefinitionStoreException { final StringBuilder buf = new StringBuilder(strVal); int startIndex = strVal.indexOf(this.placeholderPrefix); while (startIndex != -1) { final int endIndex = findPlaceholderEndIndex(buf, startIndex); if (endIndex != -1) { String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); if (!visitedPlaceholders.add(placeholder)) { throw new BeanDefinitionStoreException( "Circular placeholder reference '" + placeholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the // placeholder key. placeholder = parseStringValue(placeholder, attributes, visitedPlaceholders); // Now obtain the value for the fully resolved key... final Object propValue = attributes.get(placeholder); if (propValue != null) { String propVal = propValue.toString(); // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, attributes, visitedPlaceholders); buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new BeanDefinitionStoreException( "Could not resolve placeholder '" + placeholder + "'"); } visitedPlaceholders.remove(placeholder); } else { startIndex = -1; } } return buf.toString(); } @Override public String resolveStringValue(final String strVal) throws BeansException { final String value = parseStringValue(strVal, this.attributes, new HashSet<String>()); return value.equals(this.nullValue) ? null : value; } }