All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration2.builder.combined.CombinedConfigurationBuilder Maven / Gradle / Ivy

Go to download

Tools to assist in the reading of configuration/preferences files in various formats

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

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.configuration2.CombinedConfiguration;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.ConfigurationLookup;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.SystemConfiguration;
import org.apache.commons.configuration2.XMLConfiguration;
import org.apache.commons.configuration2.beanutils.BeanDeclaration;
import org.apache.commons.configuration2.beanutils.BeanHelper;
import org.apache.commons.configuration2.beanutils.CombinedBeanDeclaration;
import org.apache.commons.configuration2.beanutils.XMLBeanDeclaration;
import org.apache.commons.configuration2.builder.BasicBuilderParameters;
import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
import org.apache.commons.configuration2.builder.BuilderParameters;
import org.apache.commons.configuration2.builder.ConfigurationBuilder;
import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent;
import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
import org.apache.commons.configuration2.builder.XMLBuilderProperties;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.interpol.Lookup;
import org.apache.commons.configuration2.io.FileSystem;
import org.apache.commons.configuration2.resolver.CatalogResolver;
import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
import org.apache.commons.configuration2.tree.OverrideCombiner;
import org.apache.commons.configuration2.tree.UnionCombiner;
import org.xml.sax.EntityResolver;

