    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 */
    017package org.apache.commons.configuration2.builder;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.HashMap;
    022import java.util.Map;
    023
    024import org.apache.commons.configuration2.ConfigurationDecoder;
    025import org.apache.commons.configuration2.io.ConfigurationLogger;
    026import org.apache.commons.configuration2.beanutils.BeanHelper;
    027import org.apache.commons.configuration2.convert.ConversionHandler;
    028import org.apache.commons.configuration2.convert.ListDelimiterHandler;
    029import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    030import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
    031import org.apache.commons.configuration2.interpol.Lookup;
    032import org.apache.commons.configuration2.sync.Synchronizer;
    033
    034/**
    035 * <p>
    036 * An implementation of {@code BuilderParameters} which handles the parameters
    037 * of a {@link ConfigurationBuilder} common to all concrete
    038 * {@code Configuration} implementations.
    039 * </p>
    040 * <p>
    041 * This class provides methods for setting standard properties supported by the
    042 * {@code AbstractConfiguration} base class. A fluent interface can be used to
    043 * set property values.
    044 * </p>
    045 * <p>
    046 * This class is not thread-safe. It is intended that an instance is constructed
    047 * and initialized by a single thread during configuration of a
    048 * {@code ConfigurationBuilder}.
    049 * </p>
    050 *
    051 * @since 2.0
    052 */
    053public class BasicBuilderParameters implements Cloneable, BuilderParameters,
    054        BasicBuilderProperties<BasicBuilderParameters>
    055{
    056    /** The key of the <em>throwExceptionOnMissing</em> property. */
    057    private static final String PROP_THROW_EXCEPTION_ON_MISSING =
    058            "throwExceptionOnMissing";
    059
    060    /** The key of the <em>listDelimiterHandler</em> property. */
    061    private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler";
    062
    063    /** The key of the <em>logger</em> property. */
    064    private static final String PROP_LOGGER = "logger";
    065
    066    /** The key for the <em>interpolator</em> property. */
    067    private static final String PROP_INTERPOLATOR = "interpolator";
    068
    069    /** The key for the <em>prefixLookups</em> property. */
    070    private static final String PROP_PREFIX_LOOKUPS = "prefixLookups";
    071
    072    /** The key for the <em>defaultLookups</em> property. */
    073    private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups";
    074
    075    /** The key for the <em>parentInterpolator</em> property. */
    076    private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator";
    077
    078    /** The key for the <em>synchronizer</em> property. */
    079    private static final String PROP_SYNCHRONIZER = "synchronizer";
    080
    081    /** The key for the <em>conversionHandler</em> property. */
    082    private static final String PROP_CONVERSION_HANDLER = "conversionHandler";
    083
    084    /** The key for the <em>configurationDecoder</em> property. */
    085    private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder";
    086
    087    /** The key for the {@code BeanHelper}. */
    088    private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX
    089            + "BeanHelper";
    090
    091    /** The map for storing the current property values. */
    092    private Map<String, Object> properties;
    093
    094    /**
    095     * Creates a new instance of {@code BasicBuilderParameters}.
    096     */
    097    public BasicBuilderParameters()
    098    {
    099        properties = new HashMap<>();
    100    }
    101
    102    /**
    103     * {@inheritDoc} This implementation returns a copy of the internal
    104     * parameters map with the values set so far. Collection structures
    105     * (e.g. for lookup objects) are stored as defensive copies, so the
    106     * original data cannot be modified.
    107     */
    108    @Override
    109    public Map<String, Object> getParameters()
    110    {
    111        final HashMap<String, Object> result =
    112                new HashMap<>(properties);
    113        if (result.containsKey(PROP_INTERPOLATOR))
    114        {
    115            // A custom ConfigurationInterpolator overrides lookups
    116            result.remove(PROP_PREFIX_LOOKUPS);
    117            result.remove(PROP_DEFAULT_LOOKUPS);
    118            result.remove(PROP_PARENT_INTERPOLATOR);
    119        }
    120
    121        createDefensiveCopies(result);
    122        return result;
    123    }
    124
    125    /**
    126     * Sets the <em>logger</em> property. With this property a concrete
    127     * {@code Log} object can be set for the configuration. Thus logging
    128     * behavior can be controlled.
    129     *
    130     * @param log the {@code Log} for the configuration produced by this builder
    131     * @return a reference to this object for method chaining
    132     */
    133    @Override
    134    public BasicBuilderParameters setLogger(final ConfigurationLogger log)
    135    {
    136        return setProperty(PROP_LOGGER, log);
    137    }
    138
    139    /**
    140     * Sets the value of the <em>throwExceptionOnMissing</em> property. This
    141     * property controls the configuration's behavior if missing properties are
    142     * queried: a value of <b>true</b> causes the configuration to throw an
    143     * exception, for a value of <b>false</b> it will return <b>null</b> values.
    144     * (Note: Methods returning a primitive data type will always throw an
    145     * exception if the property is not defined.)
    146     *
    147     * @param b the value of the property
    148     * @return a reference to this object for method chaining
    149     */
    150    @Override
    151    public BasicBuilderParameters setThrowExceptionOnMissing(final boolean b)
    152    {
    153        return setProperty(PROP_THROW_EXCEPTION_ON_MISSING, Boolean.valueOf(b));
    154    }
    155
    156    /**
    157     * Sets the value of the <em>listDelimiterHandler</em> property. This
    158     * property defines the object responsible for dealing with list delimiter
    159     * and escaping characters. Note:
    160     * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration}
    161     * does not allow setting this property to <b>null</b>. If the default
    162     * {@code ListDelimiterHandler} is to be used, do not call this method.
    163     *
    164     * @param handler the {@code ListDelimiterHandler}
    165     * @return a reference to this object for method chaining
    166     */
    167    @Override
    168    public BasicBuilderParameters setListDelimiterHandler(
    169            final ListDelimiterHandler handler)
    170    {
    171        return setProperty(PROP_LIST_DELIMITER_HANDLER, handler);
    172    }
    173
    174    /**
    175     * {@inheritDoc} The passed in {@code ConfigurationInterpolator} is set
    176     * without modifications.
    177     */
    178    @Override
    179    public BasicBuilderParameters setInterpolator(final ConfigurationInterpolator ci)
    180    {
    181        return setProperty(PROP_INTERPOLATOR, ci);
    182    }
    183
    184    /**
    185     * {@inheritDoc} A defensive copy of the passed in map is created. A
    186     * <b>null</b> argument causes all prefix lookups to be removed from the
    187     * internal parameters map.
    188     */
    189    @Override
    190    public BasicBuilderParameters setPrefixLookups(
    191            final Map<String, ? extends Lookup> lookups)
    192    {
    193        if (lookups == null)
    194        {
    195            properties.remove(PROP_PREFIX_LOOKUPS);
    196            return this;
    197        }
    198        return setProperty(PROP_PREFIX_LOOKUPS,
    199                new HashMap<>(lookups));
    200    }
    201
    202    /**
    203     * {@inheritDoc} A defensive copy of the passed in collection is created. A
    204     * <b>null</b> argument causes all default lookups to be removed from the
    205     * internal parameters map.
    206     */
    207    @Override
    208    public BasicBuilderParameters setDefaultLookups(
    209            final Collection<? extends Lookup> lookups)
    210    {
    211        if (lookups == null)
    212        {
    213            properties.remove(PROP_DEFAULT_LOOKUPS);
    214            return this;
    215        }
    216        return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>(
    217                lookups));
    218    }
    219
    220    /**
    221     * {@inheritDoc} This implementation stores the passed in
    222     * {@code ConfigurationInterpolator} object in the internal parameters map.
    223     */
    224    @Override
    225    public BasicBuilderParameters setParentInterpolator(
    226            final ConfigurationInterpolator parent)
    227    {
    228        return setProperty(PROP_PARENT_INTERPOLATOR, parent);
    229    }
    230
    231    /**
    232     * {@inheritDoc} This implementation stores the passed in
    233     * {@code Synchronizer} object in the internal parameters map.
    234     */
    235    @Override
    236    public BasicBuilderParameters setSynchronizer(final Synchronizer sync)
    237    {
    238        return setProperty(PROP_SYNCHRONIZER, sync);
    239    }
    240
    241    /**
    242     * {@inheritDoc} This implementation stores the passed in
    243     * {@code ConversionHandler} object in the internal parameters map.
    244     */
    245    @Override
    246    public BasicBuilderParameters setConversionHandler(final ConversionHandler handler)
    247    {
    248        return setProperty(PROP_CONVERSION_HANDLER, handler);
    249    }
    250
    251    /**
    252     * {@inheritDoc} This implementation stores the passed in {@code BeanHelper}
    253     * object in the internal parameters map, but uses a reserved key, so that
    254     * it is not used for the initialization of properties of the managed
    255     * configuration object. The {@code fetchBeanHelper()} method can be used to
    256     * obtain the {@code BeanHelper} instance from a parameters map.
    257     */
    258    @Override
    259    public BasicBuilderParameters setBeanHelper(final BeanHelper beanHelper)
    260    {
    261        return setProperty(PROP_BEAN_HELPER, beanHelper);
    262    }
    263
    264    /**
    265     * {@inheritDoc} This implementation stores the passed in
    266     * {@code ConfigurationDecoder} object in the internal parameters map.
    267     */
    268    @Override
    269    public BasicBuilderParameters setConfigurationDecoder(
    270            final ConfigurationDecoder decoder)
    271    {
    272        return setProperty(PROP_CONFIGURATION_DECODER, decoder);
    273    }
    274
    275    /**
    276     * Merges this object with the given parameters object. This method adds all
    277     * property values defined by the passed in parameters object to the
    278     * internal storage which are not already in. So properties already defined
    279     * in this object take precedence. Property names starting with the reserved
    280     * parameter prefix are ignored.
    281     *
    282     * @param p the object whose properties should be merged (must not be
    283     *        <b>null</b>)
    284     * @throws IllegalArgumentException if the passed in object is <b>null</b>
    285     */
    286    public void merge(final BuilderParameters p)
    287    {
    288        if (p == null)
    289        {
    290            throw new IllegalArgumentException(
    291                    "Parameters to merge must not be null!");
    292        }
    293
    294        for (final Map.Entry<String, Object> e : p.getParameters().entrySet())
    295        {
    296            if (!properties.containsKey(e.getKey())
    297                    && !e.getKey().startsWith(RESERVED_PARAMETER_PREFIX))
    298            {
    299                storeProperty(e.getKey(), e.getValue());
    300            }
    301        }
    302    }
    303
    304    /**
    305     * Inherits properties from the specified map. This can be used for instance
    306     * to reuse parameters from one builder in another builder - also in
    307     * parent-child relations in which a parent builder creates child builders.
    308     * The purpose of this method is to let a concrete implementation decide
    309     * which properties can be inherited. Because parameters are basically
    310     * organized as a map it would be possible to simply copy over all
    311     * properties from the source object. However, this is not appropriate in
    312     * all cases. For instance, some properties - like a
    313     * {@code ConfigurationInterpolator} - are tightly connected to a
    314     * configuration and cannot be reused in a different context. For other
    315     * properties, e.g. a file name, it does not make sense to copy it.
    316     * Therefore, an implementation has to be explicit in the properties it
    317     * wants to take over.
    318     *
    319     * @param source the source properties to inherit from
    320     * @throws IllegalArgumentException if the source map is <b>null</b>
    321     */
    322    public void inheritFrom(final Map<String, ?> source)
    323    {
    324        if (source == null)
    325        {
    326            throw new IllegalArgumentException(
    327                    "Source properties must not be null!");
    328        }
    329        copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER,
    330                PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER,
    331                PROP_LOGGER, PROP_SYNCHRONIZER,
    332                PROP_THROW_EXCEPTION_ON_MISSING);
    333    }
    334
    335    /**
    336     * Obtains a specification for a {@link ConfigurationInterpolator} from the
    337     * specified map with parameters. All properties related to interpolation
    338     * are evaluated and added to the specification object.
    339     *
    340     * @param params the map with parameters (must not be <b>null</b>)
    341     * @return an {@code InterpolatorSpecification} object constructed with data
    342     *         from the map
    343     * @throws IllegalArgumentException if the map is <b>null</b> or contains
    344     *         invalid data
    345     */
    346    public static InterpolatorSpecification fetchInterpolatorSpecification(
    347            final Map<String, Object> params)
    348    {
    349        checkParameters(params);
    350        return new InterpolatorSpecification.Builder()
    351                .withInterpolator(
    352                        fetchParameter(params, PROP_INTERPOLATOR,
    353                                ConfigurationInterpolator.class))
    354                .withParentInterpolator(
    355                        fetchParameter(params, PROP_PARENT_INTERPOLATOR,
    356                                ConfigurationInterpolator.class))
    357                .withPrefixLookups(fetchAndCheckPrefixLookups(params))
    358                .withDefaultLookups(fetchAndCheckDefaultLookups(params))
    359                .create();
    360    }
    361
    362    /**
    363     * Obtains the {@code BeanHelper} object from the specified map with
    364     * parameters. This method can be used to obtain an instance from a
    365     * parameters map that has been set via the {@code setBeanHelper()} method.
    366     * If no such instance is found, result is <b>null</b>.
    367     *
    368     * @param params the map with parameters (must not be <b>null</b>)
    369     * @return the {@code BeanHelper} stored in this map or <b>null</b>
    370     * @throws IllegalArgumentException if the map is <b>null</b>
    371     */
    372    public static BeanHelper fetchBeanHelper(final Map<String, Object> params)
    373    {
    374        checkParameters(params);
    375        return (BeanHelper) params.get(PROP_BEAN_HELPER);
    376    }
    377
    378    /**
    379     * Clones this object. This is useful because multiple builder instances may
    380     * use a similar set of parameters. However, single instances of parameter
    381     * objects must not assigned to multiple builders. Therefore, cloning a
    382     * parameters object provides a solution for this use case. This method
    383     * creates a new parameters object with the same content as this one. The
    384     * internal map storing the parameter values is cloned, too, also collection
    385     * structures contained in this map. However, no a full deep clone operation
    386     * is performed. Objects like a {@code ConfigurationInterpolator} or
    387     * {@code Lookup}s are shared between this and the newly created instance.
    388     *
    389     * @return a clone of this object
    390     */
    391    @Override
    392    public BasicBuilderParameters clone()
    393    {
    394        try
    395        {
    396            final BasicBuilderParameters copy =
    397                    (BasicBuilderParameters) super.clone();
    398            copy.properties = getParameters();
    399            return copy;
    400        }
    401        catch (final CloneNotSupportedException cnex)
    402        {
    403            // should not happen
    404            throw new AssertionError(cnex);
    405        }
    406    }
    407
    408    /**
    409     * Sets a property for this parameters object. Properties are stored in an
    410     * internal map. With this method a new entry can be added to this map. If
    411     * the value is <b>null</b>, the key is removed from the internal map. This
    412     * method can be used by sub classes which also store properties in a map.
    413     *
    414     * @param key the key of the property
    415     * @param value the value of the property
    416     */
    417    protected void storeProperty(final String key, final Object value)
    418    {
    419        if (value == null)
    420        {
    421            properties.remove(key);
    422        }
    423        else
    424        {
    425            properties.put(key, value);
    426        }
    427    }
    428
    429    /**
    430     * Obtains the value of the specified property from the internal map. This
    431     * method can be used by derived classes if a specific property is to be
    432     * accessed. If the given key is not found, result is <b>null</b>.
    433     *
    434     * @param key the key of the property in question
    435     * @return the value of the property with this key or <b>null</b>
    436     */
    437    protected Object fetchProperty(final String key)
    438    {
    439        return properties.get(key);
    440    }
    441
    442    /**
    443     * Copies a number of properties from the given map into this object.
    444     * Properties are only copied if they are defined in the source map.
    445     *
    446     * @param source the source map
    447     * @param keys the keys to be copied
    448     */
    449    protected void copyPropertiesFrom(final Map<String, ?> source, final String... keys)
    450    {
    451        for (final String key : keys)
    452        {
    453            final Object value = source.get(key);
    454            if (value != null)
    455            {
    456                storeProperty(key, value);
    457            }
    458        }
    459    }
    460
    461    /**
    462     * Helper method for setting a property value.
    463     *
    464     * @param key the key of the property
    465     * @param value the value of the property
    466     * @return a reference to this object
    467     */
    468    private BasicBuilderParameters setProperty(final String key, final Object value)
    469    {
    470        storeProperty(key, value);
    471        return this;
    472    }
    473
    474    /**
    475     * Creates defensive copies for collection structures when constructing the
    476     * map with parameters. It should not be possible to modify this object's
    477     * internal state when having access to the parameters map.
    478     *
    479     * @param params the map with parameters to be passed to the caller
    480     */
    481    private static void createDefensiveCopies(final HashMap<String, Object> params)
    482    {
    483        final Map<String, ? extends Lookup> prefixLookups =
    484                fetchPrefixLookups(params);
    485        if (prefixLookups != null)
    486        {
    487            params.put(PROP_PREFIX_LOOKUPS, new HashMap<>(
    488                    prefixLookups));
    489        }
    490        final Collection<? extends Lookup> defLookups = fetchDefaultLookups(params);
    491        if (defLookups != null)
    492        {
    493            params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups));
    494        }
    495    }
    496
    497    /**
    498     * Obtains the map with prefix lookups from the parameters map.
    499     *
    500     * @param params the map with parameters
    501     * @return the map with prefix lookups (may be <b>null</b>)
    502     */
    503    private static Map<String, ? extends Lookup> fetchPrefixLookups(
    504            final Map<String, Object> params)
    505    {
    506        // This is safe to cast because we either have full control over the map
    507        // and thus know the types of the contained values or have checked
    508        // the content before
    509        @SuppressWarnings("unchecked")
    510        final
    511        Map<String, ? extends Lookup> prefixLookups =
    512                (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS);
    513        return prefixLookups;
    514    }
    515
    516    /**
    517     * Tests whether the passed in map with parameters contains a map with
    518     * prefix lookups. This method is used if the parameters map is from an
    519     * insecure source and we cannot be sure that it contains valid data.
    520     * Therefore, we have to map that the key for the prefix lookups actually
    521     * points to a map containing keys and values of expected data types.
    522     *
    523     * @param params the parameters map
    524     * @return the obtained map with prefix lookups
    525     * @throws IllegalArgumentException if the map contains invalid data
    526     */
    527    private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups(
    528            final Map<String, Object> params)
    529    {
    530        final Map<?, ?> prefixes =
    531                fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class);
    532        if (prefixes == null)
    533        {
    534            return null;
    535        }
    536
    537        for (final Map.Entry<?, ?> e : prefixes.entrySet())
    538        {
    539            if (!(e.getKey() instanceof String)
    540                    || !(e.getValue() instanceof Lookup))
    541            {
    542                throw new IllegalArgumentException(
    543                        "Map with prefix lookups contains invalid data: "
    544                                + prefixes);
    545            }
    546        }
    547        return fetchPrefixLookups(params);
    548    }
    549
    550    /**
    551     * Obtains the collection with default lookups from the parameters map.
    552     *
    553     * @param params the map with parameters
    554     * @return the collection with default lookups (may be <b>null</b>)
    555     */
    556    private static Collection<? extends Lookup> fetchDefaultLookups(
    557            final Map<String, Object> params)
    558    {
    559        // This is safe to cast because we either have full control over the map
    560        // and thus know the types of the contained values or have checked
    561        // the content before
    562        @SuppressWarnings("unchecked")
    563        final
    564        Collection<? extends Lookup> defLookups =
    565                (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS);
    566        return defLookups;
    567    }
    568
    569    /**
    570     * Tests whether the passed in map with parameters contains a valid
    571     * collection with default lookups. This method works like
    572     * {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups
    573     * collection.
    574     *
    575     * @param params the map with parameters
    576     * @return the collection with default lookups (may be <b>null</b>)
    577     * @throws IllegalArgumentException if invalid data is found
    578     */
    579    private static Collection<? extends Lookup> fetchAndCheckDefaultLookups(
    580            final Map<String, Object> params)
    581    {
    582        final Collection<?> col =
    583                fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class);
    584        if (col == null)
    585        {
    586            return null;
    587        }
    588
    589        for (final Object o : col)
    590        {
    591            if (!(o instanceof Lookup))
    592            {
    593                throw new IllegalArgumentException(
    594                        "Collection with default lookups contains invalid data: "
    595                                + col);
    596            }
    597        }
    598        return fetchDefaultLookups(params);
    599    }
    600
    601    /**
    602     * Obtains a parameter from a map and performs a type check.
    603     *
    604     * @param params the map with parameters
    605     * @param key the key of the parameter
    606     * @param expClass the expected class of the parameter value
    607     * @param <T> the parameter type
    608     * @return the value of the parameter in the correct data type
    609     * @throws IllegalArgumentException if the parameter is not of the expected
    610     *         type
    611     */
    612    private static <T> T fetchParameter(final Map<String, Object> params, final String key,
    613            final Class<T> expClass)
    614    {
    615        final Object value = params.get(key);
    616        if (value == null)
    617        {
    618            return null;
    619        }
    620        if (!expClass.isInstance(value))
    621        {
    622            throw new IllegalArgumentException(String.format(
    623                    "Parameter %s is not of type %s!", key,
    624                    expClass.getSimpleName()));
    625        }
    626        return expClass.cast(value);
    627    }
    628
    629    /**
    630     * Checks whether a map with parameters is present. Throws an exception if
    631     * not.
    632     *
    633     * @param params the map with parameters to check
    634     * @throws IllegalArgumentException if the map is <b>null</b>
    635     */
    636    private static void checkParameters(final Map<String, Object> params)
    637    {
    638        if (params == null)
    639        {
    640            throw new IllegalArgumentException(
    641                    "Parameters map must not be null!");
    642        }
    643    }
    644}