Creating Configurations
Before a configuration and the data it contains can be accessed it has
to be created and initialized first. Although the concrete
Configuration
implementations provided by Commons
Configuration typically have a public default constructor,
instances should only be created directly in exceptional cases. The
recommended way is to use a configuration builder.
Configuration Builders
Commons Configuration defines the generic
ConfigurationBuilder
interface which is used for creating
initialized configuration objects. The interface defines a
getConfiguration()
method returning a generic type
derived from Configuration
. Here a specific
Configuration
implementation class can be inserted.
Note that the name of this method is getConfiguration()
and
not createConfiguration()
. This is because a builder is not
required to create a new instance on each invocation. Rather, a builder
is also responsible for managing the instance it has created. A basic
implementation may create its managed configuration once on first
access and will then always return the same instance in its
getConfiguration()
method. A builder which is aware of
reloading in contrast may invalidate its managed configuration when it
detects that the content of the configuration has changed on disk; then
the next invocation of getConfiguration()
returns an updated
configuration object. (Reloading is discussed in more detail in a later
section of this user's guide.) Builder implementations must be
thread-safe so that it is guaranteed that they behave correctly even if
accessed concurrently by multiple threads.
The recommended usage for accessing configuration data from multiple
parts of an application is to create a builder object initially and keep
a reference to it centrally. Whenever configuration information is needed
the builder is asked for its Configuration
object; from
there the actual configuration settings can be obtained. While this
approach introduces another level of indirection, it enables the
application to add new functionality transparently by replacing the builder
implementation. For instance, if at a later stage the
requirement occurs to react on external changes of configuration data,
the builder object can be replaced by a reloading-aware builder. This
enables support for reloading all over in the application immediately
without having to change anything.
BasicConfigurationBuilder
Commons Configuration provides multiple concrete
ConfigurationBuilder
implementations supporting different
features. The most basic implementation is
BasicConfigurationBuilder
. It is the base class for all other
builder implementations and defines a framework for creating and
initializing configuration objects. The functionality provided is as
follows:
- When an instance of
BasicConfigurationBuilder
is
constructed the class of the Configuration
implementation
to be created has to be passed. This class must be compatible with
the generic type parameter of the builder.
- It is possible to set arbitrary initialization parameters for
the configuration object to be created. Such parameters correspond to
the special properties offered by the configuration class (e.g. the
throwExceptionOnMissing
flag, the object for handling
lists, helper objects for variable interpolation, and so on).
- The
getConfiguration()
method checks whether the managed
configuration instance has already been created. If so, it can be
directly returned.
- If this is the first access to
getConfiguration()
,
the managed configuration object is created. This is done via
reflection: the configuration object is created, and all initialization
parameters which have been set are applied.
- There is also a
reset()
method which removes the
managed configuration instance. Calling this method causes a new
instance to be created the next time getConfiguration()
is invoked.
Note that these methods are all properly synchronized so that the builder
class is thread-safe.
The following code fragment shows how a BasicConfigurationBuilder
can be used to create an empty PropertiesConfiguration
object. At this stage of the discussion, the details of this example will
not yet be understandable; they will be explained in the following
sections. This is just to get a feeling how the usage of configuration
builders looks like in practice:
Parameters params = new Parameters();
BasicConfigurationBuilder<PropertiesConfiguration> builder =
new BasicConfigurationBuilder<PropertiesConfiguration>(
PropertiesConfiguration.class)
.configure(params.basic()
.setListDelimiterHandler(
new DefaultListDelimiterHandler(','))
.setThrowExceptionOnMissing(true));
PropertiesConfiguration config = builder.getConfiguration();
Initialization Parameters
Depending on the concrete Configuration
class to be
instantiated, it can be necessary to set a bunch of initialization
parameters. In order to simplify this and make the code somewhat
concise, a fluent API is provided for setting initialization parameters.
Basically, initialization parameters are defined by POJOs (plain old
Java objects) with properties corresponding to the special properties
supported by the configuration object to be created. In the package
org.apache.commons.configuration2.builder.fluent
a number
of interfaces is contained defining the possible initialization
parameters for the standard Configuration
implementations
shipped with Commons Configuration. These interfaces form a
natural inheritance hierarchy corresponding to the inheritance graph
used by concrete Configuration
implementations. So there is
a fundamental set of initialization parameters supported by all classes
derived from
AbstractConfiguration
. Configurations loaded from a file
also support these parameters plus additional ones for defining the file
to be loaded. An XML-based configuration supports all basic and
file-related parameters plus specific parameters defining its specific
properties, and so on.
Exposing such parameter objects via a fluent interface becomes tricky in
Java if inheritance is involved. In Commons Configuration the
Parameters
class is responsible for the creation of parameters
objects. It serves as a type-safe factory for parameter objects with
support for inheritance. It defines a set of methods for creating
parameter objects for special Configuration
classes. On
the objects returned by these methods fluent set
methods can
be invoked in order to set the single properties. As an example consider
the following code fragment which defines some properties for an
XMLConfiguration
:
Parameters params = new Parameters();
XMLBuilderParams xmlParams = params.xml()
.setThrowExceptionOnMissing(true)
.setValidating(true)
.setEncoding("UTF-8")
.setFileName("config.xml")
.setExpressionEngine(new XPathExpressionEngine());
Note how properties from different parameter interfaces can be set in an
arbitrary order: the throwExceptionOnMissing
flag is part
of the basic initialization parameters common to all configuration
classes, the encoding and the file name parameters are common to all
file-based configurations, the expression engine parameter is supported
by all hierarchical configurations, and the validating
flag is
specific to XML configurations. We will not describe all available
initialization parameters in detail now; they are explained in the
sections dealing with specific Configuration
classes (and
of course, the Javadoc is the ultimate reference). For
now a short overview over the existing parameter objects and the
corresponding methods in the Parameters
class should be
sufficient:
Parameters method |
Interface |
Description |
basic() |
BasicBuilderParameters |
Defines fundamental properties common to all Configuration
implementations derived from AbstractConfiguration . |
fileBased() |
FileBasedBuilderParameters |
Properties related to file-based configurations. For instance,
multiple ways for defining the file to be loaded are provided. |
combined() |
CombinedBuilderParameters |
This object is used by the specialized builder for combined
configurations. Here properties can be set which define the content
of the resulting combined configuration. |
jndi() |
JndiBuilderParameters |
A parameters object for initializing JNDI configurations. |
hierarchical() |
HierarchicalBuilderParameters |
Here special parameters common to all hierarchical configurations are
defined, for instance the expression engine. |
xml() |
XMLBuilderParameters |
The parameters for XML configurations. |
properties() |
PropertiesBuilderParameters |
The parameters for properties
configurations. |
multiFile() |
MultiFileBuilderParameters |
This parameters class is used by the builder for
multi
file configurations. |
database() |
DatabaseBuilderParameters |
The parameters for
DatabaseConfiguration . |
After a parameters object has been created and initialized via its fluent
set()
methods, it can be passed to a configuration builder's
configure()
method. This method extracts all properties from
the passed in object and stores them internally. They are then used to
initialize a newly created Configuration
object. Calling
configure()
another time with a different parameters object
overrides all properties set so far; more precise, the existing properties are
cleared, and the new ones are copied over. However, it is possible to
pass multiple parameters objects at once to the configure()
method
(it has a varargs argument). In this case, the union of all parameters
is constructed.
Configuring a configuration builder with parameters objects is an
expressive and type-safe way. For initialization parameters constructed
more dynamically there is an alternative based on maps. Some
constructors of BasicConfigurationBuilder
accept a
Map<String, Object>
. Here arbitrary initialization
parameters can be passed. The keys of the map are strings corresponding
to the names of the initialization parameters (they are equivalent to
the property names in the associated Configuration
implementations; for instance throwExceptionOnMissing
);
the map's values are the values of the parameters. No matter which
mechanism is used to define initialization parameters, it has to be
ensured that the configuration object to be constructed supports all of
these parameters; otherwise, an exception is thrown when the instance is
created.
Default Initialization Parameters
Big applications may use configuration data from multiple files or
sources. If they need special settings for all their configuration
objects, there is the issue that these settings have to be repeated
again and again for each configuration source to be created. For instance,
all files to be read may have a specific encoding, or hierarchical
configurations should use a special expression engine. In a naive
approach, all these settings have to be set on each configuration builder
used by the application.
To make life of developers easier and in compliance with the DRY (don't
repeat yourself) principle, Commons Configuration supports
default initialization parameters for configuration sources. It was
already shown how an instance of the
Parameters
class is used to create initialization parameter
objects for various types of configuration sources. In addition to the
methods for creating these objects, Parameters
also deals
with default values for them. The mechanisms are as follows:
Parameters
defines methods for registering so-called
defaults handler objects. A defaults handler is an object
implementing the
DefaultParametersHandler
interface. This interface defines a
single method which accepts a parameters object of a generic type and
populates it according to its logic. Such handlers can be registered for
specific initialization parameter interfaces.
When an initialization parameters object of a specific class is to be
created the Parameters
instance checks whether
DefaultParameterHandler
objects have been registered for this
class or its base classes. If this is the case, the matching handler
objects are invoked on the newly created parameters object - they can now
initialize it as they like.
Note that the inheritance hierarchy of parameters objects is implicitly
taken into account: A defaults handler registered for file-based
parameters is also invoked for XML parameters because XML parameters are
derived from file-based parameters and thus contain all the properties
the handler may initialize. When registering a defaults handler it is
also supported to specify the start class in the inheritance hierarchy of
parameters objects on which the handler should be executed. This makes it
possible for instance to register a handler for file-based parameters,
but define that it should be invoked only for XML parameters. That way
special file-related properties can be set for XML configurations, but
they will not apply to, say, properties configurations although they are
file-based, too. When registering default handlers the registration order
matters. Defaults handlers are invoked in the order they have been
registered; so a handler registered later can override initializations
made by a handler registered before. With these options a very
fine-grained control of initialization parameters is possible;
especially, different initialization parameters can be set for specific
configuration classes even if the parameters are of the same (base) type.
After all this theory let's come to some concrete examples. For now we
assume that we already have some DefaultParametersHandler
implementations in place that we want to register on a Parameters
object. (The next section will focus on the implementation of handlers.)
In this example the following is to be achieved:
- There is a
CommonDefaultsHandler
class setting
default initialization parameters to be applied for all configuration
sources.
- There is a defaults handler for file-based parameters which sets
the expected encoding of the file:
EncodingDefaultsHandler
.
We want this handler to be applied on XML configurations only.
- Our application will also load some properties files. For these
configuration sources we want an alternative setting of some basic
properties. This is implemented by a handler class called
PropertiesDefaultHandler
.
The code for this initialization could look as follows:
// Create the defaults handler objects.
DefaultParametersHandler<BasicBuilderParameters> basicHandler =
new CommonDefaultsHandler();
DefaultParametersHandler<FileBasedBuilderParameters> encodingHandler =
new EncodingDefaultsHandler("iso-8859-1");
DefaultParametersHandler<PropertiesBuilderParameters> propsHandler =
new PropertiesDefaultHandler();
// Register the handlers
Parameters params = new Parameters();
params.registerDefaultsHandler(BasicBuilderParameters.class, basicHandler);
params.registerDefaultsHandler(FileBasedBuilderParameters.class,
encodingHandler, XMLBuilderParameters.class);
params.registerDefaultsHandler(PropertiesBuilderParameters.class, propsHandler);
Now every time this Parameters
instance is used for the
creation of specific initialization parameters objects, the defaults
handlers registered are applied. So the produced parameters objects are
already initialized (at least partly).
This registration of defaults handlers could be done in the startup phase
of an application. The Parameters
class is thread-safe, so
an application can create and configure a single instance and use it
across all modules to create parameter objects. The actual functionality
of managing and invoking DefaultParametersHandler
objects is
implemented by the
DefaultParametersManager
class - Parameters
just
delegates to a wrapped instance. In some usage scenarios it may make sense
to use DefaultParametersManager
directly.
Defining Default Parameters Handlers
After the registration of default handlers has been discussed, it is still
open how such handlers can be created. Because the
DefaultParametersHandler
interface is very simple, it is easy
to create a specialized implementation. The following listing shows how
the EncodingDefaultsHandler
from the previous example could
be implemented:
public class EncodingDefaultsHandler
implements DefaultParametersHandler<FileBasedBuilderParameters>
{
/** The encoding to be set. */
private final String encoding;
/**
* Creates a new instance and sets the encoding.
* @param enc the encoding to be set on the parameters objects
*/
public EncodingDefaultsHandler(String enc)
{
encoding = enc;
}
@Override
public void initializeDefaults(FileBasedBuilderParameters parameters)
{
parameters.setEncoding(encoding);
}
}
The point to take is that in the initializeDefaults()
method
arbitrary initializations can be performed. In many scenarios the
implementation of a specialized DefaultParametersHandler
is
not necessary because Commons Configuration provides a pretty
generic default implementation:
CopyObjectDefaultHandler
. The name stems from the fact that a
handler is constructed from a parameters object to be used as reference.
In the initializeDefaults()
method the handler copies all
properties of this reference object onto the object to be initialized.
So all a developer needs to do is creating a parameters object of the
correct type, initializing all desired properties, and passing this object to
a newly created CopyObjectDefaultHandler
object. Let's
explore how the EncodingDefaultsHandler
class discussed
previously class can be replaced by CopyObjectDefaultHandler
:
Parameters params = new Parameters();
// Create a file-based parameters object to be used as copy source
FileBasedBuilderParameters encParams =
params.fileBased().setEncoding("iso-8859-1");
// Perform handler registration with a copy handler
params.registerDefaultsHandler(FileBasedBuilderParameters.class,
new CopyObjectDefaultHandler(encParams), XMLBuilderParameters.class);
So this fragment has the same effect (regarding the initialization of the
encoding property) as the example using the custom
EncodingDefaultsHandler
class - but without the need to
provide a custom DefaultParametersHandler
implementation.
Because of the flexibility of CopyObjectDefaultHandler
custom implementations are probably only required for initializations
that depend on conditional logic.
This completes the description of the builder concept in Commons
Configuration and the BasicConfigurationBuilder
base
class. Following chapters will deal with specialized builders and
explain the extended functionality they provide to the user of this
library.