/**
 * 

* A specialized {@code ConfigurationBuilder} implementation that creates a * {@link CombinedConfiguration} from multiple configuration sources defined by * an XML-based configuration definition file. *

*

* This class provides an easy and flexible means for loading multiple * configuration sources and combining the results into a single configuration * object. The sources to be loaded are defined in an XML document that can * contain certain tags representing the different supported configuration * classes. If such a tag is found, a corresponding {@code ConfigurationBuilder} * class is instantiated and initialized using the classes of the * {@code beanutils} package (namely * {@link org.apache.commons.configuration2.beanutils.XMLBeanDeclaration * XMLBeanDeclaration} will be used to extract the configuration's * initialization parameters, which allows for complex initialization * scenarios). *

*

* It is also possible to add custom tags to the configuration definition file. * For this purpose an implementation of * {@link CombinedConfigurationBuilderProvider} has to be created which is * responsible for the creation of a {@code ConfigurationBuilder} associated * with the custom tag. An instance of this class has to be registered at the * {@link CombinedBuilderParametersImpl} object which is used to initialize this * {@code CombinedConfigurationBuilder}. This provider will then be called when * the corresponding custom tag is detected. For many default configuration * classes providers are already registered. *

*

* The configuration definition file has the following basic structure: *

* *
 * <configuration systemProperties="properties file name">
 *   <header>
 *     <!-- Optional meta information about the combined configuration -->
 *   </header>
 *   <override>
 *     <!-- Declarations for override configurations -->
 *   </override>
 *   <additional>
 *     <!-- Declarations for union configurations -->
 *   </additional>
 * </configuration>
 * 
* *

* The name of the root element (here {@code configuration}) is arbitrary. The * optional {@code systemProperties} attribute identifies the path to a property * file containing properties that should be added to the system properties. If * specified on the root element, the system properties are set before the rest * of the configuration is processed. *

*

* There are two sections (both of them are optional) for declaring * override and additional configurations. Configurations in * the former section are evaluated in the order of their declaration, and * properties of configurations declared earlier hide those of configurations * declared later. Configurations in the latter section are combined to a union * configuration, i.e. all of their properties are added to a large hierarchical * configuration. Configuration declarations that occur as direct children of * the root element are treated as override declarations. *

*

* Each configuration declaration consists of a tag whose name is associated * with a {@code CombinedConfigurationBuilderProvider}. This can be one of the * predefined tags like {@code properties}, or {@code xml}, or a custom tag, for * which a configuration builder provider was registered (as described above). * Attributes and sub elements with specific initialization parameters can be * added. There are some reserved attributes with a special meaning that can be * used in every configuration declaration: *

* * * * * * * * * * * * * * * * * * * * * * *
Standard attributes for configuration declarations
AttributeMeaning
{@code config-name}Allows specifying a name for this configuration. This name can be used to * obtain a reference to the configuration from the resulting combined * configuration (see below). It can also be passed to the * {@link #getNamedBuilder(String)} method.
{@code config-at}With this attribute an optional prefix can be specified for the * properties of the corresponding configuration.
{@code config-optional}Declares a configuration source as optional. This means that errors that * occur when creating the configuration are ignored.
{@code config-reload}Many configuration sources support a reloading mechanism. For those * sources it is possible to enable reloading by providing this attribute with a * value of true.
*

* The optional header section can contain some meta data about the * created configuration itself. For instance, it is possible to set further * properties of the {@code NodeCombiner} objects used for constructing the * resulting configuration. *

*

* The default configuration object returned by this builder is an instance of * the {@link CombinedConfiguration} class. This allows for convenient access to * the configuration objects maintained by the combined configuration (e.g. for * updates of single configuration objects). It has also the advantage that the * properties stored in all declared configuration objects are collected and * transformed into a single hierarchical structure, which can be accessed using * different expression engines. The actual {@code CombinedConfiguration} * implementation can be overridden by specifying the class in the * config-class attribute of the result element. *

*

* A custom EntityResolver can be used for all XMLConfigurations by adding *

* *
 * <entity-resolver config-class="EntityResolver fully qualified class name">
 * 
* *

* A specific CatalogResolver can be specified for all XMLConfiguration sources * by adding *

*
 * <entity-resolver catalogFiles="comma separated list of catalog files">
 * 
* *

* Additional ConfigurationProviders can be added by configuring them in the * header section. *

* *
 * <providers>
 *   <provider config-tag="tag name" config-class="provider fully qualified class name"/>
 * </providers>
 * 
* *

* Additional variable resolvers can be added by configuring them in the * header section. *

* *
 * <lookups>
 *   <lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/>
 * </lookups>
 * 
* *

* All declared override configurations are directly added to the resulting * combined configuration. If they are given names (using the * {@code config-name} attribute), they can directly be accessed using the * {@code getConfiguration(String)} method of {@code CombinedConfiguration}. The * additional configurations are altogether added to another combined * configuration, which uses a union combiner. Then this union configuration is * added to the resulting combined configuration under the name defined by the * {@code ADDITIONAL_NAME} constant. The {@link #getNamedBuilder(String)} method * can be used to access the {@code ConfigurationBuilder} objects for all * configuration sources which have been assigned a name; care has to be taken * that these names are unique. *

* * @since 1.3 * @author Commons * Configuration team */ public class CombinedConfigurationBuilder extends BasicConfigurationBuilder { /** * Constant for the name of the additional configuration. If the * configuration definition file contains an {@code additional} * section, a special union configuration is created and added under this * name to the resulting combined configuration. */ public static final String ADDITIONAL_NAME = CombinedConfigurationBuilder.class .getName() + "/ADDITIONAL_CONFIG"; /** Constant for the name of the configuration bean factory. */ static final String CONFIG_BEAN_FACTORY_NAME = CombinedConfigurationBuilder.class .getName() + ".CONFIG_BEAN_FACTORY_NAME"; /** Constant for the reserved name attribute. */ static final String ATTR_NAME = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "name" + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the name of the at attribute. */ static final String ATTR_ATNAME = "at"; /** Constant for the reserved at attribute. */ static final String ATTR_AT_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_ATNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the at attribute without the reserved prefix. */ static final String ATTR_AT = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_ATNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the name of the optional attribute. */ static final String ATTR_OPTIONALNAME = "optional"; /** Constant for the reserved optional attribute. */ static final String ATTR_OPTIONAL_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_OPTIONALNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the optional attribute without the reserved prefix. */ static final String ATTR_OPTIONAL = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_OPTIONALNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the forceCreate attribute. */ static final String ATTR_FORCECREATE = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "forceCreate" + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** Constant for the reload attribute. */ static final String ATTR_RELOAD = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "reload" + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END; /** * Constant for the tag attribute for providers. */ static final String KEY_SYSTEM_PROPS = "[@systemProperties]"; /** Constant for the name of the header section. */ static final String SEC_HEADER = "header"; /** Constant for an expression that selects the union configurations. */ static final String KEY_UNION = "additional"; /** An array with the names of top level configuration sections.*/ static final String[] CONFIG_SECTIONS = { "additional", "override", SEC_HEADER }; /** * Constant for an expression that selects override configurations in the * override section. */ static final String KEY_OVERRIDE = "override"; /** * Constant for the key that points to the list nodes definition of the * override combiner. */ static final String KEY_OVERRIDE_LIST = SEC_HEADER + ".combiner.override.list-nodes.node"; /** * Constant for the key that points to the list nodes definition of the * additional combiner. */ static final String KEY_ADDITIONAL_LIST = SEC_HEADER + ".combiner.additional.list-nodes.node"; /** * Constant for the key for defining providers in the configuration file. */ static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER + ".providers.provider"; /** * Constant for the tag attribute for providers. */ static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]"; /** * Constant for the key for defining variable resolvers */ static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER + ".lookups.lookup"; /** * Constant for the key for defining entity resolvers */ static final String KEY_ENTITY_RESOLVER = SEC_HEADER + ".entity-resolver"; /** * Constant for the prefix attribute for lookups. */ static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]"; /** * Constant for the FileSystem. */ static final String FILE_SYSTEM = SEC_HEADER + ".fileSystem"; /** * Constant for the key of the result declaration. This key can point to a * bean declaration, which defines properties of the resulting combined * configuration. */ static final String KEY_RESULT = SEC_HEADER + ".result"; /** Constant for the key of the combiner in the result declaration.*/ static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner"; /** Constant for the XML file extension. */ static final String EXT_XML = "xml"; /** Constant for the basic configuration builder class. */ private static final String BASIC_BUILDER = "org.apache.commons.configuration2.builder.BasicConfigurationBuilder"; /** Constant for the file-based configuration builder class. */ private static final String FILE_BUILDER = "org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder"; /** Constant for the reloading file-based configuration builder class. */ private static final String RELOADING_BUILDER = "org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder"; /** Constant for the name of the file-based builder parameters class. */ private static final String FILE_PARAMS = "org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl"; /** Constant for the provider for properties files. */ private static final ConfigurationBuilderProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationBuilderProvider( FILE_BUILDER, RELOADING_BUILDER, "org.apache.commons.configuration2.XMLPropertiesConfiguration", "org.apache.commons.configuration2.PropertiesConfiguration", EXT_XML, Collections.singletonList(FILE_PARAMS)); /** Constant for the provider for XML files. */ private static final ConfigurationBuilderProvider XML_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER, "org.apache.commons.configuration2.XMLConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.XMLBuilderParametersImpl")); /** Constant for the provider for JNDI sources. */ private static final BaseConfigurationBuilderProvider JNDI_PROVIDER = new BaseConfigurationBuilderProvider( BASIC_BUILDER, null, "org.apache.commons.configuration2.JNDIConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.JndiBuilderParametersImpl")); /** Constant for the provider for system properties. */ private static final BaseConfigurationBuilderProvider SYSTEM_PROVIDER = new BaseConfigurationBuilderProvider( BASIC_BUILDER, null, "org.apache.commons.configuration2.SystemConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters")); /** Constant for the provider for ini files. */ private static final BaseConfigurationBuilderProvider INI_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER, "org.apache.commons.configuration2.INIConfiguration", Collections.singletonList(FILE_PARAMS)); /** Constant for the provider for environment properties. */ private static final BaseConfigurationBuilderProvider ENV_PROVIDER = new BaseConfigurationBuilderProvider( BASIC_BUILDER, null, "org.apache.commons.configuration2.EnvironmentConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters")); /** Constant for the provider for plist files. */ private static final BaseConfigurationBuilderProvider PLIST_PROVIDER = new FileExtensionConfigurationBuilderProvider( FILE_BUILDER, RELOADING_BUILDER, "org.apache.commons.configuration2.plist.XMLPropertyListConfiguration", "org.apache.commons.configuration2.plist.PropertyListConfiguration", EXT_XML, Collections.singletonList(FILE_PARAMS)); /** Constant for the provider for configuration definition files. */ private static final BaseConfigurationBuilderProvider COMBINED_PROVIDER = new CombinedConfigurationBuilderProvider(); /** Constant for the provider for multiple XML configurations. */ private static final MultiFileConfigurationBuilderProvider MULTI_XML_PROVIDER = new MultiFileConfigurationBuilderProvider( "org.apache.commons.configuration2.XMLConfiguration", "org.apache.commons.configuration2.builder.XMLBuilderParametersImpl"); /** An array with the names of the default tags. */ private static final String[] DEFAULT_TAGS = { "properties", "xml", "hierarchicalXml", "plist", "ini", "system", "env", "jndi", "configuration", "multiFile" }; /** An array with the providers for the default tags. */ private static final ConfigurationBuilderProvider[] DEFAULT_PROVIDERS = { PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, PLIST_PROVIDER, INI_PROVIDER, SYSTEM_PROVIDER, ENV_PROVIDER, JNDI_PROVIDER, COMBINED_PROVIDER, MULTI_XML_PROVIDER }; /** A map with the default configuration builder providers. */ private static final Map DEFAULT_PROVIDERS_MAP; /** The builder for the definition configuration. */ private ConfigurationBuilder> definitionBuilder; /** Stores temporarily the configuration with the builder definitions. */ private HierarchicalConfiguration definitionConfiguration; /** The object with data about configuration sources. */ private ConfigurationSourceData sourceData; /** Stores the current parameters object. */ private CombinedBuilderParametersImpl currentParameters; /** The current XML parameters object. */ private XMLBuilderParametersImpl currentXMLParameters; /** The configuration that is currently constructed. */ private CombinedConfiguration currentConfiguration; /** * A {@code ConfigurationInterpolator} to be used as parent for all child * configurations to enable cross-source interpolation. */ private ConfigurationInterpolator parentInterpolator; /** * Creates a new instance of {@code CombinedConfigurationBuilder}. No parameters * are set. */ public CombinedConfigurationBuilder() { super(CombinedConfiguration.class); } /** * * Creates a new instance of {@code CombinedConfigurationBuilder} and sets * the specified initialization parameters. * @param params a map with initialization parameters */ public CombinedConfigurationBuilder(final Map params) { super(CombinedConfiguration.class, params); } /** * * Creates a new instance of {@code CombinedConfigurationBuilder} and sets * the specified initialization parameters and the allowFailOnInit flag. * @param params a map with initialization parameters * @param allowFailOnInit the allowFailOnInit flag */ public CombinedConfigurationBuilder(final Map params, final boolean allowFailOnInit) { super(CombinedConfiguration.class, params, allowFailOnInit); } /** * Returns the {@code ConfigurationBuilder} which creates the definition * configuration. * * @return the builder for the definition configuration * @throws ConfigurationException if an error occurs */ public synchronized ConfigurationBuilder> getDefinitionBuilder() throws ConfigurationException { if (definitionBuilder == null) { definitionBuilder = setupDefinitionBuilder(getParameters()); addDefinitionBuilderChangeListener(definitionBuilder); } return definitionBuilder; } /** * {@inheritDoc} This method is overridden to adapt the return type. */ @Override public CombinedConfigurationBuilder configure(final BuilderParameters... params) { super.configure(params); return this; } /** *

