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}