    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    }