* Returns the configuration builder with the given name. With this method a * builder of a child configuration which was given a name in the * configuration definition file can be accessed directly. *

*

* Important note: This method only returns a meaningful * result after the result configuration has been created by calling * {@code getConfiguration()}. If called before, always an exception is * thrown. *

* * @param name the name of the builder in question * @return the child configuration builder with this name * @throws ConfigurationException if information about named builders is not * yet available or no builder with this name exists */ public synchronized ConfigurationBuilder getNamedBuilder( final String name) throws ConfigurationException { if (sourceData == null) { throw new ConfigurationException("Information about child builders" + " has not been setup yet! Call getConfiguration() first."); } final ConfigurationBuilder builder = sourceData.getNamedBuilder(name); if (builder == null) { throw new ConfigurationException("Builder cannot be resolved: " + name); } return builder; } /** *

* Returns a set with the names of all child configuration builders. A tag * defining a configuration source in the configuration definition file can * have the {@code config-name} attribute. If this attribute is present, the * corresponding builder is assigned this name and can be directly accessed * through the {@link #getNamedBuilder(String)} method. This method returns * a collection with all available builder names. *

*

* Important note: This method only returns a meaningful * result after the result configuration has been created by calling * {@code getConfiguration()}. If called before, always an empty set is * returned. *

* * @return a set with the names of all builders */ public synchronized Set builderNames() { if (sourceData == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(sourceData.builderNames()); } /** * {@inheritDoc} This implementation resets some specific internal state of * this builder. */ @Override public synchronized void resetParameters() { super.resetParameters(); definitionBuilder = null; definitionConfiguration = null; currentParameters = null; currentXMLParameters = null; if (sourceData != null) { sourceData.cleanUp(); sourceData = null; } } /** * Obtains the {@code ConfigurationBuilder} object which provides access to * the configuration containing the definition of the combined configuration * to create. If a definition builder is defined in the parameters, it is * used. Otherwise, we check whether the combined builder parameters object * contains a parameters object for the definition builder. If this is the * case, a builder for an {@code XMLConfiguration} is created and configured * with this object. As a last resort, it is looked for a * {@link FileBasedBuilderParametersImpl} object in the properties. If * found, also a XML configuration builder is created which loads this file. * Note: This method is called from a synchronized block. * * @param params the current parameters for this builder * @return the builder for the definition configuration * @throws ConfigurationException if an error occurs */ protected ConfigurationBuilder> setupDefinitionBuilder( final Map params) throws ConfigurationException { final CombinedBuilderParametersImpl cbParams = CombinedBuilderParametersImpl.fromParameters(params); if (cbParams != null) { final ConfigurationBuilder> defBuilder = cbParams.getDefinitionBuilder(); if (defBuilder != null) { return defBuilder; } if (cbParams.getDefinitionBuilderParameters() != null) { return createXMLDefinitionBuilder(cbParams .getDefinitionBuilderParameters()); } } final BuilderParameters fileParams = FileBasedBuilderParametersImpl.fromParameters(params); if (fileParams != null) { return createXMLDefinitionBuilder(fileParams); } throw new ConfigurationException( "No builder for configuration definition specified!"); } /** * Creates a default builder for the definition configuration and * initializes it with a parameters object. This method is called if no * definition builder is defined in this builder's parameters. This * implementation creates a default file-based builder which produces an * {@code XMLConfiguration}; it expects a corresponding file specification. * Note: This method is called in a synchronized block. * * @param builderParams the parameters object for the builder * @return the standard builder for the definition configuration */ protected ConfigurationBuilder> createXMLDefinitionBuilder( final BuilderParameters builderParams) { return new FileBasedConfigurationBuilder<>( XMLConfiguration.class).configure(builderParams); } /** * Returns the configuration containing the definition of the combined * configuration to be created. This method only returns a defined result * during construction of the result configuration. The definition * configuration is obtained from the definition builder at first access and * then stored temporarily to ensure that during result construction always * the same configuration instance is used. (Otherwise, it would be possible * that the definition builder returns a different instance when queried * multiple times.) * * @return the definition configuration * @throws ConfigurationException if an error occurs */ protected HierarchicalConfiguration getDefinitionConfiguration() throws ConfigurationException { if (definitionConfiguration == null) { definitionConfiguration = getDefinitionBuilder().getConfiguration(); } return definitionConfiguration; } /** * Returns a collection with the builders for all child configuration * sources. This method can be used by derived classes providing additional * functionality on top of the declared configuration sources. It only * returns a defined value during construction of the result configuration * instance. * * @return a collection with the builders for child configuration sources */ protected synchronized Collection> getChildBuilders() { return sourceData.getChildBuilders(); } /** * {@inheritDoc} This implementation evaluates the {@code result} property * of the definition configuration. It creates a combined bean declaration * with both the properties specified in the definition file and the * properties defined as initialization parameters. */ @Override protected BeanDeclaration createResultDeclaration(final Map params) throws ConfigurationException { final BeanDeclaration paramsDecl = super.createResultDeclaration(params); final XMLBeanDeclaration resultDecl = new XMLBeanDeclaration(getDefinitionConfiguration(), KEY_RESULT, true, CombinedConfiguration.class.getName()); return new CombinedBeanDeclaration(resultDecl, paramsDecl); } /** * {@inheritDoc} This implementation processes the definition configuration * in order to *
    *
  • initialize the resulting {@code CombinedConfiguration}
  • *
  • determine the builders for all configuration sources
  • *
  • populate the resulting {@code CombinedConfiguration}
  • *
*/ @Override protected void initResultInstance(final CombinedConfiguration result) throws ConfigurationException { super.initResultInstance(result); currentConfiguration = result; final HierarchicalConfiguration config = getDefinitionConfiguration(); if (config.getMaxIndex(KEY_COMBINER) < 0) { // No combiner defined => set default result.setNodeCombiner(new OverrideCombiner()); } setUpCurrentParameters(); initNodeCombinerListNodes(result, config, KEY_OVERRIDE_LIST); registerConfiguredProviders(config); setUpCurrentXMLParameters(); currentXMLParameters.setFileSystem(initFileSystem(config)); initSystemProperties(config, getBasePath()); registerConfiguredLookups(config, result); configureEntityResolver(config, currentXMLParameters); setUpParentInterpolator(currentConfiguration, config); final ConfigurationSourceData data = getSourceData(); final boolean createBuilders = data.getChildBuilders().isEmpty(); final List> overrideBuilders = data.createAndAddConfigurations(result, data.getOverrideSources(), data.overrideBuilders); if (createBuilders) { data.overrideBuilders.addAll(overrideBuilders); } if (!data.getUnionSources().isEmpty()) { final CombinedConfiguration addConfig = createAdditionalsConfiguration(result); result.addConfiguration(addConfig, ADDITIONAL_NAME); initNodeCombinerListNodes(addConfig, config, KEY_ADDITIONAL_LIST); final List> unionBuilders = data.createAndAddConfigurations(addConfig, data.unionDeclarations, data.unionBuilders); if (createBuilders) { data.unionBuilders.addAll(unionBuilders); } } result.isEmpty(); // this sets up the node structure currentConfiguration = null; } /** * Creates the {@code CombinedConfiguration} for the configuration * sources in the <additional> section. This method is * called when the builder constructs the final configuration. It creates a * new {@code CombinedConfiguration} and initializes some properties * from the result configuration. * * @param resultConfig the result configuration (this is the configuration * that will be returned by the builder) * @return the {@code CombinedConfiguration} for the additional * configuration sources * @since 1.7 */ protected CombinedConfiguration createAdditionalsConfiguration( final CombinedConfiguration resultConfig) { final CombinedConfiguration addConfig = new CombinedConfiguration(new UnionCombiner()); addConfig.setListDelimiterHandler(resultConfig.getListDelimiterHandler()); return addConfig; } /** * Processes custom {@link Lookup} objects that might be declared in the * definition configuration. Each {@code Lookup} object is registered at the * definition configuration and at the result configuration. It is also * added to all child configurations added to the resulting combined * configuration. * * @param defConfig the definition configuration * @param resultConfig the resulting configuration * @throws ConfigurationException if an error occurs */ protected void registerConfiguredLookups( final HierarchicalConfiguration defConfig, final Configuration resultConfig) throws ConfigurationException { final Map lookups = new HashMap<>(); final List> nodes = defConfig.configurationsAt(KEY_CONFIGURATION_LOOKUPS); for (final HierarchicalConfiguration config : nodes) { final XMLBeanDeclaration decl = new XMLBeanDeclaration(config); final String key = config.getString(KEY_LOOKUP_KEY); final Lookup lookup = (Lookup) fetchBeanHelper().createBean(decl); lookups.put(key, lookup); } if (!lookups.isEmpty()) { final ConfigurationInterpolator defCI = defConfig.getInterpolator(); if (defCI != null) { defCI.registerLookups(lookups); } resultConfig.getInterpolator().registerLookups(lookups); } } /** * Creates and initializes a default {@code FileSystem} if the definition * configuration contains a corresponding declaration. The file system * returned by this method is used as default for all file-based child * configuration sources. * * @param config the definition configuration * @return the default {@code FileSystem} (may be null) * @throws ConfigurationException if an error occurs */ protected FileSystem initFileSystem(final HierarchicalConfiguration config) throws ConfigurationException { if (config.getMaxIndex(FILE_SYSTEM) == 0) { final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, FILE_SYSTEM); return (FileSystem) fetchBeanHelper().createBean(decl); } return null; } /** * Handles a file with system properties that may be defined in the * definition configuration. If such property file is configured, all of its * properties are added to the system properties. * * @param config the definition configuration * @param basePath the base path defined for this builder (may be * null) * @throws ConfigurationException if an error occurs. */ protected void initSystemProperties(final HierarchicalConfiguration config, final String basePath) throws ConfigurationException { final String fileName = config.getString(KEY_SYSTEM_PROPS); if (fileName != null) { try { SystemConfiguration.setSystemProperties(basePath, fileName); } catch (final Exception ex) { throw new ConfigurationException( "Error setting system properties from " + fileName, ex); } } } /** * Creates and initializes a default {@code EntityResolver} if the * definition configuration contains a corresponding declaration. * * @param config the definition configuration * @param xmlParams the (already partly initialized) object with XML * parameters; here the new resolver is to be stored * @throws ConfigurationException if an error occurs */ protected void configureEntityResolver(final HierarchicalConfiguration config, final XMLBuilderParametersImpl xmlParams) throws ConfigurationException { if (config.getMaxIndex(KEY_ENTITY_RESOLVER) == 0) { final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY_ENTITY_RESOLVER, true); final EntityResolver resolver = (EntityResolver) fetchBeanHelper().createBean(decl, CatalogResolver.class); final FileSystem fileSystem = xmlParams.getFileHandler().getFileSystem(); if (fileSystem != null) { BeanHelper.setProperty(resolver, "fileSystem", fileSystem); } final String basePath = xmlParams.getFileHandler().getBasePath(); if (basePath != null) { BeanHelper.setProperty(resolver, "baseDir", basePath); } final ConfigurationInterpolator ci = new ConfigurationInterpolator(); ci.registerLookups(fetchPrefixLookups()); BeanHelper.setProperty(resolver, "interpolator", ci); xmlParams.setEntityResolver(resolver); } } /** * Returns the {@code ConfigurationBuilderProvider} for the given tag. This * method is called during creation of the result configuration. (It is not * allowed to call it at another point of time; result is then * unpredictable!) It supports all default providers and custom providers * added through the parameters object as well. * * @param tagName the name of the tag * @return the provider that was registered for this tag or null if * there is none */ protected ConfigurationBuilderProvider providerForTag(final String tagName) { return currentParameters.providerForTag(tagName); } /** * Initializes a parameters object for a child builder. This combined * configuration builder has a bunch of properties which may be inherited by * child configurations, e.g. the base path, the file system, etc. While * processing the builders for child configurations, this method is called * for each parameters object for a child builder. It initializes some * properties of the passed in parameters objects which are derived from * this parent builder. * * @param params the parameters object to be initialized */ protected void initChildBuilderParameters(final BuilderParameters params) { initDefaultChildParameters(params); if (params instanceof BasicBuilderParameters) { initChildBasicParameters((BasicBuilderParameters) params); } if (params instanceof XMLBuilderProperties) { initChildXMLParameters((XMLBuilderProperties) params); } if (params instanceof FileBasedBuilderProperties) { initChildFileBasedParameters((FileBasedBuilderProperties) params); } if (params instanceof CombinedBuilderParametersImpl) { initChildCombinedParameters((CombinedBuilderParametersImpl) params); } } /** * Initializes the event listeners of the specified builder from this * object. This method is used to inherit all listeners from a parent * builder. * * @param dest the destination builder object which is to be initialized */ void initChildEventListeners( final BasicConfigurationBuilder dest) { copyEventListeners(dest); } /** * Returns the configuration object that is currently constructed. This * method can be called during construction of the result configuration. It * is intended for internal usage, e.g. some specialized builder providers * need access to this configuration to perform advanced initialization. * * @return the configuration that us currently under construction */ CombinedConfiguration getConfigurationUnderConstruction() { return currentConfiguration; } /** * Initializes a bean using the current {@code BeanHelper}. This is needed * by builder providers when the configuration objects for sub builders are * constructed. * * @param bean the bean to be initialized * @param decl the {@code BeanDeclaration} */ void initBean(final Object bean, final BeanDeclaration decl) { fetchBeanHelper().initBean(bean, decl); } /** * Initializes the current parameters object. This object has either been * passed at builder configuration time or it is newly created. In any * case, it is manipulated during result creation. */ private void setUpCurrentParameters() { currentParameters = CombinedBuilderParametersImpl.fromParameters(getParameters(), true); currentParameters.registerMissingProviders(DEFAULT_PROVIDERS_MAP); } /** * Sets up an XML parameters object which is used to store properties * related to XML and file-based configurations during creation of the * result configuration. The properties stored in this object can be * inherited to child configurations. * * @throws ConfigurationException if an error occurs */ private void setUpCurrentXMLParameters() throws ConfigurationException { currentXMLParameters = new XMLBuilderParametersImpl(); initDefaultBasePath(); } /** * Sets up a parent {@code ConfigurationInterpolator} object. This object * has a default {@link Lookup} querying the resulting combined * configuration. Thus interpolation works globally across all configuration * sources. * * @param resultConfig the result configuration * @param defConfig the definition configuration */ private void setUpParentInterpolator(final Configuration resultConfig, final Configuration defConfig) { parentInterpolator = new ConfigurationInterpolator(); parentInterpolator.addDefaultLookup(new ConfigurationLookup( resultConfig)); final ConfigurationInterpolator defInterpolator = defConfig.getInterpolator(); if (defInterpolator != null) { defInterpolator.setParentInterpolator(parentInterpolator); } } /** * Initializes the default base path for all file-based child configuration * sources. The base path can be explicitly defined in the parameters of * this builder. Otherwise, if the definition builder is a file-based * builder, it is obtained from there. * * @throws ConfigurationException if an error occurs */ private void initDefaultBasePath() throws ConfigurationException { assert currentParameters != null : "Current parameters undefined!"; if (currentParameters.getBasePath() != null) { currentXMLParameters.setBasePath(currentParameters.getBasePath()); } else { final ConfigurationBuilder> defBuilder = getDefinitionBuilder(); if (defBuilder instanceof FileBasedConfigurationBuilder) { @SuppressWarnings("rawtypes") final FileBasedConfigurationBuilder fileBuilder = (FileBasedConfigurationBuilder) defBuilder; final URL url = fileBuilder.getFileHandler().getURL(); currentXMLParameters.setBasePath((url != null) ? url .toExternalForm() : fileBuilder.getFileHandler() .getBasePath()); } } } /** * Executes the {@link org.apache.commons.configuration2.builder.DefaultParametersManager * DefaultParametersManager} stored in the current * parameters on the passed in parameters object. If default handlers have been * registered for this type of parameters, an initialization is now * performed. This method is called before the parameters object is * initialized from the configuration definition file. So default values * can be overridden later with concrete property definitions. * * @param params the parameters to be initialized * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error * occurs when copying properties */ private void initDefaultChildParameters(final BuilderParameters params) { currentParameters.getChildDefaultParametersManager() .initializeParameters(params); } /** * Initializes basic builder parameters for a child configuration with * default settings set for this builder. This implementation ensures that * all {@code Lookup} objects are propagated to child configurations and * interpolation is setup correctly. * * @param params the parameters object */ private void initChildBasicParameters(final BasicBuilderParameters params) { params.setPrefixLookups(fetchPrefixLookups()); params.setParentInterpolator(parentInterpolator); if (currentParameters.isInheritSettings()) { params.inheritFrom(getParameters()); } } /** * Initializes a parameters object for a file-based configuration with * properties already set for this parent builder. This method handles * properties like a default file system or a base path. * * @param params the parameters object */ private void initChildFileBasedParameters( final FileBasedBuilderProperties params) { params.setBasePath(getBasePath()); params.setFileSystem(currentXMLParameters.getFileHandler() .getFileSystem()); } /** * Initializes a parameters object for an XML configuration with properties * already set for this parent builder. * * @param params the parameters object */ private void initChildXMLParameters(final XMLBuilderProperties params) { params.setEntityResolver(currentXMLParameters.getEntityResolver()); } /** * Initializes a parameters object for a combined configuration builder with * properties already set for this parent builder. This implementation deals * only with a subset of properties. Other properties are already handled by * the specialized builder provider. * * @param params the parameters object */ private void initChildCombinedParameters( final CombinedBuilderParametersImpl params) { params.registerMissingProviders(currentParameters); params.setBasePath(getBasePath()); } /** * Obtains the data object for the configuration sources and the * corresponding builders. This object is created on first access and reset * when the definition builder sends a change event. This method is called * in a synchronized block. * * @return the object with information about configuration sources * @throws ConfigurationException if an error occurs */ private ConfigurationSourceData getSourceData() throws ConfigurationException { if (sourceData == null) { if (currentParameters == null) { setUpCurrentParameters(); setUpCurrentXMLParameters(); } sourceData = createSourceData(); } return sourceData; } /** * Creates the data object for configuration sources and the corresponding * builders. * * @return the newly created data object * @throws ConfigurationException if an error occurs */ private ConfigurationSourceData createSourceData() throws ConfigurationException { final ConfigurationSourceData result = new ConfigurationSourceData(); result.initFromDefinitionConfiguration(getDefinitionConfiguration()); return result; } /** * Returns the current base path of this configuration builder. This is used * for instance by all file-based child configurations. * * @return the base path */ private String getBasePath() { return currentXMLParameters.getFileHandler().getBasePath(); } /** * Registers providers defined in the configuration. * * @param defConfig the definition configuration * @throws ConfigurationException if an error occurs */ private void registerConfiguredProviders(final HierarchicalConfiguration defConfig) throws ConfigurationException { final List> nodes = defConfig.configurationsAt(KEY_CONFIGURATION_PROVIDERS); for (final HierarchicalConfiguration config : nodes) { final XMLBeanDeclaration decl = new XMLBeanDeclaration(config); final String key = config.getString(KEY_PROVIDER_KEY); currentParameters.registerProvider(key, (ConfigurationBuilderProvider) fetchBeanHelper().createBean(decl)); } } /** * Adds a listener at the given definition builder which resets this builder * when a reset of the definition builder happens. This way it is ensured * that this builder produces a new combined configuration when its * definition configuration changes. * * @param defBuilder the definition builder */ private void addDefinitionBuilderChangeListener( final ConfigurationBuilder> defBuilder) { defBuilder.addEventListener(ConfigurationBuilderEvent.RESET, new EventListener() { @Override public void onEvent(final ConfigurationBuilderEvent event) { synchronized (CombinedConfigurationBuilder.this) { reset(); definitionBuilder = defBuilder; } } }); } /** * Returns a map with the current prefix lookup objects. This map is * obtained from the {@code ConfigurationInterpolator} of the configuration * under construction. * * @return the map with current prefix lookups (may be null) */ private Map fetchPrefixLookups() { final CombinedConfiguration cc = getConfigurationUnderConstruction(); return (cc != null) ? cc.getInterpolator().getLookups() : null; } /** * Creates {@code ConfigurationDeclaration} objects for the specified * configurations. * * @param configs the list with configurations * @return a collection with corresponding declarations */ private Collection createDeclarations( final Collection> configs) { final Collection declarations = new ArrayList<>(configs.size()); for (final HierarchicalConfiguration c : configs) { declarations.add(new ConfigurationDeclaration(this, c)); } return declarations; } /** * Initializes the list nodes of the node combiner for the given combined * configuration. This information can be set in the header section of the * configuration definition file for both the override and the union * combiners. * * @param cc the combined configuration to initialize * @param defConfig the definition configuration * @param key the key for the list nodes */ private static void initNodeCombinerListNodes(final CombinedConfiguration cc, final HierarchicalConfiguration defConfig, final String key) { final List listNodes = defConfig.getList(key); for (final Object listNode : listNodes) { cc.getNodeCombiner().addListNode((String) listNode); } } /** * Creates the map with the default configuration builder providers. * * @return the map with default providers */ private static Map createDefaultProviders() { final Map providers = new HashMap<>(); for (int i = 0; i < DEFAULT_TAGS.length; i++) { providers.put(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]); } return providers; } static { DEFAULT_PROVIDERS_MAP = createDefaultProviders(); } /** * A data class for storing information about all configuration sources * defined for a combined builder. */ private class ConfigurationSourceData { /** A list with data for all builders for override configurations. */ private final List overrideDeclarations; /** A list with data for all builders for union configurations. */ private final List unionDeclarations; /** A list with the builders for override configurations. */ private final List> overrideBuilders; /** A list with the builders for union configurations. */ private final List> unionBuilders; /** A map for direct access to a builder by its name. */ private final Map> namedBuilders; /** A collection with all child builders. */ private final Collection> allBuilders; /** A listener for reacting on changes of sub builders. */ private final EventListener changeListener; /** * Creates a new instance of {@code ConfigurationSourceData}. */ public ConfigurationSourceData() { overrideDeclarations = new ArrayList<>(); unionDeclarations = new ArrayList<>(); overrideBuilders = new ArrayList<>(); unionBuilders = new ArrayList<>(); namedBuilders = new HashMap<>(); allBuilders = new LinkedList<>(); changeListener = createBuilderChangeListener(); } /** * Initializes this object from the specified definition configuration. * * @param config the definition configuration * @throws ConfigurationException if an error occurs */ public void initFromDefinitionConfiguration( final HierarchicalConfiguration config) throws ConfigurationException { overrideDeclarations.addAll(createDeclarations(fetchTopLevelOverrideConfigs(config))); overrideDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_OVERRIDE))); unionDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_UNION))); } /** * Processes the declaration of configuration builder providers, creates * the corresponding builder if necessary, obtains configurations, and * adds them to the specified result configuration. * * @param ccResult the result configuration * @param srcDecl the collection with the declarations of configuration * sources to process * @return a list with configuration builders * @throws ConfigurationException if an error occurs */ public List> createAndAddConfigurations( final CombinedConfiguration ccResult, final List srcDecl, final List> builders) throws ConfigurationException { final boolean createBuilders = builders.isEmpty(); List> newBuilders; if (createBuilders) { newBuilders = new ArrayList<>(srcDecl.size()); } else { newBuilders = builders; } for (int i = 0; i < srcDecl.size(); i++) { ConfigurationBuilder b; if (createBuilders) { b = createConfigurationBuilder(srcDecl.get(i)); newBuilders.add(b); } else { b = builders.get(i); } addChildConfiguration(ccResult, srcDecl.get(i), b); } return newBuilders; } /** * Frees resources used by this object and performs clean up. This * method is called when the owning builder is reset. */ public void cleanUp() { for (final ConfigurationBuilder b : getChildBuilders()) { b.removeEventListener(ConfigurationBuilderEvent.RESET, changeListener); } namedBuilders.clear(); } /** * Returns a collection containing the builders for all child * configuration sources. * * @return the child configuration builders */ public Collection> getChildBuilders() { return allBuilders; } /** * Returns a collection with all configuration source declarations * defined in the override section. * * @return the override configuration builders */ public List getOverrideSources() { return overrideDeclarations; } /** * Returns a collection with all configuration source declarations * defined in the union section. * * @return the union configuration builders */ public List getUnionSources() { return unionDeclarations; } /** * Returns the {@code ConfigurationBuilder} with the given name. If no * such builder is defined in the definition configuration, result is * null. * * @param name the name of the builder in question * @return the builder with this name or null */ public ConfigurationBuilder getNamedBuilder( final String name) { return namedBuilders.get(name); } /** * Returns a set with the names of all known named builders. * * @return the names of the available sub builders */ public Set builderNames() { return namedBuilders.keySet(); } /** * Creates a configuration builder based on a source declaration in the * definition configuration. * * @param decl the current {@code ConfigurationDeclaration} * @return the newly created builder * @throws ConfigurationException if an error occurs */ private ConfigurationBuilder createConfigurationBuilder( final ConfigurationDeclaration decl) throws ConfigurationException { final ConfigurationBuilderProvider provider = providerForTag(decl.getConfiguration().getRootElementName()); if (provider == null) { throw new ConfigurationException( "Unsupported configuration source: " + decl.getConfiguration().getRootElementName()); } final ConfigurationBuilder builder = provider.getConfigurationBuilder(decl); if (decl.getName() != null) { namedBuilders.put(decl.getName(), builder); } allBuilders.add(builder); builder.addEventListener(ConfigurationBuilderEvent.RESET, changeListener); return builder; } /** * Creates a new configuration using the specified builder and adds it * to the resulting combined configuration. * * @param ccResult the resulting combined configuration * @param decl the current {@code ConfigurationDeclaration} * @param builder the configuration builder * @throws ConfigurationException if an error occurs */ private void addChildConfiguration(final CombinedConfiguration ccResult, final ConfigurationDeclaration decl, final ConfigurationBuilder builder) throws ConfigurationException { try { ccResult.addConfiguration( builder.getConfiguration(), decl.getName(), decl.getAt()); } catch (final ConfigurationException cex) { // ignore exceptions for optional configurations if (!decl.isOptional()) { throw cex; } } } /** * Creates a listener for builder change events. This listener is * registered at all builders for child configurations. */ private EventListener createBuilderChangeListener() { return new EventListener() { @Override public void onEvent(final ConfigurationBuilderEvent event) { resetResult(); } }; } /** * Finds the override configurations that are defined as top level * elements in the configuration definition file. This method fetches * the child elements of the root node and removes the nodes that * represent other configuration sections. The remaining nodes are * treated as definitions for override configurations. * * @param config the definition configuration * @return a list with sub configurations for the top level override * configurations */ private List> fetchTopLevelOverrideConfigs( final HierarchicalConfiguration config) { final List> configs = config.childConfigurationsAt(null); for (final Iterator> it = configs.iterator(); it.hasNext();) { final String nodeName = it.next().getRootElementName(); for (final String element : CONFIG_SECTIONS) { if (element.equals(nodeName)) { it.remove(); break; } } } return configs; } } }