View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration.interpol;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import org.apache.commons.lang.text.StrLookup;
24  
25  /**
26   * <p>
27   * A class that handles interpolation (variable substitution) for configuration
28   * objects.
29   * </p>
30   * <p>
31   * Each instance of {@code AbstractConfiguration} is associated with an
32   * object of this class. All interpolation tasks are delegated to this object.
33   * </p>
34   * <p>
35   * {@code ConfigurationInterpolator} works together with the
36   * {@code StrSubstitutor} class from <a
37   * href="http://commons.apache.org/lang">Commons Lang</a>. By extending
38   * {@code StrLookup} it is able to provide values for variables that
39   * appear in expressions.
40   * </p>
41   * <p>
42   * The basic idea of this class is that it can maintain a set of primitive
43   * {@code StrLookup} objects, each of which is identified by a special
44   * prefix. The variables to be processed have the form
45   * <code>${prefix:name}</code>. {@code ConfigurationInterpolator} will
46   * extract the prefix and determine, which primitive lookup object is registered
47   * for it. Then the name of the variable is passed to this object to obtain the
48   * actual value. It is also possible to define a default lookup object, which
49   * will be used for variables that do not have a prefix or that cannot be
50   * resolved by their associated lookup object.
51   * </p>
52   * <p>
53   * When a new instance of this class is created it is initialized with a default
54   * set of primitive lookup objects. This set can be customized using the static
55   * methods {@code registerGlobalLookup()} and
56   * {@code deregisterGlobalLookup()}. Per default it contains the
57   * following standard lookup objects:
58   * </p>
59   * <p>
60   * <table border="1">
61   * <tr>
62   * <th>Prefix</th>
63   * <th>Lookup object</th>
64   * </tr>
65   * <tr>
66   * <td valign="top">sys</td>
67   * <td>With this prefix a lookup object is associated that is able to resolve
68   * system properties.</td>
69   * </tr>
70   * <tr>
71   * <td valign="top">const</td>
72   * <td>The {@code const} prefix indicates that a variable is to be
73   * interpreted as a constant member field of a class (i.e. a field with the
74   * <b>static final</b> modifiers). The name of the variable must be of the form
75   * {@code <full qualified class name>.<field name>}, e.g.
76   * {@code org.apache.commons.configuration.interpol.ConfigurationInterpolator.PREFIX_CONSTANTS}.
77   * </td>
78   * </tr>
79   * </table>
80   * </p>
81   * <p>
82   * After an instance has been created the current set of lookup objects can be
83   * modified using the {@code registerLookup()} and
84   * {@code deregisterLookup()} methods. The default lookup object (that is
85   * invoked for variables without a prefix) can be set with the
86   * {@code setDefaultLookup()} method. (If a
87   * {@code ConfigurationInterpolator} instance is created by a
88   * configuration object, this lookup points to the configuration itself, so that
89   * variables are resolved using the configuration's properties. This ensures
90   * backward compatibility to earlier version of Commons Configuration.)
91   * </p>
92   * <p>
93   * Implementation node: Instances of this class are not thread-safe related to
94   * modifications of their current set of registered lookup objects. It is
95   * intended that each instance is associated with a single
96   * {@code Configuration} object and used for its interpolation tasks.
97   * </p>
98   *
99   * @version $Id: ConfigurationInterpolator.java 1295276 2012-02-29 21:11:35Z oheger $
100  * @since 1.4
101  * @author <a
102  * href="http://commons.apache.org/configuration/team-list.html">Commons
103  * Configuration team</a>
104  */
105 public class ConfigurationInterpolator extends StrLookup
106 {
107     /**
108      * Constant for the prefix of the standard lookup object for resolving
109      * system properties.
110      */
111     public static final String PREFIX_SYSPROPERTIES = "sys";
112 
113     /**
114      * Constant for the prefix of the standard lookup object for resolving
115      * constant values.
116      */
117     public static final String PREFIX_CONSTANTS = "const";
118 
119     /**
120      * Constant for the prefix of the standard lookup object for resolving
121      * environment properties.
122      * @since 1.7
123      */
124     public static final String PREFIX_ENVIRONMENT = "env";
125 
126     /** Constant for the prefix separator. */
127     private static final char PREFIX_SEPARATOR = ':';
128 
129     /** A map with the globally registered lookup objects. */
130     private static Map<String, StrLookup> globalLookups;
131 
132     /** A map with the locally registered lookup objects. */
133     private Map<String, StrLookup> localLookups;
134 
135     /** Stores the default lookup object. */
136     private StrLookup defaultLookup;
137 
138     /** Stores a parent interpolator objects if the interpolator is nested hierarchically. */
139     private ConfigurationInterpolator parentInterpolator;
140 
141     /**
142      * Creates a new instance of {@code ConfigurationInterpolator}.
143      */
144     public ConfigurationInterpolator()
145     {
146         synchronized (globalLookups)
147         {
148             localLookups = new HashMap<String, StrLookup>(globalLookups);
149         }
150     }
151 
152     /**
153      * Registers the given lookup object for the specified prefix globally. This
154      * means that all instances that are created later will use this lookup
155      * object for this prefix. If for this prefix a lookup object is already
156      * registered, the new lookup object will replace the old one. Note that the
157      * lookup objects registered here will be shared between multiple clients.
158      * So they should be thread-safe.
159      *
160      * @param prefix the variable prefix (must not be <b>null</b>)
161      * @param lookup the lookup object to be used for this prefix (must not be
162      * <b>null</b>)
163      */
164     public static void registerGlobalLookup(String prefix, StrLookup lookup)
165     {
166         if (prefix == null)
167         {
168             throw new IllegalArgumentException(
169                     "Prefix for lookup object must not be null!");
170         }
171         if (lookup == null)
172         {
173             throw new IllegalArgumentException(
174                     "Lookup object must not be null!");
175         }
176         synchronized (globalLookups)
177         {
178             globalLookups.put(prefix, lookup);
179         }
180     }
181 
182     /**
183      * Deregisters the global lookup object for the specified prefix. This means
184      * that this lookup object won't be available for later created instances
185      * any more. For already existing instances this operation does not have any
186      * impact.
187      *
188      * @param prefix the variable prefix
189      * @return a flag whether for this prefix a lookup object had been
190      * registered
191      */
192     public static boolean deregisterGlobalLookup(String prefix)
193     {
194         synchronized (globalLookups)
195         {
196             return globalLookups.remove(prefix) != null;
197         }
198     }
199 
200     /**
201      * Registers the given lookup object for the specified prefix at this
202      * instance. From now on this lookup object will be used for variables that
203      * have the specified prefix.
204      *
205      * @param prefix the variable prefix (must not be <b>null</b>)
206      * @param lookup the lookup object to be used for this prefix (must not be
207      * <b>null</b>)
208      */
209     public void registerLookup(String prefix, StrLookup lookup)
210     {
211         if (prefix == null)
212         {
213             throw new IllegalArgumentException(
214                     "Prefix for lookup object must not be null!");
215         }
216         if (lookup == null)
217         {
218             throw new IllegalArgumentException(
219                     "Lookup object must not be null!");
220         }
221         localLookups.put(prefix, lookup);
222     }
223 
224     /**
225      * Deregisters the lookup object for the specified prefix at this instance.
226      * It will be removed from this instance.
227      *
228      * @param prefix the variable prefix
229      * @return a flag whether for this prefix a lookup object had been
230      * registered
231      */
232     public boolean deregisterLookup(String prefix)
233     {
234         return localLookups.remove(prefix) != null;
235     }
236 
237     /**
238      * Returns a set with the prefixes, for which lookup objects are registered
239      * at this instance. This means that variables with these prefixes can be
240      * processed.
241      *
242      * @return a set with the registered variable prefixes
243      */
244     public Set<String> prefixSet()
245     {
246         return localLookups.keySet();
247     }
248 
249     /**
250      * Returns the default lookup object.
251      *
252      * @return the default lookup object
253      */
254     public StrLookup getDefaultLookup()
255     {
256         return defaultLookup;
257     }
258 
259     /**
260      * Sets the default lookup object. This lookup object will be used for all
261      * variables without a special prefix. If it is set to <b>null</b>, such
262      * variables won't be processed.
263      *
264      * @param defaultLookup the new default lookup object
265      */
266     public void setDefaultLookup(StrLookup defaultLookup)
267     {
268         this.defaultLookup = defaultLookup;
269     }
270 
271     /**
272      * Resolves the specified variable. This implementation will try to extract
273      * a variable prefix from the given variable name (the first colon (':') is
274      * used as prefix separator). It then passes the name of the variable with
275      * the prefix stripped to the lookup object registered for this prefix. If
276      * no prefix can be found or if the associated lookup object cannot resolve
277      * this variable, the default lookup object will be used.
278      *
279      * @param var the name of the variable whose value is to be looked up
280      * @return the value of this variable or <b>null</b> if it cannot be
281      * resolved
282      */
283     @Override
284     public String lookup(String var)
285     {
286         if (var == null)
287         {
288             return null;
289         }
290 
291         int prefixPos = var.indexOf(PREFIX_SEPARATOR);
292         if (prefixPos >= 0)
293         {
294             String prefix = var.substring(0, prefixPos);
295             String name = var.substring(prefixPos + 1);
296             String value = fetchLookupForPrefix(prefix).lookup(name);
297             if (value == null && getParentInterpolator() != null)
298             {
299                 value = getParentInterpolator().lookup(name);
300             }
301             if (value != null)
302             {
303                 return value;
304             }
305         }
306         String value = fetchNoPrefixLookup().lookup(var);
307         if (value == null && getParentInterpolator() != null)
308         {
309             value = getParentInterpolator().lookup(var);
310         }
311         return value;
312     }
313 
314     /**
315      * Returns the lookup object to be used for variables without a prefix. This
316      * implementation will check whether a default lookup object was set. If
317      * this is the case, it will be returned. Otherwise a <b>null</b> lookup
318      * object will be returned (never <b>null</b>).
319      *
320      * @return the lookup object to be used for variables without a prefix
321      */
322     protected StrLookup fetchNoPrefixLookup()
323     {
324         return (getDefaultLookup() != null) ? getDefaultLookup() : StrLookup.noneLookup();
325     }
326 
327     /**
328      * Obtains the lookup object for the specified prefix. This method is called
329      * by the {@code lookup()} method. This implementation will check
330      * whether a lookup object is registered for the given prefix. If not, a
331      * <b>null</b> lookup object will be returned (never <b>null</b>).
332      *
333      * @param prefix the prefix
334      * @return the lookup object to be used for this prefix
335      */
336     protected StrLookup fetchLookupForPrefix(String prefix)
337     {
338         StrLookup lookup = localLookups.get(prefix);
339         if (lookup == null)
340         {
341             lookup = StrLookup.noneLookup();
342         }
343         return lookup;
344     }
345 
346     /**
347      * Registers the local lookup instances for the given interpolator.
348      *
349      * @param interpolator the instance receiving the local lookups
350      * @since upcoming
351      */
352     public void registerLocalLookups(ConfigurationInterpolator interpolator)
353     {
354         interpolator.localLookups.putAll(localLookups);
355     }
356 
357     /**
358      * Sets the parent interpolator. This object is used if the interpolation is nested
359      * hierarchically and the current interpolation object cannot resolve a variable.
360      *
361      * @param parentInterpolator the parent interpolator object or <b>null</b>
362      * @since upcoming
363      */
364     public void setParentInterpolator(ConfigurationInterpolator parentInterpolator)
365     {
366         this.parentInterpolator = parentInterpolator;
367     }
368 
369     /**
370      * Requests the parent interpolator. This object is used if the interpolation is nested
371      * hierarchically and the current interpolation
372      *
373      * @return the parent interpolator or <b>null</b>
374      * @since upcoming
375      */
376     public ConfigurationInterpolator getParentInterpolator()
377     {
378         return this.parentInterpolator;
379     }
380 
381     // static initializer, sets up the map with the standard lookups
382     static
383     {
384         globalLookups = new HashMap<String, StrLookup>();
385         globalLookups.put(PREFIX_SYSPROPERTIES, StrLookup.systemPropertiesLookup());
386         globalLookups.put(PREFIX_CONSTANTS, new ConstantLookup());
387         globalLookups.put(PREFIX_ENVIRONMENT, new EnvironmentLookup());
388     }
389 }