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:
030: /**
031: * A property resource configurer that resolves placeholders in bean property values of
032: * context definitions. It <i>pulls</i> values from a properties file into bean definitions.
033: *
034: * <p>The default placeholder syntax follows the Ant / Log4J / JSP EL style:
035: *
036: * <pre class="code">${...}</pre>
037: *
038: * Example XML context definition:
039: *
040: * <pre class="code"><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
041: * <property name="driverClassName"><value>${driver}</value></property>
042: * <property name="url"><value>jdbc:${dbname}</value></property>
043: * </bean></pre>
044: *
045: * Example properties file:
046: *
047: * <pre class="code">driver=com.mysql.jdbc.Driver
048: * dbname=mysql:mydb</pre>
049: *
050: * PropertyPlaceholderConfigurer checks simple property values, lists, maps,
051: * props, and bean names in bean references. Furthermore, placeholder values can
052: * also cross-reference other placeholders, like:
053: *
054: * <pre class="code">rootPath=myrootdir
055: * subPath=${rootPath}/subdir</pre>
056: *
057: * In contrast to PropertyOverrideConfigurer, this configurer allows to fill in
058: * explicit placeholders in context definitions. Therefore, the original definition
059: * cannot specify any default values for such bean properties, and the placeholder
060: * properties file is supposed to contain an entry for each defined placeholder.
061: *
062: * <p>If a configurer cannot resolve a placeholder, a BeanDefinitionStoreException
063: * will be thrown. If you want to check against multiple properties files, specify
064: * multiple resources via the "locations" setting. You can also define multiple
065: * PropertyPlaceholderConfigurers, each with its <i>own</i> placeholder syntax.
066: *
067: * <p>Default property values can be defined via "properties", to make overriding
068: * definitions in properties files optional. A configurer will also check against
069: * system properties (e.g. "user.dir") if it cannot resolve a placeholder with any
070: * of the specified properties. This can be customized via "systemPropertiesMode".
071: *
072: * <p>Note that the context definition <i>is</i> aware of being incomplete;
073: * this is immediately obvious to users when looking at the XML definition file.
074: * Hence, placeholders have to be resolved; any desired defaults have to be
075: * defined as placeholder values as well (for example in a default properties file).
076: *
077: * <p>Property values can be converted after reading them in, through overriding
078: * the {@link #convertPropertyValue} method. For example, encrypted values can
079: * be detected and decrypted accordingly before processing them.
080: *
081: * @author Juergen Hoeller
082: * @since 02.10.2003
083: * @see #setLocations
084: * @see #setProperties
085: * @see #setPlaceholderPrefix
086: * @see #setPlaceholderSuffix
087: * @see #setSystemPropertiesModeName
088: * @see System#getProperty(String)
089: * @see #convertPropertyValue
090: * @see PropertyOverrideConfigurer
091: */
092: public class PropertyPlaceholderConfigurer extends
093: PropertyResourceConfigurer implements BeanNameAware,
094: BeanFactoryAware {
095:
096: /** Default placeholder prefix: "${" */
097: public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
098:
099: /** Default placeholder suffix: "}" */
100: public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
101:
102: /** Never check system properties. */
103: public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
104:
105: /**
106: * Check system properties if not resolvable in the specified properties.
107: * This is the default.
108: */
109: public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
110:
111: /**
112: * Check system properties first, before trying the specified properties.
113: * This allows system properties to override any other property source.
114: */
115: public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
116:
117: private static final Constants constants = new Constants(
118: PropertyPlaceholderConfigurer.class);
119:
120: private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
121:
122: private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
123:
124: private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
125:
126: private boolean searchSystemEnvironment = true;
127:
128: private boolean ignoreUnresolvablePlaceholders = false;
129:
130: private String beanName;
131:
132: private BeanFactory beanFactory;
133:
134: /**
135: * Set the prefix that a placeholder string starts with.
136: * The default is "${".
137: * @see #DEFAULT_PLACEHOLDER_PREFIX
138: */
139: public void setPlaceholderPrefix(String placeholderPrefix) {
140: this .placeholderPrefix = placeholderPrefix;
141: }
142:
143: /**
144: * Set the suffix that a placeholder string ends with.
145: * The default is "}".
146: * @see #DEFAULT_PLACEHOLDER_SUFFIX
147: */
148: public void setPlaceholderSuffix(String placeholderSuffix) {
149: this .placeholderSuffix = placeholderSuffix;
150: }
151:
152: /**
153: * Set the system property mode by the name of the corresponding constant,
154: * e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
155: * @param constantName name of the constant
156: * @throws java.lang.IllegalArgumentException if an invalid constant was specified
157: * @see #setSystemPropertiesMode
158: */
159: public void setSystemPropertiesModeName(String constantName)
160: throws IllegalArgumentException {
161: this .systemPropertiesMode = constants.asNumber(constantName)
162: .intValue();
163: }
164:
165: /**
166: * Set how to check system properties: as fallback, as override, or never.
167: * For example, will resolve ${user.dir} to the "user.dir" system property.
168: * <p>The default is "fallback": If not being able to resolve a placeholder
169: * with the specified properties, a system property will be tried.
170: * "override" will check for a system property first, before trying the
171: * specified properties. "never" will not check system properties at all.
172: * @see #SYSTEM_PROPERTIES_MODE_NEVER
173: * @see #SYSTEM_PROPERTIES_MODE_FALLBACK
174: * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE
175: * @see #setSystemPropertiesModeName
176: */
177: public void setSystemPropertiesMode(int systemPropertiesMode) {
178: this .systemPropertiesMode = systemPropertiesMode;
179: }
180:
181: /**
182: * Set whether to search for a matching system environment variable
183: * if no matching system property has been found. Only applied when
184: * "systemPropertyMode" is active (i.e. "fallback" or "override"), right
185: * after checking JVM system properties.
186: * <p>Default is "true". Switch this setting off to never resolve placeholders
187: * against system environment variables. Note that it is generally recommended
188: * to pass external values in as JVM system properties: This can easily be
189: * achieved in a startup script, even for existing environment variables.
190: * <p><b>NOTE:</b> Access to environment variables does not work on the
191: * Sun VM 1.4, where the corresponding {@link System#getenv} support was
192: * disabled - before it eventually got re-enabled for the Sun VM 1.5.
193: * Please upgrade to 1.5 (or higher) if you intend to rely on the
194: * environment variable support.
195: * @see #setSystemPropertiesMode
196: * @see java.lang.System#getProperty(String)
197: * @see java.lang.System#getenv(String)
198: */
199: public void setSearchSystemEnvironment(
200: boolean searchSystemEnvironment) {
201: this .searchSystemEnvironment = searchSystemEnvironment;
202: }
203:
204: /**
205: * Set whether to ignore unresolvable placeholders. Default is "false":
206: * An exception will be thrown if a placeholder cannot be resolved.
207: */
208: public void setIgnoreUnresolvablePlaceholders(
209: boolean ignoreUnresolvablePlaceholders) {
210: this .ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
211: }
212:
213: /**
214: * Only necessary to check that we're not parsing our own bean definition,
215: * to avoid failing on unresolvable placeholders in properties file locations.
216: * The latter case can happen with placeholders for system properties in
217: * resource locations.
218: * @see #setLocations
219: * @see org.springframework.core.io.ResourceEditor
220: */
221: public void setBeanName(String beanName) {
222: this .beanName = beanName;
223: }
224:
225: /**
226: * Only necessary to check that we're not parsing our own bean definition,
227: * to avoid failing on unresolvable placeholders in properties file locations.
228: * The latter case can happen with placeholders for system properties in
229: * resource locations.
230: * @see #setLocations
231: * @see org.springframework.core.io.ResourceEditor
232: */
233: public void setBeanFactory(BeanFactory beanFactory) {
234: this .beanFactory = beanFactory;
235: }
236:
237: protected void processProperties(
238: ConfigurableListableBeanFactory beanFactoryToProcess,
239: Properties props) throws BeansException {
240:
241: BeanDefinitionVisitor visitor = new PlaceholderResolvingBeanDefinitionVisitor(
242: props);
243: String[] beanNames = beanFactoryToProcess
244: .getBeanDefinitionNames();
245: for (int i = 0; i < beanNames.length; i++) {
246: // Check that we're not parsing our own bean definition,
247: // to avoid failing on unresolvable placeholders in properties file locations.
248: if (!(beanNames[i].equals(this .beanName) && beanFactoryToProcess
249: .equals(this .beanFactory))) {
250: BeanDefinition bd = beanFactoryToProcess
251: .getBeanDefinition(beanNames[i]);
252: try {
253: visitor.visitBeanDefinition(bd);
254: } catch (BeanDefinitionStoreException ex) {
255: throw new BeanDefinitionStoreException(bd
256: .getResourceDescription(), beanNames[i], ex
257: .getMessage());
258: }
259: }
260: }
261: }
262:
263: /**
264: * Parse the given String value recursively, to be able to resolve
265: * nested placeholders (when resolved property values in turn contain
266: * placeholders again).
267: * @param strVal the String value to parse
268: * @param props the Properties to resolve placeholders against
269: * @param visitedPlaceholders the placeholders that have already been visited
270: * during the current resolution attempt (used to detect circular references
271: * between placeholders). Only non-null if we're parsing a nested placeholder.
272: * @throws BeanDefinitionStoreException if invalid values are encountered
273: * @see #resolvePlaceholder(String, java.util.Properties, int)
274: */
275: protected String parseStringValue(String strVal, Properties props,
276: Set visitedPlaceholders)
277: throws BeanDefinitionStoreException {
278:
279: StringBuffer buf = new StringBuffer(strVal);
280:
281: // The following code does not use JDK 1.4's StringBuffer.indexOf(String)
282: // method to retain JDK 1.3 compatibility. The slight loss in performance
283: // is not really relevant, as this code will typically just run on startup.
284:
285: int startIndex = strVal.indexOf(this .placeholderPrefix);
286: while (startIndex != -1) {
287: int endIndex = buf.toString().indexOf(
288: this .placeholderSuffix,
289: startIndex + this .placeholderPrefix.length());
290: if (endIndex != -1) {
291: String placeholder = buf.substring(startIndex
292: + this .placeholderPrefix.length(), endIndex);
293: if (!visitedPlaceholders.add(placeholder)) {
294: throw new BeanDefinitionStoreException(
295: "Circular placeholder reference '"
296: + placeholder
297: + "' in property definitions");
298: }
299: String propVal = resolvePlaceholder(placeholder, props,
300: this .systemPropertiesMode);
301: if (propVal != null) {
302: // Recursive invocation, parsing placeholders contained in the
303: // previously resolved placeholder value.
304: propVal = parseStringValue(propVal, props,
305: visitedPlaceholders);
306: buf.replace(startIndex, endIndex
307: + this .placeholderSuffix.length(), propVal);
308: if (logger.isTraceEnabled()) {
309: logger.trace("Resolved placeholder '"
310: + placeholder + "'");
311: }
312: startIndex = buf.toString().indexOf(
313: this .placeholderPrefix,
314: startIndex + propVal.length());
315: } else if (this .ignoreUnresolvablePlaceholders) {
316: // Proceed with unprocessed value.
317: startIndex = buf.toString().indexOf(
318: 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 PlaceholderResolvingBeanDefinitionVisitor extends
415: BeanDefinitionVisitor {
416:
417: private final Properties props;
418:
419: public PlaceholderResolvingBeanDefinitionVisitor(
420: Properties props) {
421: this .props = props;
422: }
423:
424: protected String resolveStringValue(String strVal)
425: throws BeansException {
426: return parseStringValue(strVal, this .props, new HashSet());
427: }
428: }
429:
430: }
|