    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 *     https://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.net.URL;
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.HashMap;
    024import java.util.Iterator;
    025import java.util.LinkedList;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Set;
    029import java.util.stream.Collectors;
    030
    031import org.apache.commons.configuration2.CombinedConfiguration;
    032import org.apache.commons.configuration2.Configuration;
    033import org.apache.commons.configuration2.ConfigurationLookup;
    034import org.apache.commons.configuration2.HierarchicalConfiguration;
    035import org.apache.commons.configuration2.SystemConfiguration;
    036import org.apache.commons.configuration2.XMLConfiguration;
    037import org.apache.commons.configuration2.beanutils.BeanDeclaration;
    038import org.apache.commons.configuration2.beanutils.BeanHelper;
    039import org.apache.commons.configuration2.beanutils.CombinedBeanDeclaration;
    040import org.apache.commons.configuration2.beanutils.XMLBeanDeclaration;
    041import org.apache.commons.configuration2.builder.BasicBuilderParameters;
    042import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
    043import org.apache.commons.configuration2.builder.BuilderParameters;
    044import org.apache.commons.configuration2.builder.ConfigurationBuilder;
    045import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent;
    046import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
    047import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
    048import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
    049import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
    050import org.apache.commons.configuration2.builder.XMLBuilderProperties;
    051import org.apache.commons.configuration2.event.EventListener;
    052import org.apache.commons.configuration2.ex.ConfigurationException;
    053import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    054import org.apache.commons.configuration2.interpol.Lookup;
    055import org.apache.commons.configuration2.io.FileSystem;
    056import org.apache.commons.configuration2.resolver.CatalogResolver;
    057import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
    058import org.apache.commons.configuration2.tree.OverrideCombiner;
    059import org.apache.commons.configuration2.tree.UnionCombiner;
    060import org.xml.sax.EntityResolver;
    061
    062/**
    063 * <p>
    064 * A specialized {@code ConfigurationBuilder} implementation that creates a {@link CombinedConfiguration} from multiple
    065 * configuration sources defined by an XML-based <em>configuration definition file</em>.
    066 * </p>
    067 * <p>
    068 * This class provides an easy and flexible means for loading multiple configuration sources and combining the results
    069 * into a single configuration object. The sources to be loaded are defined in an XML document that can contain certain
    070 * tags representing the different supported configuration classes. If such a tag is found, a corresponding
    071 * {@code ConfigurationBuilder} class is instantiated and initialized using the classes of the {@code beanutils} package
    072 * (namely {@link org.apache.commons.configuration2.beanutils.XMLBeanDeclaration XMLBeanDeclaration} will be used to
    073 * extract the configuration's initialization parameters, which allows for complex initialization scenarios).
    074 * </p>
    075 * <p>
    076 * It is also possible to add custom tags to the configuration definition file. For this purpose an implementation of
    077 * {@link CombinedConfigurationBuilderProvider} has to be created which is responsible for the creation of a
    078 * {@code ConfigurationBuilder} associated with the custom tag. An instance of this class has to be registered at the
    079 * {@link CombinedBuilderParametersImpl} object which is used to initialize this {@code CombinedConfigurationBuilder}.
    080 * This provider will then be called when the corresponding custom tag is detected. For many default configuration
    081 * classes providers are already registered.
    082 * </p>
    083 * <p>
    084 * The configuration definition file has the following basic structure:
    085 * </p>
    086 *
    087 * <pre>
    088 * &lt;configuration systemProperties="properties file name"&gt;
    089 *   &lt;header&gt;
    090 *     &lt;!-- Optional meta information about the combined configuration --&gt;
    091 *   &lt;/header&gt;
    092 *   &lt;override&gt;
    093 *     &lt;!-- Declarations for override configurations --&gt;
    094 *   &lt;/override&gt;
    095 *   &lt;additional&gt;
    096 *     &lt;!-- Declarations for union configurations --&gt;
    097 *   &lt;/additional&gt;
    098 * &lt;/configuration&gt;
    099 * </pre>
    100 *
    101 * <p>
    102 * The name of the root element (here {@code configuration}) is arbitrary. The optional {@code systemProperties}
    103 * attribute identifies the path to a property file containing properties that should be added to the system properties.
    104 * If specified on the root element, the system properties are set before the rest of the configuration is processed.
    105 * </p>
    106 * <p>
    107 * There are two sections (both of them are optional) for declaring <em>override</em> and <em>additional</em>
    108 * configurations. Configurations in the former section are evaluated in the order of their declaration, and properties
    109 * of configurations declared earlier hide those of configurations declared later. Configurations in the latter section
    110 * are combined to a union configuration, i.e. all of their properties are added to a large hierarchical configuration.
    111 * Configuration declarations that occur as direct children of the root element are treated as override declarations.
    112 * </p>
    113 * <p>
    114 * Each configuration declaration consists of a tag whose name is associated with a
    115 * {@code CombinedConfigurationBuilderProvider}. This can be one of the predefined tags like {@code properties}, or
    116 * {@code xml}, or a custom tag, for which a configuration builder provider was registered (as described above).
    117 * Attributes and sub elements with specific initialization parameters can be added. There are some reserved attributes
    118 * with a special meaning that can be used in every configuration declaration:
    119 * </p>
    120 * <table border="1">
    121 * <caption>Standard attributes for configuration declarations</caption>
    122 * <tr>
    123 * <th>Attribute</th>
    124 * <th>Meaning</th>
    125 * </tr>
    126 * <tr>
    127 * <td>{@code config-name}</td>
    128 * <td>Allows specifying a name for this configuration. This name can be used to obtain a reference to the configuration
    129 * from the resulting combined configuration (see below). It can also be passed to the {@link #getNamedBuilder(String)}
    130 * method.</td>
    131 * </tr>
    132 * <tr>
    133 * <td>{@code config-at}</td>
    134 * <td>With this attribute an optional prefix can be specified for the properties of the corresponding
    135 * configuration.</td>
    136 * </tr>
    137 * <tr>
    138 * <td>{@code config-optional}</td>
    139 * <td>Declares a configuration source as optional. This means that errors that occur when creating the configuration
    140 * are ignored.</td>
    141 * </tr>
    142 * <tr>
    143 * <td>{@code config-reload}</td>
    144 * <td>Many configuration sources support a reloading mechanism. For those sources it is possible to enable reloading by
    145 * providing this attribute with a value of <strong>true</strong>.</td>
    146 * </tr>
    147 * </table>
    148 * <p>
    149 * The optional <em>header</em> section can contain some meta data about the created configuration itself. For instance,
    150 * it is possible to set further properties of the {@code NodeCombiner} objects used for constructing the resulting
    151 * configuration.
    152 * </p>
    153 * <p>
    154 * The default configuration object returned by this builder is an instance of the {@link CombinedConfiguration} class.
    155 * This allows for convenient access to the configuration objects maintained by the combined configuration (for example for
    156 * updates of single configuration objects). It has also the advantage that the properties stored in all declared
    157 * configuration objects are collected and transformed into a single hierarchical structure, which can be accessed using
    158 * different expression engines. The actual {@code CombinedConfiguration} implementation can be overridden by specifying
    159 * the class in the <em>config-class</em> attribute of the result element.
    160 * </p>
    161 * <p>
    162 * A custom EntityResolver can be used for all XMLConfigurations by adding
    163 * </p>
    164 *
    165 * <pre>
    166 * &lt;entity-resolver config-class="EntityResolver fully qualified class name"&gt;
    167 * </pre>
    168 *
    169 * <p>
    170 * A specific CatalogResolver can be specified for all XMLConfiguration sources by adding
    171 * </p>
    172 *
    173 * <pre>
    174 * &lt;entity-resolver catalogFiles="comma separated list of catalog files"&gt;
    175 * </pre>
    176 *
    177 * <p>
    178 * Additional ConfigurationProviders can be added by configuring them in the <em>header</em> section.
    179 * </p>
    180 *
    181 * <pre>
    182 * &lt;providers&gt;
    183 *   &lt;provider config-tag="tag name" config-class="provider fully qualified class name"/&gt;
    184 * &lt;/providers&gt;
    185 * </pre>
    186 *
    187 * <p>
    188 * Additional variable resolvers can be added by configuring them in the <em>header</em> section.
    189 * </p>
    190 *
    191 * <pre>
    192 * &lt;lookups&gt;
    193 *   &lt;lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/&gt;
    194 * &lt;/lookups&gt;
    195 * </pre>
    196 *
    197 * <p>
    198 * All declared override configurations are directly added to the resulting combined configuration. If they are given
    199 * names (using the {@code config-name} attribute), they can directly be accessed using the
    200 * {@code getConfiguration(String)} method of {@code CombinedConfiguration}. The additional configurations are
    201 * altogether added to another combined configuration, which uses a union combiner. Then this union configuration is
    202 * added to the resulting combined configuration under the name defined by the {@code ADDITIONAL_NAME} constant. The
    203 * {@link #getNamedBuilder(String)} method can be used to access the {@code ConfigurationBuilder} objects for all
    204 * configuration sources which have been assigned a name; care has to be taken that these names are unique.
    205 * </p>
    206 *
    207 * @since 1.3
    208 */
    209public class CombinedConfigurationBuilder extends BasicConfigurationBuilder<CombinedConfiguration> {
    210
    211    /**
    212     * A data class for storing information about all configuration sources defined for a combined builder.
    213     */
    214    private final class ConfigurationSourceData {
    215
    216        /** A list with data for all builders for override configurations. */
    217        private final List<ConfigurationDeclaration> overrideDeclarations;
    218
    219        /** A list with data for all builders for union configurations. */
    220        private final List<ConfigurationDeclaration> unionDeclarations;
    221
    222        /** A list with the builders for override configurations. */
    223        private final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders;
    224
    225        /** A list with the builders for union configurations. */
    226        private final List<ConfigurationBuilder<? extends Configuration>> unionBuilders;
    227
    228        /** A map for direct access to a builder by its name. */
    229        private final Map<String, ConfigurationBuilder<? extends Configuration>> namedBuilders;
    230
    231        /** A collection with all child builders. */
    232        private final Collection<ConfigurationBuilder<? extends Configuration>> allBuilders;
    233
    234        /** A listener for reacting on changes of sub builders. */
    235        private final EventListener<ConfigurationBuilderEvent> changeListener;
    236
    237        /**
    238         * Creates a new instance of {@code ConfigurationSourceData}.
    239         */
    240        public ConfigurationSourceData() {
    241            overrideDeclarations = new ArrayList<>();
    242            unionDeclarations = new ArrayList<>();
    243            overrideBuilders = new ArrayList<>();
    244            unionBuilders = new ArrayList<>();
    245            namedBuilders = new HashMap<>();
    246            allBuilders = new LinkedList<>();
    247            changeListener = createBuilderChangeListener();
    248        }
    249
    250        /**
    251         * Creates a new configuration using the specified builder and adds it to the resulting combined configuration.
    252         *
    253         * @param ccResult the resulting combined configuration
    254         * @param decl the current {@code ConfigurationDeclaration}
    255         * @param builder the configuration builder
    256         * @throws ConfigurationException if an error occurs
    257         */
    258        private void addChildConfiguration(final CombinedConfiguration ccResult, final ConfigurationDeclaration decl,
    259            final ConfigurationBuilder<? extends Configuration> builder) throws ConfigurationException {
    260            try {
    261                ccResult.addConfiguration(builder.getConfiguration(), decl.getName(), decl.getAt());
    262            } catch (final ConfigurationException cex) {
    263                // ignore exceptions for optional configurations
    264                if (!decl.isOptional()) {
    265                    throw cex;
    266                }
    267            }
    268        }
    269
    270        /**
    271         * Returns a set with the names of all known named builders.
    272         *
    273         * @return the names of the available sub builders
    274         */
    275        public Set<String> builderNames() {
    276            return namedBuilders.keySet();
    277        }
    278
    279        /**
    280         * Frees resources used by this object and performs clean up. This method is called when the owning builder is reset.
    281         */
    282        public void cleanUp() {
    283            getChildBuilders().forEach(b -> b.removeEventListener(ConfigurationBuilderEvent.RESET, changeListener));
    284            namedBuilders.clear();
    285        }
    286
    287        /**
    288         * Processes the declaration of configuration builder providers, creates the corresponding builder if necessary, obtains
    289         * configurations, and adds them to the specified result configuration.
    290         *
    291         * @param ccResult the result configuration.
    292         * @param srcDecl the collection with the declarations of configuration sources to process.
    293         * @param builders List of configuration builders.
    294         * @return a list with configuration builders.
    295         * @throws ConfigurationException if an error occurs.
    296         */
    297        public List<ConfigurationBuilder<? extends Configuration>> createAndAddConfigurations(final CombinedConfiguration ccResult,
    298            final List<ConfigurationDeclaration> srcDecl, final List<ConfigurationBuilder<? extends Configuration>> builders) throws ConfigurationException {
    299            final boolean createBuilders = builders.isEmpty();
    300            final List<ConfigurationBuilder<? extends Configuration>> newBuilders;
    301            if (createBuilders) {
    302                newBuilders = new ArrayList<>(srcDecl.size());
    303            } else {
    304                newBuilders = builders;
    305            }
    306
    307            for (int i = 0; i < srcDecl.size(); i++) {
    308                final ConfigurationBuilder<? extends Configuration> b;
    309                if (createBuilders) {
    310                    b = createConfigurationBuilder(srcDecl.get(i));
    311                    newBuilders.add(b);
    312                } else {
    313                    b = builders.get(i);
    314                }
    315                addChildConfiguration(ccResult, srcDecl.get(i), b);
    316            }
    317
    318            return newBuilders;
    319        }
    320
    321        /**
    322         * Creates a listener for builder change events. This listener is registered at all builders for child configurations.
    323         */
    324        private EventListener<ConfigurationBuilderEvent> createBuilderChangeListener() {
    325            return event -> resetResult();
    326        }
    327
    328        /**
    329         * Creates a configuration builder based on a source declaration in the definition configuration.
    330         *
    331         * @param decl the current {@code ConfigurationDeclaration}
    332         * @return the newly created builder
    333         * @throws ConfigurationException if an error occurs
    334         */
    335        private ConfigurationBuilder<? extends Configuration> createConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException {
    336            final ConfigurationBuilderProvider provider = providerForTag(decl.getConfiguration().getRootElementName());
    337            if (provider == null) {
    338                throw new ConfigurationException("Unsupported configuration source: %s", decl.getConfiguration().getRootElementName());
    339            }
    340
    341            final ConfigurationBuilder<? extends Configuration> builder = provider.getConfigurationBuilder(decl);
    342            if (decl.getName() != null) {
    343                namedBuilders.put(decl.getName(), builder);
    344            }
    345            allBuilders.add(builder);
    346            builder.addEventListener(ConfigurationBuilderEvent.RESET, changeListener);
    347            return builder;
    348        }
    349
    350        /**
    351         * Finds the override configurations that are defined as top level elements in the configuration definition file. This
    352         * method fetches the child elements of the root node and removes the nodes that represent other configuration sections.
    353         * The remaining nodes are treated as definitions for override configurations.
    354         *
    355         * @param config the definition configuration
    356         * @return a list with sub configurations for the top level override configurations
    357         */
    358        private List<? extends HierarchicalConfiguration<?>> fetchTopLevelOverrideConfigs(final HierarchicalConfiguration<?> config) {
    359
    360            final List<? extends HierarchicalConfiguration<?>> configs = config.childConfigurationsAt(null);
    361            for (final Iterator<? extends HierarchicalConfiguration<?>> it = configs.iterator(); it.hasNext();) {
    362                final String nodeName = it.next().getRootElementName();
    363                for (final String element : CONFIG_SECTIONS) {
    364                    if (element.equals(nodeName)) {
    365                        it.remove();
    366                        break;
    367                    }
    368                }
    369            }
    370            return configs;
    371        }
    372
    373        /**
    374         * Gets a collection containing the builders for all child configuration sources.
    375         *
    376         * @return the child configuration builders
    377         */
    378        public Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
    379            return allBuilders;
    380        }
    381
    382        /**
    383         * Gets the {@code ConfigurationBuilder} with the given name. If no such builder is defined in the definition
    384         * configuration, result is <strong>null</strong>.
    385         *
    386         * @param name the name of the builder in question
    387         * @return the builder with this name or <strong>null</strong>
    388         */
    389        public ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) {
    390            return namedBuilders.get(name);
    391        }
    392
    393        /**
    394         * Gets a collection with all configuration source declarations defined in the override section.
    395         *
    396         * @return the override configuration builders
    397         */
    398        public List<ConfigurationDeclaration> getOverrideSources() {
    399            return overrideDeclarations;
    400        }
    401
    402        /**
    403         * Gets a collection with all configuration source declarations defined in the union section.
    404         *
    405         * @return the union configuration builders
    406         */
    407        public List<ConfigurationDeclaration> getUnionSources() {
    408            return unionDeclarations;
    409        }
    410
    411        /**
    412         * Initializes this object from the specified definition configuration.
    413         *
    414         * @param config the definition configuration
    415         * @throws ConfigurationException if an error occurs
    416         */
    417        public void initFromDefinitionConfiguration(final HierarchicalConfiguration<?> config) throws ConfigurationException {
    418            overrideDeclarations.addAll(createDeclarations(fetchTopLevelOverrideConfigs(config)));
    419            overrideDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_OVERRIDE)));
    420            unionDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_UNION)));
    421        }
    422    }
    423
    424    /**
    425     * Constant for the name of the additional configuration. If the configuration definition file contains an
    426     * {@code additional} section, a special union configuration is created and added under this name to the resulting
    427     * combined configuration.
    428     */
    429    public static final String ADDITIONAL_NAME = CombinedConfigurationBuilder.class.getName() + "/ADDITIONAL_CONFIG";
    430
    431    /** Constant for the name of the configuration bean factory. */
    432    static final String CONFIG_BEAN_FACTORY_NAME = CombinedConfigurationBuilder.class.getName() + ".CONFIG_BEAN_FACTORY_NAME";
    433
    434    /** Constant for the reserved name attribute. */
    435    static final String ATTR_NAME = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "name"
    436        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    437
    438    /** Constant for the name of the at attribute. */
    439    static final String ATTR_ATNAME = "at";
    440
    441    /** Constant for the reserved at attribute. */
    442    static final String ATTR_AT_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_ATNAME
    443        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    444
    445    /** Constant for the at attribute without the reserved prefix. */
    446    static final String ATTR_AT = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_ATNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    447
    448    /** Constant for the name of the optional attribute. */
    449    static final String ATTR_OPTIONALNAME = "optional";
    450
    451    /** Constant for the reserved optional attribute. */
    452    static final String ATTR_OPTIONAL_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_OPTIONALNAME
    453        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    454
    455    /** Constant for the optional attribute without the reserved prefix. */
    456    static final String ATTR_OPTIONAL = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_OPTIONALNAME
    457        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    458
    459    /** Constant for the forceCreate attribute. */
    460    static final String ATTR_FORCECREATE = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "forceCreate"
    461        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    462
    463    /** Constant for the reload attribute. */
    464    static final String ATTR_RELOAD = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "reload"
    465        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
    466
    467    /**
    468     * Constant for the tag attribute for providers.
    469     */
    470    static final String KEY_SYSTEM_PROPS = "[@systemProperties]";
    471
    472    /** Constant for the name of the header section. */
    473    static final String SEC_HEADER = "header";
    474
    475    /** Constant for an expression that selects the union configurations. */
    476    static final String KEY_UNION = "additional";
    477
    478    /** An array with the names of top level configuration sections. */
    479    static final String[] CONFIG_SECTIONS = {"additional", "override", SEC_HEADER};
    480
    481    /**
    482     * Constant for an expression that selects override configurations in the override section.
    483     */
    484    static final String KEY_OVERRIDE = "override";
    485
    486    /**
    487     * Constant for the key that points to the list nodes definition of the override combiner.
    488     */
    489    static final String KEY_OVERRIDE_LIST = SEC_HEADER + ".combiner.override.list-nodes.node";
    490
    491    /**
    492     * Constant for the key that points to the list nodes definition of the additional combiner.
    493     */
    494    static final String KEY_ADDITIONAL_LIST = SEC_HEADER + ".combiner.additional.list-nodes.node";
    495
    496    /**
    497     * Constant for the key for defining providers in the configuration file.
    498     */
    499    static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER + ".providers.provider";
    500
    501    /**
    502     * Constant for the tag attribute for providers.
    503     */
    504    static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]";
    505
    506    /**
    507     * Constant for the key for defining variable resolvers
    508     */
    509    static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER + ".lookups.lookup";
    510
    511    /**
    512     * Constant for the key for defining entity resolvers
    513     */
    514    static final String KEY_ENTITY_RESOLVER = SEC_HEADER + ".entity-resolver";
    515
    516    /**
    517     * Constant for the prefix attribute for lookups.
    518     */
    519    static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]";
    520
    521    /**
    522     * Constant for the FileSystem.
    523     */
    524    static final String FILE_SYSTEM = SEC_HEADER + ".fileSystem";
    525
    526    /**
    527     * Constant for the key of the result declaration. This key can point to a bean declaration, which defines properties of
    528     * the resulting combined configuration.
    529     */
    530    static final String KEY_RESULT = SEC_HEADER + ".result";
    531
    532    /** Constant for the key of the combiner in the result declaration. */
    533    static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
    534
    535    /** Constant for the XML file extension. */
    536    static final String EXT_XML = "xml";
    537
    538    /** Constant for the basic configuration builder class. */
    539    private static final String BASIC_BUILDER = "org.apache.commons.configuration2.builder.BasicConfigurationBuilder";
    540
    541    /** Constant for the file-based configuration builder class. */
    542    private static final String FILE_BUILDER = "org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder";
    543
    544    /** Constant for the reloading file-based configuration builder class. */
    545    private static final String RELOADING_BUILDER = "org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder";
    546
    547    /** Constant for the name of the file-based builder parameters class. */
    548    private static final String FILE_PARAMS = "org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl";
    549
    550    /** Constant for the provider for properties files. */
    551    private static final ConfigurationBuilderProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
    552        "org.apache.commons.configuration2.XMLPropertiesConfiguration", "org.apache.commons.configuration2.PropertiesConfiguration", EXT_XML,
    553        Collections.singletonList(FILE_PARAMS));
    554
    555    /** Constant for the provider for XML files. */
    556    private static final ConfigurationBuilderProvider XML_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
    557        "org.apache.commons.configuration2.XMLConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.XMLBuilderParametersImpl"));
    558
    559    /** Constant for the provider for JNDI sources. */
    560    private static final BaseConfigurationBuilderProvider JNDI_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
    561        "org.apache.commons.configuration2.JNDIConfiguration",
    562        Collections.singletonList("org.apache.commons.configuration2.builder.JndiBuilderParametersImpl"));
    563
    564    /** Constant for the provider for system properties. */
    565    private static final BaseConfigurationBuilderProvider SYSTEM_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
    566        "org.apache.commons.configuration2.SystemConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
    567
    568    /** Constant for the provider for ini files. */
    569    private static final BaseConfigurationBuilderProvider INI_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
    570        "org.apache.commons.configuration2.INIConfiguration", Collections.singletonList(FILE_PARAMS));
    571
    572    /** Constant for the provider for environment properties. */
    573    private static final BaseConfigurationBuilderProvider ENV_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
    574        "org.apache.commons.configuration2.EnvironmentConfiguration",
    575        Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
    576
    577    /** Constant for the provider for plist files. */
    578    private static final BaseConfigurationBuilderProvider PLIST_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
    579        "org.apache.commons.configuration2.plist.XMLPropertyListConfiguration", "org.apache.commons.configuration2.plist.PropertyListConfiguration", EXT_XML,
    580        Collections.singletonList(FILE_PARAMS));
    581
    582    /** Constant for the provider for configuration definition files. */
    583    private static final BaseConfigurationBuilderProvider COMBINED_PROVIDER = new CombinedConfigurationBuilderProvider();
    584
    585    /** Constant for the provider for multiple XML configurations. */
    586    private static final MultiFileConfigurationBuilderProvider MULTI_XML_PROVIDER = new MultiFileConfigurationBuilderProvider(
    587        "org.apache.commons.configuration2.XMLConfiguration", "org.apache.commons.configuration2.builder.XMLBuilderParametersImpl");
    588
    589    /** An array with the names of the default tags. */
    590    private static final String[] DEFAULT_TAGS = {"properties", "xml", "hierarchicalXml", "plist", "ini", "system", "env", "jndi", "configuration",
    591        "multiFile"};
    592
    593    /** An array with the providers for the default tags. */
    594    private static final ConfigurationBuilderProvider[] DEFAULT_PROVIDERS = {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, PLIST_PROVIDER, INI_PROVIDER,
    595        SYSTEM_PROVIDER, ENV_PROVIDER, JNDI_PROVIDER, COMBINED_PROVIDER, MULTI_XML_PROVIDER};
    596
    597    /** A map with the default configuration builder providers. */
    598    private static final Map<String, ConfigurationBuilderProvider> DEFAULT_PROVIDERS_MAP;
    599
    600    static {
    601        DEFAULT_PROVIDERS_MAP = createDefaultProviders();
    602    }
    603
    604    /**
    605     * Creates the map with the default configuration builder providers.
    606     *
    607     * @return the map with default providers
    608     */
    609    private static Map<String, ConfigurationBuilderProvider> createDefaultProviders() {
    610        final Map<String, ConfigurationBuilderProvider> providers = new HashMap<>();
    611        for (int i = 0; i < DEFAULT_TAGS.length; i++) {
    612            providers.put(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
    613        }
    614        return providers;
    615    }
    616
    617    /**
    618     * Initializes the list nodes of the node combiner for the given combined configuration. This information can be set in
    619     * the header section of the configuration definition file for both the override and the union combiners.
    620     *
    621     * @param cc the combined configuration to initialize
    622     * @param defConfig the definition configuration
    623     * @param key the key for the list nodes
    624     */
    625    private static void initNodeCombinerListNodes(final CombinedConfiguration cc, final HierarchicalConfiguration<?> defConfig, final String key) {
    626        defConfig.getList(key).forEach(listNode -> cc.getNodeCombiner().addListNode((String) listNode));
    627    }
    628
    629    /** The builder for the definition configuration. */
    630    private ConfigurationBuilder<? extends HierarchicalConfiguration<?>> definitionBuilder;
    631
    632    /** Stores temporarily the configuration with the builder definitions. */
    633    private HierarchicalConfiguration<?> definitionConfiguration;
    634
    635    /** The object with data about configuration sources. */
    636    private ConfigurationSourceData sourceData;
    637
    638    /** Stores the current parameters object. */
    639    private CombinedBuilderParametersImpl currentParameters;
    640
    641    /** The current XML parameters object. */
    642    private XMLBuilderParametersImpl currentXMLParameters;
    643
    644    /** The configuration that is currently constructed. */
    645    private CombinedConfiguration currentConfiguration;
    646
    647    /**
    648     * A {@code ConfigurationInterpolator} to be used as parent for all child configurations to enable cross-source
    649     * interpolation.
    650     */
    651    private ConfigurationInterpolator parentInterpolator;
    652
    653    /**
    654     * Creates a new instance of {@code CombinedConfigurationBuilder}. No parameters are set.
    655     */
    656    public CombinedConfigurationBuilder() {
    657        super(CombinedConfiguration.class);
    658    }
    659
    660    /**
    661     *
    662     * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters.
    663     *
    664     * @param params a map with initialization parameters
    665     */
    666    public CombinedConfigurationBuilder(final Map<String, Object> params) {
    667        super(CombinedConfiguration.class, params);
    668    }
    669
    670    /**
    671     *
    672     * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters and
    673     * the <em>allowFailOnInit</em> flag.
    674     *
    675     * @param params a map with initialization parameters
    676     * @param allowFailOnInit the <em>allowFailOnInit</em> flag
    677     */
    678    public CombinedConfigurationBuilder(final Map<String, Object> params, final boolean allowFailOnInit) {
    679        super(CombinedConfiguration.class, params, allowFailOnInit);
    680    }
    681
    682    /**
    683     * Adds a listener at the given definition builder which resets this builder when a reset of the definition builder
    684     * happens. This way it is ensured that this builder produces a new combined configuration when its definition
    685     * configuration changes.
    686     *
    687     * @param defBuilder the definition builder
    688     */
    689    private void addDefinitionBuilderChangeListener(final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder) {
    690        defBuilder.addEventListener(ConfigurationBuilderEvent.RESET, event -> {
    691            synchronized (this) {
    692                reset();
    693                definitionBuilder = defBuilder;
    694            }
    695        });
    696    }
    697
    698    /**
    699     * <p>
    700     * Returns a set with the names of all child configuration builders. A tag defining a configuration source in the
    701     * configuration definition file can have the {@code config-name} attribute. If this attribute is present, the
    702     * corresponding builder is assigned this name and can be directly accessed through the {@link #getNamedBuilder(String)}
    703     * method. This method returns a collection with all available builder names.
    704     * </p>
    705     * <p>
    706     * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
    707     * created by calling {@code getConfiguration()}. If called before, always an empty set is returned.
    708     * </p>
    709     *
    710     * @return a set with the names of all builders
    711     */
    712    public synchronized Set<String> builderNames() {
    713        if (sourceData == null) {
    714            return Collections.emptySet();
    715        }
    716        return Collections.unmodifiableSet(sourceData.builderNames());
    717    }
    718
    719    /**
    720     * {@inheritDoc} This method is overridden to adapt the return type.
    721     */
    722    @Override
    723    public CombinedConfigurationBuilder configure(final BuilderParameters... params) {
    724        super.configure(params);
    725        return this;
    726    }
    727
    728    /**
    729     * Creates and initializes a default {@code EntityResolver} if the definition configuration contains a corresponding
    730     * declaration.
    731     *
    732     * @param config the definition configuration
    733     * @param xmlParams the (already partly initialized) object with XML parameters; here the new resolver is to be stored
    734     * @throws ConfigurationException if an error occurs
    735     */
    736    protected void configureEntityResolver(final HierarchicalConfiguration<?> config, final XMLBuilderParametersImpl xmlParams) throws ConfigurationException {
    737        if (config.getMaxIndex(KEY_ENTITY_RESOLVER) == 0) {
    738            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY_ENTITY_RESOLVER, true);
    739            final EntityResolver resolver = (EntityResolver) fetchBeanHelper().createBean(decl, CatalogResolver.class);
    740            final FileSystem fileSystem = xmlParams.getFileHandler().getFileSystem();
    741            if (fileSystem != null) {
    742                BeanHelper.setProperty(resolver, "fileSystem", fileSystem);
    743            }
    744            final String basePath = xmlParams.getFileHandler().getBasePath();
    745            if (basePath != null) {
    746                BeanHelper.setProperty(resolver, "baseDir", basePath);
    747            }
    748            final ConfigurationInterpolator ci = new ConfigurationInterpolator();
    749            ci.registerLookups(fetchPrefixLookups());
    750            BeanHelper.setProperty(resolver, "interpolator", ci);
    751
    752            xmlParams.setEntityResolver(resolver);
    753        }
    754    }
    755
    756    /**
    757     * Creates the {@code CombinedConfiguration} for the configuration sources in the {@code &lt;additional&gt;} section.
    758     * This method is called when the builder constructs the final configuration. It creates a new
    759     * {@code CombinedConfiguration} and initializes some properties from the result configuration.
    760     *
    761     * @param resultConfig the result configuration (this is the configuration that will be returned by the builder)
    762     * @return the {@code CombinedConfiguration} for the additional configuration sources
    763     * @since 1.7
    764     */
    765    protected CombinedConfiguration createAdditionalsConfiguration(final CombinedConfiguration resultConfig) {
    766        final CombinedConfiguration addConfig = new CombinedConfiguration(new UnionCombiner());
    767        addConfig.setListDelimiterHandler(resultConfig.getListDelimiterHandler());
    768        return addConfig;
    769    }
    770
    771    /**
    772     * Creates {@code ConfigurationDeclaration} objects for the specified configurations.
    773     *
    774     * @param configs the list with configurations
    775     * @return a collection with corresponding declarations
    776     */
    777    private Collection<ConfigurationDeclaration> createDeclarations(final Collection<? extends HierarchicalConfiguration<?>> configs) {
    778        return configs.stream().map(c -> new ConfigurationDeclaration(this, c)).collect(Collectors.toList());
    779    }
    780
    781    /**
    782     * {@inheritDoc} This implementation evaluates the {@code result} property of the definition configuration. It creates a
    783     * combined bean declaration with both the properties specified in the definition file and the properties defined as
    784     * initialization parameters.
    785     */
    786    @Override
    787    protected BeanDeclaration createResultDeclaration(final Map<String, Object> params) throws ConfigurationException {
    788        final BeanDeclaration paramsDecl = super.createResultDeclaration(params);
    789        final XMLBeanDeclaration resultDecl = new XMLBeanDeclaration(getDefinitionConfiguration(), KEY_RESULT, true, CombinedConfiguration.class.getName());
    790        return new CombinedBeanDeclaration(resultDecl, paramsDecl);
    791    }
    792
    793    /**
    794     * Creates the data object for configuration sources and the corresponding builders.
    795     *
    796     * @return the newly created data object
    797     * @throws ConfigurationException if an error occurs
    798     */
    799    private ConfigurationSourceData createSourceData() throws ConfigurationException {
    800        final ConfigurationSourceData result = new ConfigurationSourceData();
    801        result.initFromDefinitionConfiguration(getDefinitionConfiguration());
    802        return result;
    803    }
    804
    805    /**
    806     * Creates a default builder for the definition configuration and initializes it with a parameters object. This method
    807     * is called if no definition builder is defined in this builder's parameters. This implementation creates a default
    808     * file-based builder which produces an {@code XMLConfiguration}; it expects a corresponding file specification. Note:
    809     * This method is called in a synchronized block.
    810     *
    811     * @param builderParams the parameters object for the builder
    812     * @return the standard builder for the definition configuration
    813     */
    814    protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> createXMLDefinitionBuilder(final BuilderParameters builderParams) {
    815        return new FileBasedConfigurationBuilder<>(XMLConfiguration.class).configure(builderParams);
    816    }
    817
    818    /**
    819     * Returns a map with the current prefix lookup objects. This map is obtained from the {@code ConfigurationInterpolator}
    820     * of the configuration under construction.
    821     *
    822     * @return the map with current prefix lookups (may be <strong>null</strong>)
    823     */
    824    private Map<String, ? extends Lookup> fetchPrefixLookups() {
    825        final CombinedConfiguration cc = getConfigurationUnderConstruction();
    826        return cc != null ? cc.getInterpolator().getLookups() : null;
    827    }
    828
    829    /**
    830     * Gets the current base path of this configuration builder. This is used for instance by all file-based child
    831     * configurations.
    832     *
    833     * @return the base path
    834     */
    835    private String getBasePath() {
    836        return currentXMLParameters.getFileHandler().getBasePath();
    837    }
    838
    839    /**
    840     * Gets a collection with the builders for all child configuration sources. This method can be used by derived
    841     * classes providing additional functionality on top of the declared configuration sources. It only returns a defined
    842     * value during construction of the result configuration instance.
    843     *
    844     * @return a collection with the builders for child configuration sources
    845     */
    846    protected synchronized Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
    847        return sourceData.getChildBuilders();
    848    }
    849
    850    /**
    851     * Gets the configuration object that is currently constructed. This method can be called during construction of the
    852     * result configuration. It is intended for internal usage, for example some specialized builder providers need access to this
    853     * configuration to perform advanced initialization.
    854     *
    855     * @return the configuration that us currently under construction
    856     */
    857    CombinedConfiguration getConfigurationUnderConstruction() {
    858        return currentConfiguration;
    859    }
    860
    861    /**
    862     * Gets the {@code ConfigurationBuilder} which creates the definition configuration.
    863     *
    864     * @return the builder for the definition configuration
    865     * @throws ConfigurationException if an error occurs
    866     */
    867    public synchronized ConfigurationBuilder<? extends HierarchicalConfiguration<?>> getDefinitionBuilder() throws ConfigurationException {
    868        if (definitionBuilder == null) {
    869            definitionBuilder = setupDefinitionBuilder(getParameters());
    870            addDefinitionBuilderChangeListener(definitionBuilder);
    871        }
    872        return definitionBuilder;
    873    }
    874
    875    /**
    876     * Gets the configuration containing the definition of the combined configuration to be created. This method only
    877     * returns a defined result during construction of the result configuration. The definition configuration is obtained
    878     * from the definition builder at first access and then stored temporarily to ensure that during result construction
    879     * always the same configuration instance is used. (Otherwise, it would be possible that the definition builder returns
    880     * a different instance when queried multiple times.)
    881     *
    882     * @return the definition configuration
    883     * @throws ConfigurationException if an error occurs
    884     */
    885    protected HierarchicalConfiguration<?> getDefinitionConfiguration() throws ConfigurationException {
    886        if (definitionConfiguration == null) {
    887            definitionConfiguration = getDefinitionBuilder().getConfiguration();
    888        }
    889        return definitionConfiguration;
    890    }
    891
    892    /**
    893     * <p>
    894     * Gets the configuration builder with the given name. With this method a builder of a child configuration which was
    895     * given a name in the configuration definition file can be accessed directly.
    896     * </p>
    897     * <p>
    898     * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
    899     * created by calling {@code getConfiguration()}. If called before, always an exception is thrown.
    900     * </p>
    901     *
    902     * @param name the name of the builder in question
    903     * @return the child configuration builder with this name
    904     * @throws ConfigurationException if information about named builders is not yet available or no builder with this name
    905     *         exists
    906     */
    907    public synchronized ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) throws ConfigurationException {
    908        if (sourceData == null) {
    909            throw new ConfigurationException("Information about child builders has not been setup yet! Call getConfiguration() first.");
    910        }
    911        final ConfigurationBuilder<? extends Configuration> builder = sourceData.getNamedBuilder(name);
    912        if (builder == null) {
    913            throw new ConfigurationException("Builder cannot be resolved: %s", name);
    914        }
    915        return builder;
    916    }
    917
    918    /**
    919     * Obtains the data object for the configuration sources and the corresponding builders. This object is created on first
    920     * access and reset when the definition builder sends a change event. This method is called in a synchronized block.
    921     *
    922     * @return the object with information about configuration sources
    923     * @throws ConfigurationException if an error occurs
    924     */
    925    private ConfigurationSourceData getSourceData() throws ConfigurationException {
    926        if (sourceData == null) {
    927            if (currentParameters == null) {
    928                setUpCurrentParameters();
    929                setUpCurrentXMLParameters();
    930            }
    931            sourceData = createSourceData();
    932        }
    933        return sourceData;
    934    }
    935
    936    /**
    937     * Initializes a bean using the current {@code BeanHelper}. This is needed by builder providers when the configuration
    938     * objects for sub builders are constructed.
    939     *
    940     * @param bean the bean to be initialized
    941     * @param decl the {@code BeanDeclaration}
    942     */
    943    void initBean(final Object bean, final BeanDeclaration decl) {
    944        fetchBeanHelper().initBean(bean, decl);
    945    }
    946
    947    /**
    948     * Initializes basic builder parameters for a child configuration with default settings set for this builder. This
    949     * implementation ensures that all {@code Lookup} objects are propagated to child configurations and interpolation is
    950     * setup correctly.
    951     *
    952     * @param params the parameters object
    953     */
    954    private void initChildBasicParameters(final BasicBuilderParameters params) {
    955        params.setPrefixLookups(fetchPrefixLookups());
    956        params.setParentInterpolator(parentInterpolator);
    957        if (currentParameters.isInheritSettings()) {
    958            params.inheritFrom(getParameters());
    959        }
    960    }
    961
    962    /**
    963     * Initializes a parameters object for a child builder. This combined configuration builder has a bunch of properties
    964     * which may be inherited by child configurations, for example the base path, the file system, etc. While processing the
    965     * builders for child configurations, this method is called for each parameters object for a child builder. It
    966     * initializes some properties of the passed in parameters objects which are derived from this parent builder.
    967     *
    968     * @param params the parameters object to be initialized
    969     */
    970    protected void initChildBuilderParameters(final BuilderParameters params) {
    971        initDefaultChildParameters(params);
    972
    973        if (params instanceof BasicBuilderParameters) {
    974            initChildBasicParameters((BasicBuilderParameters) params);
    975        }
    976        if (params instanceof XMLBuilderProperties<?>) {
    977            initChildXMLParameters((XMLBuilderProperties<?>) params);
    978        }
    979        if (params instanceof FileBasedBuilderProperties<?>) {
    980            initChildFileBasedParameters((FileBasedBuilderProperties<?>) params);
    981        }
    982        if (params instanceof CombinedBuilderParametersImpl) {
    983            initChildCombinedParameters((CombinedBuilderParametersImpl) params);
    984        }
    985    }
    986
    987    /**
    988     * Initializes a parameters object for a combined configuration builder with properties already set for this parent
    989     * builder. This implementation deals only with a subset of properties. Other properties are already handled by the
    990     * specialized builder provider.
    991     *
    992     * @param params the parameters object
    993     */
    994    private void initChildCombinedParameters(final CombinedBuilderParametersImpl params) {
    995        params.registerMissingProviders(currentParameters);
    996        params.setBasePath(getBasePath());
    997    }
    998
    999    /**
    1000     * Initializes the event listeners of the specified builder from this object. This method is used to inherit all
    1001     * listeners from a parent builder.
    1002     *
    1003     * @param dest the destination builder object which is to be initialized
    1004     */
    1005    void initChildEventListeners(final BasicConfigurationBuilder<? extends Configuration> dest) {
    1006        copyEventListeners(dest);
    1007    }
    1008
    1009    /**
    1010     * Initializes a parameters object for a file-based configuration with properties already set for this parent builder.
    1011     * This method handles properties like a default file system or a base path.
    1012     *
    1013     * @param params the parameters object
    1014     */
    1015    private void initChildFileBasedParameters(final FileBasedBuilderProperties<?> params) {
    1016        params.setBasePath(getBasePath());
    1017        params.setFileSystem(currentXMLParameters.getFileHandler().getFileSystem());
    1018    }
    1019
    1020    /**
    1021     * Initializes a parameters object for an XML configuration with properties already set for this parent builder.
    1022     *
    1023     * @param params the parameters object
    1024     */
    1025    private void initChildXMLParameters(final XMLBuilderProperties<?> params) {
    1026        params.setEntityResolver(currentXMLParameters.getEntityResolver());
    1027    }
    1028
    1029    /**
    1030     * Initializes the default base path for all file-based child configuration sources. The base path can be explicitly
    1031     * defined in the parameters of this builder. Otherwise, if the definition builder is a file-based builder, it is
    1032     * obtained from there.
    1033     *
    1034     * @throws ConfigurationException if an error occurs
    1035     */
    1036    private void initDefaultBasePath() throws ConfigurationException {
    1037        assert currentParameters != null : "Current parameters undefined!";
    1038        if (currentParameters.getBasePath() != null) {
    1039            currentXMLParameters.setBasePath(currentParameters.getBasePath());
    1040        } else {
    1041            final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = getDefinitionBuilder();
    1042            if (defBuilder instanceof FileBasedConfigurationBuilder) {
    1043                @SuppressWarnings("rawtypes")
    1044                final FileBasedConfigurationBuilder fileBuilder = (FileBasedConfigurationBuilder) defBuilder;
    1045                final URL url = fileBuilder.getFileHandler().getURL();
    1046                currentXMLParameters.setBasePath(url != null ? url.toExternalForm() : fileBuilder.getFileHandler().getBasePath());
    1047            }
    1048        }
    1049    }
    1050
    1051    /**
    1052     * Executes the {@link org.apache.commons.configuration2.builder.DefaultParametersManager DefaultParametersManager}
    1053     * stored in the current parameters on the passed in parameters object. If default handlers have been registered for
    1054     * this type of parameters, an initialization is now performed. This method is called before the parameters object is
    1055     * initialized from the configuration definition file. So default values can be overridden later with concrete property
    1056     * definitions.
    1057     *
    1058     * @param params the parameters to be initialized
    1059     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when copying properties
    1060     */
    1061    private void initDefaultChildParameters(final BuilderParameters params) {
    1062        currentParameters.getChildDefaultParametersManager().initializeParameters(params);
    1063    }
    1064
    1065    /**
    1066     * Creates and initializes a default {@code FileSystem} if the definition configuration contains a corresponding
    1067     * declaration. The file system returned by this method is used as default for all file-based child configuration
    1068     * sources.
    1069     *
    1070     * @param config the definition configuration
    1071     * @return the default {@code FileSystem} (may be <strong>null</strong>)
    1072     * @throws ConfigurationException if an error occurs
    1073     */
    1074    protected FileSystem initFileSystem(final HierarchicalConfiguration<?> config) throws ConfigurationException {
    1075        if (config.getMaxIndex(FILE_SYSTEM) == 0) {
    1076            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, FILE_SYSTEM);
    1077            return (FileSystem) fetchBeanHelper().createBean(decl);
    1078        }
    1079        return null;
    1080    }
    1081
    1082    /**
    1083     * {@inheritDoc} This implementation processes the definition configuration in order to
    1084     * <ul>
    1085     * <li>initialize the resulting {@code CombinedConfiguration}</li>
    1086     * <li>determine the builders for all configuration sources</li>
    1087     * <li>populate the resulting {@code CombinedConfiguration}</li>
    1088     * </ul>
    1089     */
    1090    @Override
    1091    protected void initResultInstance(final CombinedConfiguration result) throws ConfigurationException {
    1092        super.initResultInstance(result);
    1093
    1094        currentConfiguration = result;
    1095        final HierarchicalConfiguration<?> config = getDefinitionConfiguration();
    1096        if (config.getMaxIndex(KEY_COMBINER) < 0) {
    1097            // No combiner defined => set default
    1098            result.setNodeCombiner(new OverrideCombiner());
    1099        }
    1100
    1101        setUpCurrentParameters();
    1102        initNodeCombinerListNodes(result, config, KEY_OVERRIDE_LIST);
    1103        registerConfiguredProviders(config);
    1104        setUpCurrentXMLParameters();
    1105        currentXMLParameters.setFileSystem(initFileSystem(config));
    1106        initSystemProperties(config, getBasePath());
    1107        registerConfiguredLookups(config, result);
    1108        configureEntityResolver(config, currentXMLParameters);
    1109        setUpParentInterpolator(currentConfiguration, config);
    1110
    1111        final ConfigurationSourceData data = getSourceData();
    1112        final boolean createBuilders = data.getChildBuilders().isEmpty();
    1113        final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders = data.createAndAddConfigurations(result, data.getOverrideSources(),
    1114            data.overrideBuilders);
    1115        if (createBuilders) {
    1116            data.overrideBuilders.addAll(overrideBuilders);
    1117        }
    1118        if (!data.getUnionSources().isEmpty()) {
    1119            final CombinedConfiguration addConfig = createAdditionalsConfiguration(result);
    1120            result.addConfiguration(addConfig, ADDITIONAL_NAME);
    1121            initNodeCombinerListNodes(addConfig, config, KEY_ADDITIONAL_LIST);
    1122            final List<ConfigurationBuilder<? extends Configuration>> unionBuilders = data.createAndAddConfigurations(addConfig, data.unionDeclarations,
    1123                data.unionBuilders);
    1124            if (createBuilders) {
    1125                data.unionBuilders.addAll(unionBuilders);
    1126            }
    1127        }
    1128
    1129        result.isEmpty(); // this sets up the node structure
    1130        currentConfiguration = null;
    1131    }
    1132
    1133    /**
    1134     * Handles a file with system properties that may be defined in the definition configuration. If such property file is
    1135     * configured, all of its properties are added to the system properties.
    1136     *
    1137     * @param config the definition configuration
    1138     * @param basePath the base path defined for this builder (may be <strong>null</strong>)
    1139     * @throws ConfigurationException if an error occurs.
    1140     */
    1141    protected void initSystemProperties(final HierarchicalConfiguration<?> config, final String basePath) throws ConfigurationException {
    1142        final String fileName = config.getString(KEY_SYSTEM_PROPS);
    1143        if (fileName != null) {
    1144            try {
    1145                SystemConfiguration.setSystemProperties(basePath, fileName);
    1146            } catch (final Exception e) {
    1147                throw new ConfigurationException(e, "Error setting system properties from %s (basePath = %s)", fileName, basePath);
    1148            }
    1149        }
    1150    }
    1151
    1152    /**
    1153     * Returns the {@code ConfigurationBuilderProvider} for the given tag. This method is called during creation of the
    1154     * result configuration. (It is not allowed to call it at another point of time; result is then unpredictable!) It
    1155     * supports all default providers and custom providers added through the parameters object as well.
    1156     *
    1157     * @param tagName the name of the tag
    1158     * @return the provider that was registered for this tag or <strong>null</strong> if there is none
    1159     */
    1160    protected ConfigurationBuilderProvider providerForTag(final String tagName) {
    1161        return currentParameters.providerForTag(tagName);
    1162    }
    1163
    1164    /**
    1165     * Processes custom {@link Lookup} objects that might be declared in the definition configuration. Each {@code Lookup}
    1166     * object is registered at the definition configuration and at the result configuration. It is also added to all child
    1167     * configurations added to the resulting combined configuration.
    1168     *
    1169     * @param defConfig the definition configuration
    1170     * @param resultConfig the resulting configuration
    1171     * @throws ConfigurationException if an error occurs
    1172     */
    1173    protected void registerConfiguredLookups(final HierarchicalConfiguration<?> defConfig, final Configuration resultConfig) throws ConfigurationException {
    1174        final Map<String, Lookup> lookups = defConfig.configurationsAt(KEY_CONFIGURATION_LOOKUPS).stream().collect(
    1175                Collectors.toMap(config -> config.getString(KEY_LOOKUP_KEY), config -> (Lookup) fetchBeanHelper().createBean(new XMLBeanDeclaration(config))));
    1176
    1177        if (!lookups.isEmpty()) {
    1178            final ConfigurationInterpolator defCI = defConfig.getInterpolator();
    1179            if (defCI != null) {
    1180                defCI.registerLookups(lookups);
    1181            }
    1182            resultConfig.getInterpolator().registerLookups(lookups);
    1183        }
    1184    }
    1185
    1186    /**
    1187     * Registers providers defined in the configuration.
    1188     *
    1189     * @param defConfig the definition configuration
    1190     */
    1191    private void registerConfiguredProviders(final HierarchicalConfiguration<?> defConfig) {
    1192        defConfig.configurationsAt(KEY_CONFIGURATION_PROVIDERS).forEach(config -> {
    1193            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
    1194            final String key = config.getString(KEY_PROVIDER_KEY);
    1195            currentParameters.registerProvider(key, (ConfigurationBuilderProvider) fetchBeanHelper().createBean(decl));
    1196        });
    1197    }
    1198
    1199    /**
    1200     * {@inheritDoc} This implementation resets some specific internal state of this builder.
    1201     */
    1202    @Override
    1203    public synchronized void resetParameters() {
    1204        super.resetParameters();
    1205        definitionBuilder = null;
    1206        definitionConfiguration = null;
    1207        currentParameters = null;
    1208        currentXMLParameters = null;
    1209
    1210        if (sourceData != null) {
    1211            sourceData.cleanUp();
    1212            sourceData = null;
    1213        }
    1214    }
    1215
    1216    /**
    1217     * Initializes the current parameters object. This object has either been passed at builder configuration time or it is
    1218     * newly created. In any case, it is manipulated during result creation.
    1219     */
    1220    private void setUpCurrentParameters() {
    1221        currentParameters = CombinedBuilderParametersImpl.fromParameters(getParameters(), true);
    1222        currentParameters.registerMissingProviders(DEFAULT_PROVIDERS_MAP);
    1223    }
    1224
    1225    /**
    1226     * Sets up an XML parameters object which is used to store properties related to XML and file-based configurations
    1227     * during creation of the result configuration. The properties stored in this object can be inherited to child
    1228     * configurations.
    1229     *
    1230     * @throws ConfigurationException if an error occurs
    1231     */
    1232    private void setUpCurrentXMLParameters() throws ConfigurationException {
    1233        currentXMLParameters = new XMLBuilderParametersImpl();
    1234        initDefaultBasePath();
    1235    }
    1236
    1237    /**
    1238     * Obtains the {@code ConfigurationBuilder} object which provides access to the configuration containing the definition
    1239     * of the combined configuration to create. If a definition builder is defined in the parameters, it is used. Otherwise,
    1240     * we check whether the combined builder parameters object contains a parameters object for the definition builder. If
    1241     * this is the case, a builder for an {@code XMLConfiguration} is created and configured with this object. As a last
    1242     * resort, it is looked for a {@link FileBasedBuilderParametersImpl} object in the properties. If found, also a XML
    1243     * configuration builder is created which loads this file. Note: This method is called from a synchronized block.
    1244     *
    1245     * @param params the current parameters for this builder
    1246     * @return the builder for the definition configuration
    1247     * @throws ConfigurationException if an error occurs
    1248     */
    1249    protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> setupDefinitionBuilder(final Map<String, Object> params)
    1250        throws ConfigurationException {
    1251        final CombinedBuilderParametersImpl cbParams = CombinedBuilderParametersImpl.fromParameters(params);
    1252        if (cbParams != null) {
    1253            final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = cbParams.getDefinitionBuilder();
    1254            if (defBuilder != null) {
    1255                return defBuilder;
    1256            }
    1257
    1258            if (cbParams.getDefinitionBuilderParameters() != null) {
    1259                return createXMLDefinitionBuilder(cbParams.getDefinitionBuilderParameters());
    1260            }
    1261        }
    1262
    1263        final BuilderParameters fileParams = FileBasedBuilderParametersImpl.fromParameters(params);
    1264        if (fileParams != null) {
    1265            return createXMLDefinitionBuilder(fileParams);
    1266        }
    1267
    1268        throw new ConfigurationException("No builder for configuration definition specified.");
    1269    }
    1270
    1271    /**
    1272     * Sets up a parent {@code ConfigurationInterpolator} object. This object has a default {@link Lookup} querying the
    1273     * resulting combined configuration. Thus interpolation works globally across all configuration sources.
    1274     *
    1275     * @param resultConfig the result configuration
    1276     * @param defConfig the definition configuration
    1277     */
    1278    private void setUpParentInterpolator(final Configuration resultConfig, final Configuration defConfig) {
    1279        parentInterpolator = new ConfigurationInterpolator();
    1280        parentInterpolator.addDefaultLookup(new ConfigurationLookup(resultConfig));
    1281        final ConfigurationInterpolator defInterpolator = defConfig.getInterpolator();
    1282        if (defInterpolator != null) {
    1283            defInterpolator.setParentInterpolator(parentInterpolator);
    1284        }
    1285    }
    1286}