BaseConfigurationBuilderProvider.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.builder.combined;

  18. import java.lang.reflect.Constructor;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.Map;

  23. import org.apache.commons.configuration2.Configuration;
  24. import org.apache.commons.configuration2.ConfigurationUtils;
  25. import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
  26. import org.apache.commons.configuration2.builder.BuilderParameters;
  27. import org.apache.commons.configuration2.builder.ConfigurationBuilder;
  28. import org.apache.commons.configuration2.ex.ConfigurationException;

  29. /**
  30.  * <p>
  31.  * A fully-functional, reflection-based implementation of the {@code ConfigurationBuilderProvider} interface which can
  32.  * deal with the default tags defining configuration sources.
  33.  * </p>
  34.  * <p>
  35.  * An instance of this class is initialized with the names of the {@code ConfigurationBuilder} class used by this
  36.  * provider and the concrete {@code Configuration} class. The {@code ConfigurationBuilder} class must be derived from
  37.  * {@link BasicConfigurationBuilder}. When asked for the builder object, an instance of the builder class is created and
  38.  * initialized from the bean declaration associated with the current configuration source.
  39.  * </p>
  40.  * <p>
  41.  * {@code ConfigurationBuilder} objects are configured using parameter objects. When declaring configuration sources in
  42.  * XML it should not be necessary to define the single parameter objects. Rather, simple and complex properties are set
  43.  * in the typical way of a bean declaration (i.e. as attributes of the current XML element or as child elements). This
  44.  * class creates all supported parameter objects (whose names also must be provided at construction time) and takes care
  45.  * that their properties are initialized according to the current bean declaration.
  46.  * </p>
  47.  * <p>
  48.  * The use of reflection to create builder instances allows a generic implementation supporting many concrete builder
  49.  * classes. Another reason for this approach is that builder classes are only loaded if actually needed. Some
  50.  * specialized {@code Configuration} implementations require specific external dependencies which should not be
  51.  * mandatory for the use of {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded, an application
  52.  * only has to include the dependencies it actually uses.
  53.  * </p>
  54.  *
  55.  * @since 2.0
  56.  */
  57. public class BaseConfigurationBuilderProvider implements ConfigurationBuilderProvider {
  58.     /** The types of the constructor parameters for a basic builder. */
  59.     private static final Class<?>[] CTOR_PARAM_TYPES = {Class.class, Map.class, Boolean.TYPE};

  60.     /**
  61.      * Creates an instance of a parameter class using reflection.
  62.      *
  63.      * @param paramcls the parameter class
  64.      * @return the newly created instance
  65.      * @throws Exception if an error occurs
  66.      */
  67.     private static BuilderParameters createParameterObject(final String paramcls) throws ReflectiveOperationException {
  68.         return (BuilderParameters) ConfigurationUtils.loadClass(paramcls).getConstructor().newInstance();
  69.     }

  70.     /**
  71.      * Creates a new, unmodifiable collection for the parameter classes.
  72.      *
  73.      * @param paramCls the collection with parameter classes passed to the constructor
  74.      * @return the collection to be stored
  75.      */
  76.     private static Collection<String> initParameterClasses(final Collection<String> paramCls) {
  77.         if (paramCls == null) {
  78.             return Collections.emptySet();
  79.         }
  80.         return Collections.unmodifiableCollection(new ArrayList<>(paramCls));
  81.     }

  82.     /** The name of the builder class. */
  83.     private final String builderClass;

  84.     /** The name of a builder class with reloading support. */
  85.     private final String reloadingBuilderClass;

  86.     /** Stores the name of the configuration class to be created. */
  87.     private final String configurationClass;

  88.     /** A collection with the names of parameter classes. */
  89.     private final Collection<String> parameterClasses;

  90.     /**
  91.      * Creates a new instance of {@code BaseConfigurationBuilderProvider} and initializes all its properties.
  92.      *
  93.      * @param bldrCls the name of the builder class (must not be <strong>null</strong>)
  94.      * @param reloadBldrCls the name of a builder class to be used if reloading support is required (<strong>null</strong> if
  95.      *        reloading is not supported)
  96.      * @param configCls the name of the configuration class (must not be <strong>null</strong>)
  97.      * @param paramCls a collection with the names of parameters classes
  98.      * @throws IllegalArgumentException if a required parameter is missing
  99.      */
  100.     public BaseConfigurationBuilderProvider(final String bldrCls, final String reloadBldrCls, final String configCls, final Collection<String> paramCls) {
  101.         if (bldrCls == null) {
  102.             throw new IllegalArgumentException("Builder class must not be null!");
  103.         }
  104.         if (configCls == null) {
  105.             throw new IllegalArgumentException("Configuration class must not be null!");
  106.         }

  107.         builderClass = bldrCls;
  108.         reloadingBuilderClass = reloadBldrCls;
  109.         configurationClass = configCls;
  110.         parameterClasses = initParameterClasses(paramCls);
  111.     }

  112.     /**
  113.      * Configures a newly created builder instance with its initialization parameters. This method is called after a new
  114.      * instance was created using reflection. This implementation passes the parameter objects to the builder's
  115.      * {@code configure()} method.
  116.      *
  117.      * @param builder the builder to be initialized
  118.      * @param decl the current {@code ConfigurationDeclaration}
  119.      * @param params the collection with initialization parameter objects
  120.      * @throws Exception if an error occurs
  121.      */
  122.     protected void configureBuilder(final BasicConfigurationBuilder<? extends Configuration> builder, final ConfigurationDeclaration decl,
  123.         final Collection<BuilderParameters> params) throws Exception {
  124.         builder.configure(params.toArray(new BuilderParameters[params.size()]));
  125.     }

  126.     /**
  127.      * Creates a new, uninitialized instance of the builder class managed by this provider. This implementation determines
  128.      * the builder class to be used by delegating to {@code determineBuilderClass()}. It then calls the constructor
  129.      * expecting the configuration class, the map with properties, and the<em>allowFailOnInit</em> flag.
  130.      *
  131.      * @param decl the current {@code ConfigurationDeclaration}
  132.      * @param params initialization parameters for the new builder object
  133.      * @return the newly created builder instance
  134.      * @throws Exception if an error occurs
  135.      */
  136.     protected BasicConfigurationBuilder<? extends Configuration> createBuilder(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params)
  137.         throws Exception {
  138.         final Class<?> bldCls = ConfigurationUtils.loadClass(determineBuilderClass(decl));
  139.         final Class<?> configCls = ConfigurationUtils.loadClass(determineConfigurationClass(decl, params));
  140.         final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES);
  141.         // ? extends Configuration is the minimum constraint
  142.         @SuppressWarnings("unchecked")
  143.         final BasicConfigurationBuilder<? extends Configuration> builder = (BasicConfigurationBuilder<? extends Configuration>) ctor.newInstance(configCls,
  144.             null, isAllowFailOnInit(decl));
  145.         return builder;
  146.     }

  147.     /**
  148.      * Creates a collection of parameter objects to be used for configuring the builder. This method creates instances of
  149.      * the parameter classes passed to the constructor.
  150.      *
  151.      * @return a collection with parameter objects for the builder
  152.      * @throws Exception if an error occurs while creating parameter objects via reflection
  153.      */
  154.     protected Collection<BuilderParameters> createParameterObjects() throws Exception {
  155.         final Collection<BuilderParameters> params = new ArrayList<>(getParameterClasses().size());
  156.         for (final String paramcls : getParameterClasses()) {
  157.             params.add(createParameterObject(paramcls));
  158.         }
  159.         return params;
  160.     }

  161.     /**
  162.      * Determines the name of the class to be used for a new builder instance. This implementation selects between the
  163.      * normal and the reloading builder class, based on the passed in {@code ConfigurationDeclaration}. If a reloading
  164.      * builder is desired, but this provider has no reloading support, an exception is thrown.
  165.      *
  166.      * @param decl the current {@code ConfigurationDeclaration}
  167.      * @return the name of the builder class
  168.      * @throws ConfigurationException if the builder class cannot be determined
  169.      */
  170.     protected String determineBuilderClass(final ConfigurationDeclaration decl) throws ConfigurationException {
  171.         if (decl.isReload()) {
  172.             if (getReloadingBuilderClass() == null) {
  173.                 throw new ConfigurationException("No support for reloading for builder class " + getBuilderClass());
  174.             }
  175.             return getReloadingBuilderClass();
  176.         }
  177.         return getBuilderClass();
  178.     }

  179.     /**
  180.      * Determines the name of the configuration class produced by the builder. This method is called when obtaining the
  181.      * arguments for invoking the constructor of the builder class. This implementation just returns the pre-configured
  182.      * configuration class name. Derived classes may determine this class name dynamically based on the passed in
  183.      * parameters.
  184.      *
  185.      * @param decl the current {@code ConfigurationDeclaration}
  186.      * @param params the collection with parameter objects
  187.      * @return the name of the builder's result configuration class
  188.      * @throws ConfigurationException if an error occurs
  189.      */
  190.     protected String determineConfigurationClass(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params)
  191.         throws ConfigurationException {
  192.         return getConfigurationClass();
  193.     }

  194.     /**
  195.      * Gets the name of the class of the builder created by this provider.
  196.      *
  197.      * @return the builder class
  198.      */
  199.     public String getBuilderClass() {
  200.         return builderClass;
  201.     }

  202.     /**
  203.      * {@inheritDoc} This implementation delegates to some protected methods to create a new builder instance using
  204.      * reflection and to configure it with parameter values defined by the passed in {@code BeanDeclaration}.
  205.      */
  206.     @Override
  207.     public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException {
  208.         try {
  209.             final Collection<BuilderParameters> params = createParameterObjects();
  210.             initializeParameterObjects(decl, params);
  211.             final BasicConfigurationBuilder<? extends Configuration> builder = createBuilder(decl, params);
  212.             configureBuilder(builder, decl, params);
  213.             return builder;
  214.         } catch (final ConfigurationException cex) {
  215.             throw cex;
  216.         } catch (final Exception ex) {
  217.             throw new ConfigurationException(ex);
  218.         }
  219.     }

  220.     /**
  221.      * Gets the name of the configuration class created by the builder produced by this provider.
  222.      *
  223.      * @return the configuration class
  224.      */
  225.     public String getConfigurationClass() {
  226.         return configurationClass;
  227.     }

  228.     /**
  229.      * Gets an unmodifiable collection with the names of parameter classes supported by this provider.
  230.      *
  231.      * @return the parameter classes
  232.      */
  233.     public Collection<String> getParameterClasses() {
  234.         return parameterClasses;
  235.     }

  236.     /**
  237.      * Gets the name of the class of the builder created by this provider if the reload flag is set. If this method
  238.      * returns <strong>null</strong>, reloading builders are not supported by this provider.
  239.      *
  240.      * @return the reloading builder class
  241.      */
  242.     public String getReloadingBuilderClass() {
  243.         return reloadingBuilderClass;
  244.     }

  245.     /**
  246.      * Passes all parameter objects to the parent {@code CombinedConfigurationBuilder} so that properties already defined
  247.      * for the parent builder can be added. This method is called before the parameter objects are initialized from the
  248.      * definition configuration. This way properties from the parent builder are inherited, but can be overridden for child
  249.      * configurations.
  250.      *
  251.      * @param decl the current {@code ConfigurationDeclaration}
  252.      * @param params the collection with (uninitialized) parameter objects
  253.      */
  254.     protected void inheritParentBuilderProperties(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) {
  255.         params.forEach(p -> decl.getConfigurationBuilder().initChildBuilderParameters(p));
  256.     }

  257.     /**
  258.      * Initializes the parameter objects with data stored in the current bean declaration. This method is called before the
  259.      * newly created builder instance is configured with the parameter objects. It maps attributes of the bean declaration
  260.      * to properties of parameter objects. In addition, it invokes the parent {@code CombinedConfigurationBuilder} so that
  261.      * the parameters object can inherit properties already defined for this builder.
  262.      *
  263.      * @param decl the current {@code ConfigurationDeclaration}
  264.      * @param params the collection with (uninitialized) parameter objects
  265.      * @throws Exception if an error occurs
  266.      */
  267.     protected void initializeParameterObjects(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception {
  268.         inheritParentBuilderProperties(decl, params);
  269.         final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params);
  270.         decl.getConfigurationBuilder().initBean(wrapBean, decl);
  271.     }

  272.     /**
  273.      * Determines the <em>allowFailOnInit</em> flag for the newly created builder based on the given
  274.      * {@code ConfigurationDeclaration}. Some combinations of flags in the declaration say that a configuration source is
  275.      * optional, but an empty instance should be created if its creation fail.
  276.      *
  277.      * @param decl the current {@code ConfigurationDeclaration}
  278.      * @return the value of the <em>allowFailOnInit</em> flag
  279.      */
  280.     protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) {
  281.         return decl.isOptional() && decl.isForceCreate();
  282.     }
  283. }