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

The 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 java.util.stream.Collectors;

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 */ public class CombinedConfigurationBuilder extends BasicConfigurationBuilder { /** * A data class for storing information about all configuration sources defined for a combined builder. */ private final 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(); } /** * 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; } } } /** * 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(); } /** * Frees resources used by this object and performs clean up. This method is called when the owning builder is reset. */ public void cleanUp() { getChildBuilders().forEach(b -> b.removeEventListener(ConfigurationBuilderEvent.RESET, changeListener)); namedBuilders.clear(); } /** * 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. * @param builders List of configuration builders. * @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(); final List> newBuilders; if (createBuilders) { newBuilders = new ArrayList<>(srcDecl.size()); } else { newBuilders = builders; } for (int i = 0; i < srcDecl.size(); i++) { final 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; } /** * Creates a listener for builder change events. This listener is registered at all builders for child configurations. */ private EventListener createBuilderChangeListener() { return event -> resetResult(); } /** * 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; } /** * 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; } /** * Gets a collection containing the builders for all child configuration sources. * * @return the child configuration builders */ public Collection> getChildBuilders() { return allBuilders; } /** * Gets 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); } /** * Gets a collection with all configuration source declarations defined in the override section. * * @return the override configuration builders */ public List getOverrideSources() { return overrideDeclarations; } /** * Gets a collection with all configuration source declarations defined in the union section. * * @return the union configuration builders */ public List getUnionSources() { return unionDeclarations; } /** * 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))); } } /** * 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; static { DEFAULT_PROVIDERS_MAP = createDefaultProviders(); } /** * 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; } /** * 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) { defConfig.getList(key).forEach(listNode -> cc.getNodeCombiner().addListNode((String) listNode)); } /** 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); } /** * 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, event -> { synchronized (this) { reset(); definitionBuilder = defBuilder; } }); } /** *

* 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 method is overridden to adapt the return type. */ @Override public CombinedConfigurationBuilder configure(final BuilderParameters... params) { super.configure(params); return this; } /** * 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); } } /** * Creates the {@code CombinedConfiguration} for the configuration sources in the {@code <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; } /** * 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) { return configs.stream().map(c -> new ConfigurationDeclaration(this, c)).collect(Collectors.toList()); } /** * {@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); } /** * 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; } /** * 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 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; } /** * Gets 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(); } /** * Gets 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(); } /** * Gets 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; } /** * Gets 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; } /** * Gets 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; } /** *

* Gets 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; } /** * 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; } /** * 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 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 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 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()); } /** * 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); } /** * 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 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); } /** * 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; } /** * {@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; } /** * 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); } } } /** * 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); } /** * 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 = defConfig.configurationsAt(KEY_CONFIGURATION_LOOKUPS).stream().collect( Collectors.toMap(config -> config.getString(KEY_LOOKUP_KEY), config -> (Lookup) fetchBeanHelper().createBean(new XMLBeanDeclaration(config)))); if (!lookups.isEmpty()) { final ConfigurationInterpolator defCI = defConfig.getInterpolator(); if (defCI != null) { defCI.registerLookups(lookups); } resultConfig.getInterpolator().registerLookups(lookups); } } /** * Registers providers defined in the configuration. * * @param defConfig the definition configuration */ private void registerConfiguredProviders(final HierarchicalConfiguration defConfig) { defConfig.configurationsAt(KEY_CONFIGURATION_PROVIDERS).forEach(config -> { final XMLBeanDeclaration decl = new XMLBeanDeclaration(config); final String key = config.getString(KEY_PROVIDER_KEY); currentParameters.registerProvider(key, (ConfigurationBuilderProvider) fetchBeanHelper().createBean(decl)); }); } /** * {@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; } } /** * 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(); } /** * 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!"); } /** * 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy