The ConfigurationFactory
class that was introduced in the
last
section is a powerful tool for dealing with multiple different
configuration sources, but it also has some shortcomings:
ConfigurationFactory
depends on the order of declarations
in a configuration definition file.
To work around these limitations the class
DefaultConfigurationBuilder
was introduced.
From its basic usage scenarios DefaultConfigurationBuilder
is
very similar to ConfigurationFactory
. It is able to process
the same configuration definition files as can be read by
ConfigurationFactory
, but supports more options. The following
list identifies the main differences between these classes:
DefaultConfigurationBuilder
extends XMLConfiguration
.
This means that it is a
file-based configuration, and thus supports multiple ways of
specifying the location of the configuration definition file (e.g.
as java.io.File
object, as URL, etc.).DefaultConfigurationBuilder
is an instance of the
CombinedConfiguration class, i.e. a truely hierarchical
configuration supporting enhanced query facilities.DefaultConfigurationBuilder
supports custom tags in its
configuration definition file. For this purpose a so-called
configuration provider has to be registered, which will be
called when a corresponding tag is encountered.
As was already pointed out, DefaultConfigurationBuilder
maintains compatibility with ConfigurationFactory
in that it
understands the same configuration definition files. In addition to the
elements that are allowed in a configuration definition file for
ConfigurationFactory
the data files for
DefaultConfigurationBuilder
support some additional options
providing greater flexibility. This section explains these enhanced
features.
Overall structure of a configuration definition file
A configuration definition file for DefaultConfigurationBuilder
can contain three sections, all of which are optional. A skeleton looks as
follows:
<?xml version="1.0" encoding="ISO-8859-1" ?> <configuration systemProperties="path to property file"> <header> <!-- Meta data about the resulting combined configuration --> </header> <override> <!-- Configuration declarations with override semantics --> </override> <additional> <!-- Configuration declarations that form a union configuration --> </additional> </configuration>
Declaring configuration sources
The override
and additional
sections should look
familar to users that have already worked with
ConfigurationFactory
. They have the exact same purpose here,
i.e. they contain declarations for the configuration sources to be
embedded. For compatibility reasons it is also allowed to declare
configuration sources outside these sections; they are then treated as if
they were placed inside the override
section.
Each declaration of a configuration source is represented by an XML
element whose name determines the type of the configuration source (e.g.
properties
for properties files, or xml
for
XML documents). Per default all configuration types are supported that
are also allowed for ConfigurationFactory
. A list of all
supported tags can be found
here.
In addition to the default tags provided by ConfigurationFactory
DefaultConfigurationBuilder
knows the following tags:
configuration
tag allows other configuration
definition files to be included. This makes it possible to nest these
definition files up to an arbitrary depth. In fact, this tag will
create another DefaultConfigurationBuilder
object,
initialize it, and obtain the CombinedConfiguation
from it.
This combined configuration will then be added to the resulting
combined configuration. Like all file-based configurations the
fileName
attribute can be used to specify the configuration
definition file to be loaded. This file must be an XML document that
conforms to the format described here.In the declaration of a configuration source it is possible to set properties on the corresponding configuration objects. Configuration declarations are indeed Bean declarations. That means they can have attributes matching simple properties of the configuration object to create and sub elements matching complex properties. The following example fragment shows how complex initialization can be performed in a configuration declaration:
<properties fileName="test.properties" throwExceptionOnMissing="true"> <reloadingStrategy refreshDelay="10000" config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/> </properties> <xml fileName="test.xml" delimiterParsingDisabled="true"> <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine" propertyDelimiter="/" indexStart="[" indexEnd="]"/> </xml>
In this example a configuration source for a properties file and one for
an XML document are defined. For the properties source the
throwExceptionOnMissing
property is set to true,
which means that it should throw an exception if a requested property is
not found. In addition it is assigned a reloading strategy, which is
declared and configured in a sub element. The XML configuration source is
initialized in a similar way: a simple property is set, and an expression
engine is assigned. More information about the format for declaring objects
and initializing their properties can be found in the section about
bean
declarations.
In addition to the attributes that correspond to properties of the
configuration object to be created, a configuration declaration can have a
set of special attributes that are evaluated by
DefaultConfigurationBuilder
when it creates the objects.
These attributes are listed in the following table:
Attribute | Meaning |
---|---|
config-name | Allows a name to be specified for this configuration. This name can be used to obtain a reference to the configuration from the resulting combined configuration (see below). |
config-at | With this attribute an optional prefix can be specified for the properties of the corresponding configuration. |
config-optional | Declares a configuration as optional. This means that errors that
occur when creating the configuration are silently ignored. The default
behavior when an error occurs is that no configuration is added to
the resulting combined configuration. This behavior can be used to find
out whether an optional configuration could be successfully created or
not. If you specify a name for the optional configuration (using the
config-name attribute), you can later check whether the
combined configuration contains a configuration with this name. With the
config-forceCreate attribute (see below) this default
behavior can be changed. |
config-forceCreate | This boolean attribute is only evaluated for configurations declared as
optional. It determines the behavior of the configuration builder when
the optional configuration could not be created. If set to true,
the builder tries to create an empty, uninitialized configuration of the
correct type and add it to the resulting combined configuration. This is
especially useful for file-based configurations. Consider a use case
where an application wants to store user specific configuration files in
the users' home directories. When a user starts this application for the
first time, the user configuration does not exist yet. If it is declared
as optional and forceCreate, the missing configuration
file won't cause an error, but an empty configuration will be created.
The application can then obtain this configuration, add properties to it
(e.g. user specific settings) and save it. Without the
config-forceCreate attribute the application would have to
check whether the user configuration exists in the combined configuration
and eventually create it manually. Note that not all configuration
providers support this mechanism. Sometimes it may not be possible to
create an empty configuration if the standard initialization fails. In
this case no configuration will be added to the combined configuration
(with other words: the config-forceCreate attribute will not
have any effect). |
The config-at
and config-optional
attributes
have the same meaning as the at
and optional
attributes for ConfigurationFactory
. For compatibility
reasons the old attributes without the config-
prefix are
still supported. Note that the config-at
is now allowed for
override configurations, too (ConfigurationFactory
evaluated
the at
attribute only for configuration declarations in the
additional
section).
Another useful feature is the built-in support for interpolation (i.e.
variable substitution): You can use variables in your configuration
definition file that are defined in declared configuration sources. For
instance, if the name of a configuration file to be loaded is defined by
the system property CONFIG_FILE
, you can do something like
this:
<configuration> <!-- Load the system properties --> <system/> <!-- Now load the config file, using a system property as file name --> <properties fileName="${CONFIG_FILE}"/> </configuration>
Note that you can refer only to properties that have already been loaded.
If you change the order of the <system>
and the
<properties>
elements in the example above, an error
will occur because the ${CONFIG_FILE}
variable will then be
undefined at the moment it is evaluated.
<configuration systemProperties="systemProperties.xml"> <!-- Load the system properties --> <system/> <!-- Now load the config file, using a system property as file name --> <properties fileName="${CONFIG_FILE}"/> </configuration>
This example differs from the previous one by allowing CONFIG_FILE, and other properties, to be defined in a properties file and added to the system properties before the configuration is constructed.
The header section
In the header section properties of the resulting combined configuration object can be set. The main part of this section is a bean declaration that is used for creating the resulting configuration object. Other elements can be used for customizing the Node combiners used by the override and the union combined configuration. The following example shows a header section that uses all supported properties:
<header> <result delimiterParsingDisabled="true" forceReloadCheck="true"> <nodeCombiner config-class="org.apache.commons.configuration.tree.OverrideCombiner"/> <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/> </result> <combiner> <override> <list-nodes> <node>table</node> <node>list</node> </list-nodes> </override> <additional> <list-nodes> <node>table</node> </list-nodes> </additional> </combiner> </header>
The result
element points to the bean declaration for the
resulting combined configuration. In this example we set some attributes
and initialize the node combiner (which is not necessary because the
default override combiner is specified) and the expression engine to be
used. Note that the config-class
attribute makes it
possible to inject custom classes for the resulting configuration or the
node combiner.
The combiner
section allows nodes to be defined as list nodes.
This can be necessary for certain node combiner implementations to work
correctly. More information can be found in the section about
Node combiners.
After all that theory let's go through an example! We start with the configuration definition file that looks like the following:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!-- Test configuration definition file that demonstrates complex initialization --> <configuration> <header> <result delimiterParsingDisabled="true" forceReloadCheck="true"> <expressionEngine config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/> </result> <combiner> <additional> <list-nodes> <node>table</node> </list-nodes> </additional> </combiner> </header> <override> <properties fileName="user.properties" throwExceptionOnMissing="true" config-name="properties" config-optional="true"> <reloadingStrategy refreshDelay="10000" config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"/> </properties> <xml fileName="settings.xml" config-name="xml"/> </override> <additional> <xml config-name="tab1" fileName="table1.xml" config-at="database.tables"/> <xml config-name="tab2" fileName="table2.xml" config-at="database.tables" validating="true"/> </additional> </configuration>
This configuration definition file includes four configuration sources and
sets some properties for the resulting CombinedConfiguration
.
Of special interest is the forceReloadCheck
property, which
enables a special check for detecting property changes in the contained
configuration sources. If this property is not set, reloading won't work.
Because we have configured a reloading strategy for one of the included
configuration sources we have to set this flag so that this reloading
strategy can function properly. More details about this topic can be
found in the Javadocs for
CombinedConfiguration
. We also set some properties for the
configurations to be loaded; for instance we declare that one of the XML
configurations should be validated.
With the following code we can create a DefaultConfigurationBuilder
and load this file:
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); builder.setFile(new File("configuration.xml")); CombinedConfiguration cc = builder.getConfiguration(true);
It would have been possible to specify the location of the configuration
definition file in multiple other ways, e.g. as a URL. The boolean argument
in the call to getConfiguration()
determines whether the
configuration definition file should be loaded. For our simple example we
want this to happen, but it would also be possible to load the file
manually (by calling the load()
method), and after that
updating the configuration. (Remember that DefaultConfigurationBuilder
is derived from XMLConfiguration
, that means you can use all methods
provided by this class to alter its data, e.g. to add further configuration
sources.) If the configuration's data was manually changed, you should
call getConfiguration()
with the argument false.
XMLConfiguration
also provides the registerEntityId()
method that can be used to define the location of DTD files (refer to the
section
Validation of XML configuration files for more details). This method
is available for DefaultConfigurationBuilder
, too. The
entities registered here will be passed to the loaded child XML
configurations. So you can register the DTDs of all child XML configurations
globally at the configuration builder.
In the header
section we have chosen an XPATH expression
engine for the resulting configuration. So we can query our properties
using the convenient XPATH syntax. By providing the config-name
attribute we have given all configuration sources a name. This name can
be used to obtain the corresponding sources from the combined
configuration. For configurations in the override section this is
directly possible:
Configuration propertiesConfig = cc.getConfiguration("properties"); Configuration xmlConfig = cc.getConfiguration("xml");
Configurations in the additional
section are treated a bit
differently: they are all packed together in another combined configuration
and then added to the resulting combined configuration. So in our example
the combined configuration cc
will contain three configurations:
the two configurations from the override section, and the combined
configuration with the additional
configurations. The latter
is stored under a name determined by the ADDITIONAL_NAME
constant of DefaultConfigurationBuilder
. The following
code shows how the configurations of the additional
section
can be accessed:
CombinedConfiguration ccAdd = (CombinedConfiguration) cc.getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME); Configuration tab1Config = ccAdd.getConfiguration("tab1"); Configuration tab2Config = ccAdd.getConfiguration("tab2");
If you have written a custom configuration class, you might want to
declare instances of this class in a configuration definition file, too.
With DefaultConfigurationBuilder
this is now possible by
registering a ConfigurationProvider.
ConfigurationProvider
is an inner class defined in
DefaultConfigurationBuilder
. Its task is to create and
initialize a configuration object. Whenever DefaultConfigurationBuilder
encounters a tag in the override
or the additional
section it checks whether for this tag a ConfigurationProvider
was registered. If this is the case, the provider is asked to create a
new configuration instance; otherwise an exception will be thrown.
So for adding support for a new configuration class you have to create an
instance of ConfigurationProvider
(or a derived class) and
register it at the configuration builder using the
addConfigurationProvider()
method. This method expects the
name of the associated tag and the provider instance as arguments.
If your custom configuration class does not need any special initialization,
you can use the ConfigurationProvider
class directly. It is
able of creating an instance of a specified class (which must be derived
from AbstractConfiguration
). Let's take a look at an example
where we want to add support for a configuration class called
MyConfiguration
. The corresponding tag in the configuration
definition file should have the name myconfig
. The code for
registering the new provider and loading the configuration definition file
looks as follows:
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider(MyConfiguration.class); builder.addConfigurationProvider("myconfig", provider); builder.setFileName("configuration.xml"); Configuration config = builder.getConfiguration();
If your configuration provider is registered this way, your configuration
definition file can contain the myconfig
tag just as any
other tag for declaring a configuration source:
<configuration> <additional> <xml fileName="settings.xml"/> <myconfig delimiterParsingDisabled="true"/> </additional> </configuration>
As is demonstrated in this example, it is possible to specify attributes
for initializing properties of your configuration object. In this example
we set the default delimiterParsingDisabled
property
inherited from AbstractConfiguration
. Of course you can set
custom properties of your configuration class, too.
If your custom configuration class is a file-based configuration, you
should use the FileConfigurationProvider
class instead of
ConfigurationProvider
. FileConfigurationProvider
is another inner class of DefaultConfigurationBuilder
that
knows how to deal with file-based configurations: it ensures that the
correct base path is set and takes care of invoking the load()
method.
If your custom configuration class requires special initialization, you
need to create your own provider class that extends
ConfigurationProvider
. Here you will have to override the
getConfiguration(ConfigurationDeclaration)
method, which is
responsible for creating the configuration instance (all information
necessary for this purpose can be obtained from the passed in declaration
object). It is recommended that you call the inherited method first,
which will instantiate and initialize the new configuration object. Then
you can perform your specific initialization.