001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration.interpol;
018    
019    import java.util.HashMap;
020    import java.util.Map;
021    import java.util.Set;
022    
023    import org.apache.commons.lang.text.StrLookup;
024    
025    /**
026     * <p>
027     * A class that handles interpolation (variable substitution) for configuration
028     * objects.
029     * </p>
030     * <p>
031     * Each instance of <code>AbstractConfiguration</code> is associated with an
032     * object of this class. All interpolation tasks are delegated to this object.
033     * </p>
034     * <p>
035     * <code>ConfigurationInterpolator</code> works together with the
036     * <code>StrSubstitutor</code> class from <a
037     * href="http://commons.apache.org/lang">Commons Lang</a>. By extending
038     * <code>StrLookup</code> it is able to provide values for variables that
039     * appear in expressions.
040     * </p>
041     * <p>
042     * The basic idea of this class is that it can maintain a set of primitive
043     * <code>StrLookup</code> objects, each of which is identified by a special
044     * prefix. The variables to be processed have the form
045     * <code>${prefix:name}</code>. <code>ConfigurationInterpolator</code> will
046     * extract the prefix and determine, which primitive lookup object is registered
047     * for it. Then the name of the variable is passed to this object to obtain the
048     * actual value. It is also possible to define a default lookup object, which
049     * will be used for variables that do not have a prefix or that cannot be
050     * resolved by their associated lookup object.
051     * </p>
052     * <p>
053     * When a new instance of this class is created it is initialized with a default
054     * set of primitive lookup objects. This set can be customized using the static
055     * methods <code>registerGlobalLookup()</code> and
056     * <code>deregisterGlobalLookup()</code>. Per default it contains the
057     * following standard lookup objects:
058     * </p>
059     * <p>
060     * <table border="1">
061     * <tr>
062     * <th>Prefix</th>
063     * <th>Lookup object</th>
064     * </tr>
065     * <tr>
066     * <td valign="top">sys</td>
067     * <td>With this prefix a lookup object is associated that is able to resolve
068     * system properties.</td>
069     * </tr>
070     * <tr>
071     * <td valign="top">const</td>
072     * <td>The <code>const</code> prefix indicates that a variable is to be
073     * interpreted as a constant member field of a class (i.e. a field with the
074     * <b>static final</b> modifiers). The name of the variable must be of the form
075     * <code>&lt;full qualified class name&gt;.&lt;field name&gt;</code>, e.g.
076     * <code>org.apache.commons.configuration.interpol.ConfigurationInterpolator.PREFIX_CONSTANTS
077     * </code>.</td>
078     * </tr>
079     * </table>
080     * </p>
081     * <p>
082     * After an instance has been created the current set of lookup objects can be
083     * modified using the <code>registerLookup()</code> and
084     * <code>deregisterLookup()</code> methods. The default lookup object (that is
085     * invoked for variables without a prefix) can be set with the
086     * <code>setDefaultLookup()</code> method. (If a
087     * <code>ConfigurationInterpolator</code> instance is created by a
088     * configuration object, this lookup points to the configuration itself, so that
089     * variables are resolved using the configuration's properties. This ensures
090     * backward compatibility to earlier version of Commons Configuration.)
091     * </p>
092     * <p>
093     * Implementation node: Instances of this class are not thread-safe related to
094     * modifications of their current set of registered lookup objects. It is
095     * intended that each instance is associated with a single
096     * <code>Configuration</code> object and used for its interpolation tasks.
097     * </p>
098     *
099     * @version $Id: ConfigurationInterpolator.java 833923 2009-11-08 20:53:52Z 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 globalLookups;
131    
132        /** A map with the locally registered lookup objects. */
133        private Map 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</code>.
143         */
144        public ConfigurationInterpolator()
145        {
146            synchronized (globalLookups)
147            {
148                localLookups = new HashMap(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 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        public String lookup(String var)
284        {
285            if (var == null)
286            {
287                return null;
288            }
289    
290            int prefixPos = var.indexOf(PREFIX_SEPARATOR);
291            if (prefixPos >= 0)
292            {
293                String prefix = var.substring(0, prefixPos);
294                String name = var.substring(prefixPos + 1);
295                String value = fetchLookupForPrefix(prefix).lookup(name);
296                if (value == null && getParentInterpolator() != null)
297                {
298                    value = getParentInterpolator().fetchLookupForPrefix(prefix).lookup(name);
299                }
300                if (value != null)
301                {
302                    return value;
303                }
304            }
305            String value = fetchNoPrefixLookup().lookup(var);
306            if (value == null && getParentInterpolator() != null)
307            {
308                value = getParentInterpolator().fetchNoPrefixLookup().lookup(var);
309            }
310            return value;
311        }
312    
313        /**
314         * Returns the lookup object to be used for variables without a prefix. This
315         * implementation will check whether a default lookup object was set. If
316         * this is the case, it will be returned. Otherwise a <b>null</b> lookup
317         * object will be returned (never <code>null</code>).
318         *
319         * @return the lookup object to be used for variables without a prefix
320         */
321        protected StrLookup fetchNoPrefixLookup()
322        {
323            return (getDefaultLookup() != null) ? getDefaultLookup() : StrLookup.noneLookup();
324        }
325    
326        /**
327         * Obtains the lookup object for the specified prefix. This method is called
328         * by the <code>lookup()</code> method. This implementation will check
329         * whether a lookup object is registered for the given prefix. If not, a
330         * <b>null</b> lookup object will be returned (never <code>null</code>).
331         *
332         * @param prefix the prefix
333         * @return the lookup object to be used for this prefix
334         */
335        protected StrLookup fetchLookupForPrefix(String prefix)
336        {
337            StrLookup lookup = (StrLookup) localLookups.get(prefix);
338            if (lookup == null)
339            {
340                lookup = StrLookup.noneLookup();
341            }
342            return lookup;
343        }
344    
345        /**
346         * Registers the local lookup instances for the given interpolator.
347         *
348         * @param interpolator the instance receiving the local lookups
349         * @since upcoming
350         */
351        public void registerLocalLookups(ConfigurationInterpolator interpolator)
352        {
353            interpolator.localLookups.putAll(localLookups);
354        }
355    
356        /**
357         * Sets the parent interpolator. This object is used if the interpolation is nested
358         * hierarchically and the current interpolation object cannot resolve a variable.
359         *
360         * @param parentInterpolator the parent interpolator object or <code>null</code>
361         * @since upcoming
362         */
363        public void setParentInterpolator(ConfigurationInterpolator parentInterpolator)
364        {
365            this.parentInterpolator = parentInterpolator;
366        }
367    
368        /**
369         * Requests the parent interpolator. This object is used if the interpolation is nested
370         * hierarchically and the current interpolation
371         *
372         * @return the parent interpolator or <code>null</code>
373         * @since upcoming
374         */
375        public ConfigurationInterpolator getParentInterpolator()
376        {
377            return this.parentInterpolator;
378        }
379    
380        // static initializer, sets up the map with the standard lookups
381        static
382        {
383            globalLookups = new HashMap();
384            globalLookups.put(PREFIX_SYSPROPERTIES, StrLookup.systemPropertiesLookup());
385            globalLookups.put(PREFIX_CONSTANTS, new ConstantLookup());
386            globalLookups.put(PREFIX_ENVIRONMENT, new EnvironmentLookup());
387        }
388    }