001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.beans.factory.config;
018:
019: import java.util.HashSet;
020: import java.util.Properties;
021: import java.util.Set;
022:
023: import org.springframework.beans.BeansException;
024: import org.springframework.beans.factory.BeanDefinitionStoreException;
025: import org.springframework.beans.factory.BeanFactory;
026: import org.springframework.beans.factory.BeanFactoryAware;
027: import org.springframework.beans.factory.BeanNameAware;
028: import org.springframework.core.Constants;
029: import org.springframework.util.StringValueResolver;
030:
031: /**
032: * A property resource configurer that resolves placeholders in bean property values of
033: * context definitions. It <i>pulls</i> values from a properties file into bean definitions.
034: *
035: * <p>The default placeholder syntax follows the Ant / Log4J / JSP EL style:
036: *
037: * <pre class="code">${...}</pre>
038: *
039: * Example XML context definition:
040: *
041: * <pre class="code"><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
042: * <property name="driverClassName"><value>${driver}</value></property>
043: * <property name="url"><value>jdbc:${dbname}</value></property>
044: * </bean></pre>
045: *
046: * Example properties file:
047: *
048: * <pre class="code">driver=com.mysql.jdbc.Driver
049: * dbname=mysql:mydb</pre>
050: *
051: * PropertyPlaceholderConfigurer checks simple property values, lists, maps,
052: * props, and bean names in bean references. Furthermore, placeholder values can
053: * also cross-reference other placeholders, like:
054: *
055: * <pre class="code">rootPath=myrootdir
056: * subPath=${rootPath}/subdir</pre>
057: *
058: * In contrast to PropertyOverrideConfigurer, this configurer allows to fill in
059: * explicit placeholders in context definitions. Therefore, the original definition
060: * cannot specify any default values for such bean properties, and the placeholder
061: * properties file is supposed to contain an entry for each defined placeholder.
062: *
063: * <p>If a configurer cannot resolve a placeholder, a BeanDefinitionStoreException
064: * will be thrown. If you want to check against multiple properties files, specify
065: * multiple resources via the "locations" setting. You can also define multiple
066: * PropertyPlaceholderConfigurers, each with its <i>own</i> placeholder syntax.
067: *
068: * <p>Default property values can be defined via "properties", to make overriding
069: * definitions in properties files optional. A configurer will also check against
070: * system properties (e.g. "user.dir") if it cannot resolve a placeholder with any
071: * of the specified properties. This can be customized via "systemPropertiesMode".
072: *
073: * <p>Note that the context definition <i>is</i> aware of being incomplete;
074: * this is immediately obvious to users when looking at the XML definition file.
075: * Hence, placeholders have to be resolved; any desired defaults have to be
076: * defined as placeholder values as well (for example in a default properties file).
077: *
078: * <p>Property values can be converted after reading them in, through overriding
079: * the {@link #convertPropertyValue} method. For example, encrypted values can
080: * be detected and decrypted accordingly before processing them.
081: *
082: * @author Juergen Hoeller
083: * @since 02.10.2003
084: * @see #setLocations
085: * @see #setProperties
086: * @see #setPlaceholderPrefix
087: * @see #setPlaceholderSuffix
088: * @see #setSystemPropertiesModeName
089: * @see System#getProperty(String)
090: * @see #convertPropertyValue
091: * @see PropertyOverrideConfigurer
092: */
093: public class PropertyPlaceholderConfigurer extends
094: PropertyResourceConfigurer implements BeanNameAware,
095: BeanFactoryAware {
096:
097: /** Default placeholder prefix: "${" */
098: public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
099:
100: /** Default placeholder suffix: "}" */
101: public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
102:
103: /** Never check system properties. */
104: public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
105:
106: /**
107: * Check system properties if not resolvable in the specified properties.
108: * This is the default.
109: */
110: public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
111:
112: /**
113: * Check system properties first, before trying the specified properties.
114: * This allows system properties to override any other property source.
115: */
116: public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
117:
118: private static final Constants constants = new Constants(
119: PropertyPlaceholderConfigurer.class);
120:
121: private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
122:
123: private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
124:
125: private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
126:
127: private boolean searchSystemEnvironment = true;
128:
129: private boolean ignoreUnresolvablePlaceholders = false;
130:
131: private String beanName;
132:
133: private BeanFactory beanFactory;
134:
135: /**
136: * Set the prefix that a placeholder string starts with.
137: * The default is "${".
138: * @see #DEFAULT_PLACEHOLDER_PREFIX
139: */
140: public void setPlaceholderPrefix(String placeholderPrefix) {
141: this .placeholderPrefix = placeholderPrefix;
142: }
143:
144: /**
145: * Set the suffix that a placeholder string ends with.
146: * The default is "}".
147: * @see #DEFAULT_PLACEHOLDER_SUFFIX
148: */
149: public void setPlaceholderSuffix(String placeholderSuffix) {
150: this .placeholderSuffix = placeholderSuffix;
151: }
152:
153: /**
154: * Set the system property mode by the name of the corresponding constant,
155: * e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
156: * @param constantName name of the constant
157: * @throws java.lang.IllegalArgumentException if an invalid constant was specified
158: * @see #setSystemPropertiesMode
159: */
160: public void setSystemPropertiesModeName(String constantName)
161: throws IllegalArgumentException {
162: this .systemPropertiesMode = constants.asNumber(constantName)
163: .intValue();
164: }
165:
166: /**
167: * Set how to check system properties: as fallback, as override, or never.
168: * For example, will resolve ${user.dir} to the "user.dir" system property.
169: * <p>The default is "fallback": If not being able to resolve a placeholder
170: * with the specified properties, a system property will be tried.
171: * "override" will check for a system property first, before trying the
172: * specified properties. "never" will not check system properties at all.
173: * @see #SYSTEM_PROPERTIES_MODE_NEVER
174: * @see #SYSTEM_PROPERTIES_MODE_FALLBACK
175: * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE
176: * @see #setSystemPropertiesModeName
177: */
178: public void setSystemPropertiesMode(int systemPropertiesMode) {
179: this .systemPropertiesMode = systemPropertiesMode;
180: }
181:
182: /**
183: * Set whether to search for a matching system environment variable
184: * if no matching system property has been found. Only applied when
185: * "systemPropertyMode" is active (i.e. "fallback" or "override"), right
186: * after checking JVM system properties.
187: * <p>Default is "true". Switch this setting off to never resolve placeholders
188: * against system environment variables. Note that it is generally recommended
189: * to pass external values in as JVM system properties: This can easily be
190: * achieved in a startup script, even for existing environment variables.
191: * <p><b>NOTE:</b> Access to environment variables does not work on the
192: * Sun VM 1.4, where the corresponding {@link System#getenv} support was
193: * disabled - before it eventually got re-enabled for the Sun VM 1.5.
194: * Please upgrade to 1.5 (or higher) if you intend to rely on the
195: * environment variable support.
196: * @see #setSystemPropertiesMode
197: * @see java.lang.System#getProperty(String)
198: * @see java.lang.System#getenv(String)
199: */
200: public void setSearchSystemEnvironment(
201: boolean searchSystemEnvironment) {
202: this .searchSystemEnvironment = searchSystemEnvironment;
203: }
204:
205: /**
206: * Set whether to ignore unresolvable placeholders. Default is "false":
207: * An exception will be thrown if a placeholder cannot be resolved.
208: */
209: public void setIgnoreUnresolvablePlaceholders(
210: boolean ignoreUnresolvablePlaceholders) {
211: this .ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
212: }
213:
214: /**
215: * Only necessary to check that we're not parsing our own bean definition,
216: * to avoid failing on unresolvable placeholders in properties file locations.
217: * The latter case can happen with placeholders for system properties in
218: * resource locations.
219: * @see #setLocations
220: * @see org.springframework.core.io.ResourceEditor
221: */
222: public void setBeanName(String beanName) {
223: this .beanName = beanName;
224: }
225:
226: /**
227: * Only necessary to check that we're not parsing our own bean definition,
228: * to avoid failing on unresolvable placeholders in properties file locations.
229: * The latter case can happen with placeholders for system properties in
230: * resource locations.
231: * @see #setLocations
232: * @see org.springframework.core.io.ResourceEditor
233: */
234: public void setBeanFactory(BeanFactory beanFactory) {
235: this .beanFactory = beanFactory;
236: }
237:
238: protected void processProperties(
239: ConfigurableListableBeanFactory beanFactoryToProcess,
240: Properties props) throws BeansException {
241:
242: StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(
243: props);
244: BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(
245: valueResolver);
246:
247: String[] beanNames = beanFactoryToProcess
248: .getBeanDefinitionNames();
249: for (int i = 0; i < beanNames.length; i++) {
250: // Check that we're not parsing our own bean definition,
251: // to avoid failing on unresolvable placeholders in properties file locations.
252: if (!(beanNames[i].equals(this .beanName) && beanFactoryToProcess
253: .equals(this .beanFactory))) {
254: BeanDefinition bd = beanFactoryToProcess
255: .getBeanDefinition(beanNames[i]);
256: try {
257: visitor.visitBeanDefinition(bd);
258: } catch (BeanDefinitionStoreException ex) {
259: throw new BeanDefinitionStoreException(bd
260: .getResourceDescription(), beanNames[i], ex
261: .getMessage());
262: }
263: }
264: }
265:
266: // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
267: beanFactoryToProcess.resolveAliases(valueResolver);
268: }
269:
270: /**
271: * Parse the given String value recursively, to be able to resolve
272: * nested placeholders (when resolved property values in turn contain
273: * placeholders again).
274: * @param strVal the String value to parse
275: * @param props the Properties to resolve placeholders against
276: * @param visitedPlaceholders the placeholders that have already been visited
277: * during the current resolution attempt (used to detect circular references
278: * between placeholders). Only non-null if we're parsing a nested placeholder.
279: * @throws BeanDefinitionStoreException if invalid values are encountered
280: * @see #resolvePlaceholder(String, java.util.Properties, int)
281: */
282: protected String parseStringValue(String strVal, Properties props,
283: Set visitedPlaceholders)
284: throws BeanDefinitionStoreException {
285:
286: StringBuffer buf = new StringBuffer(strVal);
287:
288: int startIndex = strVal.indexOf(this .placeholderPrefix);
289: while (startIndex != -1) {
290: int endIndex = buf.indexOf(this .placeholderSuffix,
291: startIndex + this .placeholderPrefix.length());
292: if (endIndex != -1) {
293: String placeholder = buf.substring(startIndex
294: + this .placeholderPrefix.length(), endIndex);
295: if (!visitedPlaceholders.add(placeholder)) {
296: throw new BeanDefinitionStoreException(
297: "Circular placeholder reference '"
298: + placeholder
299: + "' in property definitions");
300: }
301: String propVal = resolvePlaceholder(placeholder, props,
302: this .systemPropertiesMode);
303: if (propVal != null) {
304: // Recursive invocation, parsing placeholders contained in the
305: // previously resolved placeholder value.
306: propVal = parseStringValue(propVal, props,
307: visitedPlaceholders);
308: buf.replace(startIndex, endIndex
309: + this .placeholderSuffix.length(), propVal);
310: if (logger.isTraceEnabled()) {
311: logger.trace("Resolved placeholder '"
312: + placeholder + "'");
313: }
314: startIndex = buf.indexOf(this .placeholderPrefix,
315: startIndex + propVal.length());
316: } else if (this .ignoreUnresolvablePlaceholders) {
317: // Proceed with unprocessed value.
318: startIndex = buf.indexOf(this .placeholderPrefix,
319: endIndex + this .placeholderSuffix.length());
320: } else {
321: throw new BeanDefinitionStoreException(
322: "Could not resolve placeholder '"
323: + placeholder + "'");
324: }
325: visitedPlaceholders.remove(placeholder);
326: } else {
327: startIndex = -1;
328: }
329: }
330:
331: return buf.toString();
332: }
333:
334: /**
335: * Resolve the given placeholder using the given properties, performing
336: * a system properties check according to the given mode.
337: * <p>Default implementation delegates to <code>resolvePlaceholder
338: * (placeholder, props)</code> before/after the system properties check.
339: * <p>Subclasses can override this for custom resolution strategies,
340: * including customized points for the system properties check.
341: * @param placeholder the placeholder to resolve
342: * @param props the merged properties of this configurer
343: * @param systemPropertiesMode the system properties mode,
344: * according to the constants in this class
345: * @return the resolved value, of null if none
346: * @see #setSystemPropertiesMode
347: * @see System#getProperty
348: * @see #resolvePlaceholder(String, java.util.Properties)
349: */
350: protected String resolvePlaceholder(String placeholder,
351: Properties props, int systemPropertiesMode) {
352: String propVal = null;
353: if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
354: propVal = resolveSystemProperty(placeholder);
355: }
356: if (propVal == null) {
357: propVal = resolvePlaceholder(placeholder, props);
358: }
359: if (propVal == null
360: && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
361: propVal = resolveSystemProperty(placeholder);
362: }
363: return propVal;
364: }
365:
366: /**
367: * Resolve the given placeholder using the given properties.
368: * The default implementation simply checks for a corresponding property key.
369: * <p>Subclasses can override this for customized placeholder-to-key mappings
370: * or custom resolution strategies, possibly just using the given properties
371: * as fallback.
372: * <p>Note that system properties will still be checked before respectively
373: * after this method is invoked, according to the system properties mode.
374: * @param placeholder the placeholder to resolve
375: * @param props the merged properties of this configurer
376: * @return the resolved value, of <code>null</code> if none
377: * @see #setSystemPropertiesMode
378: */
379: protected String resolvePlaceholder(String placeholder,
380: Properties props) {
381: return props.getProperty(placeholder);
382: }
383:
384: /**
385: * Resolve the given key as JVM system property, and optionally also as
386: * system environment variable if no matching system property has been found.
387: * @param key the placeholder to resolve as system property key
388: * @return the system property value, or <code>null</code> if not found
389: * @see #setSearchSystemEnvironment
390: * @see java.lang.System#getProperty(String)
391: * @see java.lang.System#getenv(String)
392: */
393: protected String resolveSystemProperty(String key) {
394: try {
395: String value = System.getProperty(key);
396: if (value == null && this .searchSystemEnvironment) {
397: value = System.getenv(key);
398: }
399: return value;
400: } catch (Throwable ex) {
401: if (logger.isDebugEnabled()) {
402: logger.debug("Could not access system property '" + key
403: + "': " + ex);
404: }
405: return null;
406: }
407: }
408:
409: /**
410: * BeanDefinitionVisitor that resolves placeholders in String values,
411: * delegating to the <code>parseStringValue</code> method of the
412: * containing class.
413: */
414: private class PlaceholderResolvingStringValueResolver implements
415: StringValueResolver {
416:
417: private final Properties props;
418:
419: public PlaceholderResolvingStringValueResolver(Properties props) {
420: this .props = props;
421: }
422:
423: public String resolveStringValue(String strVal)
424: throws BeansException {
425: return parseStringValue(strVal, this .props, new HashSet());
426: }
427: }
428:
429: }
|