1 /*
2  * Copyright 2002-2012 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.util;
18 
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Properties;
23 import java.util.Set;
24 
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 
28 /**
29  * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
30  * <code>${name}</code>. Using <code>PropertyPlaceholderHelper</code> these placeholders can be substituted for
31  * user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or
32  * using a {@link PlaceholderResolver}.
33  *
34  * @author Juergen Hoeller
35  * @author Rob Harrop
36  * @since 3.0
37  */
38 public class PropertyPlaceholderHelper {
39 
40 	private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);
41 
42 	private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
43 
44 	static {
45 		wellKnownSimplePrefixes.put("}", "{");
46 		wellKnownSimplePrefixes.put("]", "[");
47 		wellKnownSimplePrefixes.put(")", "(");
48 	}
49 
50 
51 	private final String placeholderPrefix;
52 
53 	private final String placeholderSuffix;
54 
55 	private final String simplePrefix;
56 
57 	private final String valueSeparator;
58 
59 	private final boolean ignoreUnresolvablePlaceholders;
60 
61 
62 	/**
63 	 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix.
64 	 * Unresolvable placeholders are ignored.
65 	 * @param placeholderPrefix the prefix that denotes the start of a placeholder.
66 	 * @param placeholderSuffix the suffix that denotes the end of a placeholder.
67 	 */
PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix)68 	public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
69 		this(placeholderPrefix, placeholderSuffix, null, true);
70 	}
71 
72 	/**
73 	 * Creates a new <code>PropertyPlaceholderHelper</code> that uses the supplied prefix and suffix.
74 	 * @param placeholderPrefix the prefix that denotes the start of a placeholder
75 	 * @param placeholderSuffix the suffix that denotes the end of a placeholder
76 	 * @param valueSeparator the separating character between the placeholder variable
77 	 * and the associated default value, if any
78 	 * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored
79 	 * (<code>true</code>) or cause an exception (<code>false</code>).
80 	 */
PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, String valueSeparator, boolean ignoreUnresolvablePlaceholders)81 	public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
82 			String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
83 
84 		Assert.notNull(placeholderPrefix, "placeholderPrefix must not be null");
85 		Assert.notNull(placeholderSuffix, "placeholderSuffix must not be null");
86 		this.placeholderPrefix = placeholderPrefix;
87 		this.placeholderSuffix = placeholderSuffix;
88 		String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
89 		if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
90 			this.simplePrefix = simplePrefixForSuffix;
91 		}
92 		else {
93 			this.simplePrefix = this.placeholderPrefix;
94 		}
95 		this.valueSeparator = valueSeparator;
96 		this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
97 	}
98 
99 
100 	/**
101 	 * Replaces all placeholders of format <code>${name}</code> with the corresponding property
102 	 * from the supplied {@link Properties}.
103 	 * @param value the value containing the placeholders to be replaced.
104 	 * @param properties the <code>Properties</code> to use for replacement.
105 	 * @return the supplied value with placeholders replaced inline.
106 	 */
replacePlaceholders(String value, final Properties properties)107 	public String replacePlaceholders(String value, final Properties properties) {
108 		Assert.notNull(properties, "Argument 'properties' must not be null.");
109 		return replacePlaceholders(value, new PlaceholderResolver() {
110 			public String resolvePlaceholder(String placeholderName) {
111 				return properties.getProperty(placeholderName);
112 			}
113 		});
114 	}
115 
116 	/**
117 	 * Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied
118 	 * {@link PlaceholderResolver}.
119 	 * @param value the value containing the placeholders to be replaced.
120 	 * @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
121 	 * @return the supplied value with placeholders replaced inline.
122 	 */
123 	public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
124 		Assert.notNull(value, "Argument 'value' must not be null.");
125 		return parseStringValue(value, placeholderResolver, new HashSet<String>());
126 	}
127 
128 	protected String parseStringValue(
129 			String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
130 
131 		StringBuilder buf = new StringBuilder(strVal);
132 
133 		int startIndex = strVal.indexOf(this.placeholderPrefix);
134 		while (startIndex != -1) {
135 			int endIndex = findPlaceholderEndIndex(buf, startIndex);
136 			if (endIndex != -1) {
137 				String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
138 				String originalPlaceholder = placeholder;
139 				if (!visitedPlaceholders.add(originalPlaceholder)) {
140 					throw new IllegalArgumentException(
141 							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
142 				}
143 				// Recursive invocation, parsing placeholders contained in the placeholder key.
144 				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
145 				// Now obtain the value for the fully resolved key...
146 				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
147 				if (propVal == null && this.valueSeparator != null) {
148 					int separatorIndex = placeholder.indexOf(this.valueSeparator);
149 					if (separatorIndex != -1) {
150 						String actualPlaceholder = placeholder.substring(0, separatorIndex);
151 						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
152 						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
153 						if (propVal == null) {
154 							propVal = defaultValue;
155 						}
156 					}
157 				}
158 				if (propVal != null) {
159 					// Recursive invocation, parsing placeholders contained in the
160 					// previously resolved placeholder value.
161 					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
162 					buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
163 					if (logger.isTraceEnabled()) {
164 						logger.trace("Resolved placeholder '" + placeholder + "'");
165 					}
166 					startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
167 				}
168 				else if (this.ignoreUnresolvablePlaceholders) {
169 					// Proceed with unprocessed value.
170 					startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
171 				}
172 				else {
173 					throw new IllegalArgumentException("Could not resolve placeholder '" +
174 							placeholder + "'" + " in string value \"" + strVal + "\"");
175 				}
176 				visitedPlaceholders.remove(originalPlaceholder);
177 			}
178 			else {
179 				startIndex = -1;
180 			}
181 		}
182 
183 		return buf.toString();
184 	}
185 
186 	private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
187 		int index = startIndex + this.placeholderPrefix.length();
188 		int withinNestedPlaceholder = 0;
189 		while (index < buf.length()) {
190 			if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
191 				if (withinNestedPlaceholder > 0) {
192 					withinNestedPlaceholder--;
193 					index = index + this.placeholderSuffix.length();
194 				}
195 				else {
196 					return index;
197 				}
198 			}
199 			else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
200 				withinNestedPlaceholder++;
201 				index = index + this.simplePrefix.length();
202 			}
203 			else {
204 				index++;
205 			}
206 		}
207 		return -1;
208 	}
209 
210 
211 	/**
212 	 * Strategy interface used to resolve replacement values for placeholders contained in Strings.
213 	 * @see PropertyPlaceholderHelper
214 	 */
215 	public static interface PlaceholderResolver {
216 
217 		/**
218 		 * Resolves the supplied placeholder name into the replacement value.
219 		 * @param placeholderName the name of the placeholder to resolve.
220 		 * @return the replacement value or <code>null</code> if no replacement is to be made.
221 		 */
222 		String resolvePlaceholder(String placeholderName);
223 	}
224 
225 }
226