    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.combined;
    018
    019import java.lang.reflect.Constructor;
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.Map;
    024
    025import org.apache.commons.configuration2.Configuration;
    026import org.apache.commons.configuration2.ConfigurationUtils;
    027import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
    028import org.apache.commons.configuration2.builder.BuilderParameters;
    029import org.apache.commons.configuration2.builder.ConfigurationBuilder;
    030import org.apache.commons.configuration2.ex.ConfigurationException;
    031
    032/**
    033 * <p>
    034 * A fully-functional, reflection-based implementation of the
    035 * {@code ConfigurationBuilderProvider} interface which can deal with the
    036 * default tags defining configuration sources.
    037 * </p>
    038 * <p>
    039 * An instance of this class is initialized with the names of the
    040 * {@code ConfigurationBuilder} class used by this provider and the concrete
    041 * {@code Configuration} class. The {@code ConfigurationBuilder} class must be
    042 * derived from {@link BasicConfigurationBuilder}. When asked for the builder
    043 * object, an instance of the builder class is created and initialized from the
    044 * bean declaration associated with the current configuration source.
    045 * </p>
    046 * <p>
    047 * {@code ConfigurationBuilder} objects are configured using parameter objects.
    048 * When declaring configuration sources in XML it should not be necessary to
    049 * define the single parameter objects. Rather, simple and complex properties
    050 * are set in the typical way of a bean declaration (i.e. as attributes of the
    051 * current XML element or as child elements). This class creates all supported
    052 * parameter objects (whose names also must be provided at construction time)
    053 * and takes care that their properties are initialized according to the current
    054 * bean declaration.
    055 * </p>
    056 * <p>
    057 * The use of reflection to create builder instances allows a generic
    058 * implementation supporting many concrete builder classes. Another reason for
    059 * this approach is that builder classes are only loaded if actually needed.
    060 * Some specialized {@code Configuration} implementations require specific
    061 * external dependencies which should not be mandatory for the use of
    062 * {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded,
    063 * an application only has to include the dependencies it actually uses.
    064 * </p>
    065 *
    066 * @version $Id: BaseConfigurationBuilderProvider.java 1842194 2018-09-27 22:24:23Z ggregory $
    067 * @since 2.0
    068 */
    069public class BaseConfigurationBuilderProvider implements
    070        ConfigurationBuilderProvider
    071{
    072    /** The types of the constructor parameters for a basic builder. */
    073    private static final Class<?>[] CTOR_PARAM_TYPES = {
    074            Class.class, Map.class, Boolean.TYPE
    075    };
    076
    077    /** The name of the builder class. */
    078    private final String builderClass;
    079
    080    /** The name of a builder class with reloading support. */
    081    private final String reloadingBuilderClass;
    082
    083    /** Stores the name of the configuration class to be created. */
    084    private final String configurationClass;
    085
    086    /** A collection with the names of parameter classes. */
    087    private final Collection<String> parameterClasses;
    088
    089    /**
    090     * Creates a new instance of {@code BaseConfigurationBuilderProvider} and
    091     * initializes all its properties.
    092     *
    093     * @param bldrCls the name of the builder class (must not be <b>null</b>)
    094     * @param reloadBldrCls the name of a builder class to be used if reloading
    095     *        support is required (<b>null</b> if reloading is not supported)
    096     * @param configCls the name of the configuration class (must not be
    097     *        <b>null</b>)
    098     * @param paramCls a collection with the names of parameters classes
    099     * @throws IllegalArgumentException if a required parameter is missing
    100     */
    101    public BaseConfigurationBuilderProvider(final String bldrCls,
    102            final String reloadBldrCls, final String configCls, final Collection<String> paramCls)
    103    {
    104        if (bldrCls == null)
    105        {
    106            throw new IllegalArgumentException(
    107                    "Builder class must not be null!");
    108        }
    109        if (configCls == null)
    110        {
    111            throw new IllegalArgumentException(
    112                    "Configuration class must not be null!");
    113        }
    114
    115        builderClass = bldrCls;
    116        reloadingBuilderClass = reloadBldrCls;
    117        configurationClass = configCls;
    118        parameterClasses = initParameterClasses(paramCls);
    119    }
    120
    121    /**
    122     * Returns the name of the class of the builder created by this provider.
    123     *
    124     * @return the builder class
    125     */
    126    public String getBuilderClass()
    127    {
    128        return builderClass;
    129    }
    130
    131    /**
    132     * Returns the name of the class of the builder created by this provider if
    133     * the reload flag is set. If this method returns <b>null</b>, reloading
    134     * builders are not supported by this provider.
    135     *
    136     * @return the reloading builder class
    137     */
    138    public String getReloadingBuilderClass()
    139    {
    140        return reloadingBuilderClass;
    141    }
    142
    143    /**
    144     * Returns the name of the configuration class created by the builder
    145     * produced by this provider.
    146     *
    147     * @return the configuration class
    148     */
    149    public String getConfigurationClass()
    150    {
    151        return configurationClass;
    152    }
    153
    154    /**
    155     * Returns an unmodifiable collection with the names of parameter classes
    156     * supported by this provider.
    157     *
    158     * @return the parameter classes
    159     */
    160    public Collection<String> getParameterClasses()
    161    {
    162        return parameterClasses;
    163    }
    164
    165    /**
    166     * {@inheritDoc} This implementation delegates to some protected methods to
    167     * create a new builder instance using reflection and to configure it with
    168     * parameter values defined by the passed in {@code BeanDeclaration}.
    169     */
    170    @Override
    171    public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder(
    172            final ConfigurationDeclaration decl) throws ConfigurationException
    173    {
    174        try
    175        {
    176            final Collection<BuilderParameters> params = createParameterObjects();
    177            initializeParameterObjects(decl, params);
    178            final BasicConfigurationBuilder<? extends Configuration> builder =
    179                    createBuilder(decl, params);
    180            configureBuilder(builder, decl, params);
    181            return builder;
    182        }
    183        catch (final ConfigurationException cex)
    184        {
    185            throw cex;
    186        }
    187        catch (final Exception ex)
    188        {
    189            throw new ConfigurationException(ex);
    190        }
    191    }
    192
    193    /**
    194     * Determines the <em>allowFailOnInit</em> flag for the newly created
    195     * builder based on the given {@code ConfigurationDeclaration}. Some
    196     * combinations of flags in the declaration say that a configuration source
    197     * is optional, but an empty instance should be created if its creation
    198     * fail.
    199     *
    200     * @param decl the current {@code ConfigurationDeclaration}
    201     * @return the value of the <em>allowFailOnInit</em> flag
    202     */
    203    protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl)
    204    {
    205        return decl.isOptional() && decl.isForceCreate();
    206    }
    207
    208    /**
    209     * Creates a collection of parameter objects to be used for configuring the
    210     * builder. This method creates instances of the parameter classes passed to
    211     * the constructor.
    212     *
    213     * @return a collection with parameter objects for the builder
    214     * @throws Exception if an error occurs while creating parameter objects via
    215     *         reflection
    216     */
    217    protected Collection<BuilderParameters> createParameterObjects()
    218            throws Exception
    219    {
    220        final Collection<BuilderParameters> params =
    221                new ArrayList<>(
    222                        getParameterClasses().size());
    223        for (final String paramcls : getParameterClasses())
    224        {
    225            params.add(createParameterObject(paramcls));
    226        }
    227        return params;
    228    }
    229
    230    /**
    231     * Initializes the parameter objects with data stored in the current bean
    232     * declaration. This method is called before the newly created builder
    233     * instance is configured with the parameter objects. It maps attributes of
    234     * the bean declaration to properties of parameter objects. In addition,
    235     * it invokes the parent {@code CombinedConfigurationBuilder} so that the
    236     * parameters object can inherit properties already defined for this
    237     * builder.
    238     *
    239     * @param decl the current {@code ConfigurationDeclaration}
    240     * @param params the collection with (uninitialized) parameter objects
    241     * @throws Exception if an error occurs
    242     */
    243    protected void initializeParameterObjects(final ConfigurationDeclaration decl,
    244            final Collection<BuilderParameters> params) throws Exception
    245    {
    246        inheritParentBuilderProperties(decl, params);
    247        final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params);
    248        decl.getConfigurationBuilder().initBean(wrapBean, decl);
    249    }
    250
    251    /**
    252     * Passes all parameter objects to the parent
    253     * {@code CombinedConfigurationBuilder} so that properties already defined
    254     * for the parent builder can be added. This method is called before the
    255     * parameter objects are initialized from the definition configuration. This
    256     * way properties from the parent builder are inherited, but can be
    257     * overridden for child configurations.
    258     *
    259     * @param decl the current {@code ConfigurationDeclaration}
    260     * @param params the collection with (uninitialized) parameter objects
    261     */
    262    protected void inheritParentBuilderProperties(
    263            final ConfigurationDeclaration decl, final Collection<BuilderParameters> params)
    264    {
    265        for (final BuilderParameters p : params)
    266        {
    267            decl.getConfigurationBuilder().initChildBuilderParameters(p);
    268        }
    269    }
    270
    271    /**
    272     * Creates a new, uninitialized instance of the builder class managed by
    273     * this provider. This implementation determines the builder class to be
    274     * used by delegating to {@code determineBuilderClass()}. It then calls the
    275     * constructor expecting the configuration class, the map with properties,
    276     * and the<em>allowFailOnInit</em> flag.
    277     *
    278     * @param decl the current {@code ConfigurationDeclaration}
    279     * @param params initialization parameters for the new builder object
    280     * @return the newly created builder instance
    281     * @throws Exception if an error occurs
    282     */
    283    protected BasicConfigurationBuilder<? extends Configuration> createBuilder(
    284            final ConfigurationDeclaration decl, final Collection<BuilderParameters> params)
    285            throws Exception
    286    {
    287        final Class<?> bldCls =
    288                ConfigurationUtils.loadClass(determineBuilderClass(decl));
    289        final Class<?> configCls =
    290                ConfigurationUtils.loadClass(determineConfigurationClass(decl,
    291                        params));
    292        final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES);
    293        // ? extends Configuration is the minimum constraint
    294        @SuppressWarnings("unchecked")
    295        final
    296        BasicConfigurationBuilder<? extends Configuration> builder =
    297                (BasicConfigurationBuilder<? extends Configuration>) ctor
    298                        .newInstance(configCls, null, isAllowFailOnInit(decl));
    299        return builder;
    300    }
    301
    302    /**
    303     * Configures a newly created builder instance with its initialization
    304     * parameters. This method is called after a new instance was created using
    305     * reflection. This implementation passes the parameter objects to the
    306     * builder's {@code configure()} method.
    307     *
    308     * @param builder the builder to be initialized
    309     * @param decl the current {@code ConfigurationDeclaration}
    310     * @param params the collection with initialization parameter objects
    311     * @throws Exception if an error occurs
    312     */
    313    protected void configureBuilder(
    314            final BasicConfigurationBuilder<? extends Configuration> builder,
    315            final ConfigurationDeclaration decl, final Collection<BuilderParameters> params)
    316            throws Exception
    317    {
    318        builder.configure(params.toArray(new BuilderParameters[params.size()]));
    319    }
    320
    321    /**
    322     * Determines the name of the class to be used for a new builder instance.
    323     * This implementation selects between the normal and the reloading builder
    324     * class, based on the passed in {@code ConfigurationDeclaration}. If a
    325     * reloading builder is desired, but this provider has no reloading support,
    326     * an exception is thrown.
    327     *
    328     * @param decl the current {@code ConfigurationDeclaration}
    329     * @return the name of the builder class
    330     * @throws ConfigurationException if the builder class cannot be determined
    331     */
    332    protected String determineBuilderClass(final ConfigurationDeclaration decl)
    333            throws ConfigurationException
    334    {
    335        if (decl.isReload())
    336        {
    337            if (getReloadingBuilderClass() == null)
    338            {
    339                throw new ConfigurationException(
    340                        "No support for reloading for builder class "
    341                                + getBuilderClass());
    342            }
    343            return getReloadingBuilderClass();
    344        }
    345        return getBuilderClass();
    346    }
    347
    348    /**
    349     * Determines the name of the configuration class produced by the builder.
    350     * This method is called when obtaining the arguments for invoking the
    351     * constructor of the builder class. This implementation just returns the
    352     * pre-configured configuration class name. Derived classes may determine
    353     * this class name dynamically based on the passed in parameters.
    354     *
    355     * @param decl the current {@code ConfigurationDeclaration}
    356     * @param params the collection with parameter objects
    357     * @return the name of the builder's result configuration class
    358     * @throws ConfigurationException if an error occurs
    359     */
    360    protected String determineConfigurationClass(final ConfigurationDeclaration decl,
    361            final Collection<BuilderParameters> params) throws ConfigurationException
    362    {
    363        return getConfigurationClass();
    364    }
    365
    366    /**
    367     * Creates an instance of a parameter class using reflection.
    368     *
    369     * @param paramcls the parameter class
    370     * @return the newly created instance
    371     * @throws Exception if an error occurs
    372     */
    373    private static BuilderParameters createParameterObject(final String paramcls)
    374            throws Exception
    375    {
    376        final Class<?> cls = ConfigurationUtils.loadClass(paramcls);
    377        final BuilderParameters p = (BuilderParameters) cls.newInstance();
    378        return p;
    379    }
    380
    381    /**
    382     * Creates a new, unmodifiable collection for the parameter classes.
    383     *
    384     * @param paramCls the collection with parameter classes passed to the
    385     *        constructor
    386     * @return the collection to be stored
    387     */
    388    private static Collection<String> initParameterClasses(
    389            final Collection<String> paramCls)
    390    {
    391        if (paramCls == null)
    392        {
    393            return Collections.emptySet();
    394        }
    395        return Collections.unmodifiableCollection(new ArrayList<>(
    396                paramCls));
    397    }
    398}