    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} is associated with an
    032     * object of this class. All interpolation tasks are delegated to this object.
    033     * </p>
    034     * <p>
    035     * {@code ConfigurationInterpolator} works together with the
    036     * {@code StrSubstitutor} class from <a
    037     * href="http://commons.apache.org/lang">Commons Lang</a>. By extending
    038     * {@code StrLookup} 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} 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} 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()} and
    056     * {@code deregisterGlobalLookup()}. 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} 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 <full qualified class name>.<field name>}, e.g.
    076     * {@code org.apache.commons.configuration.interpol.ConfigurationInterpolator.PREFIX_CONSTANTS}.
    077     * </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()} and
    084     * {@code deregisterLookup()} methods. The default lookup object (that is
    085     * invoked for variables without a prefix) can be set with the
    086     * {@code setDefaultLookup()} method. (If a
    087     * {@code ConfigurationInterpolator} 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} object and used for its interpolation tasks.
    097     * </p>
    098     *
    099     * @version $Id: ConfigurationInterpolator.java 1210217 2011-12-04 20:57:33Z 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().fetchLookupForPrefix(prefix).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().fetchNoPrefixLookup().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    }