A hierarchical composite configuration class.
This class maintains a list of configuration objects, which can be added
using the divers
addConfiguration() methods. After that the
configurations can be accessed either by name (if one was provided when the
configuration was added) or by index. For the whole set of managed
configurations a logical node structure is constructed. For this purpose a
org.apache.commons.configuration.tree.NodeCombinerobject can be set. This makes it possible to specify different algorithms for
the combination process.
The big advantage of this class is that it creates a truly hierarchical
structure of all the properties stored in the contained configurations - even
if some of them are no hierarchical configurations per se. So all enhanced
features provided by a hierarchical configuration (e.g. choosing an
expression engine) are applicable.
The class works by registering itself as an event listener at all added
configurations. So it gets notified whenever one of these configurations is
changed and can invalidate its internal node structure. The next time a
property is accessed the node structure will be re-constructed using the
current state of the managed configurations. Note that, depending on the used
NodeCombiner, this may be a complex operation.
Because of the way a
CombinedConfiguration is working it has
more or less view character: it provides a logic view on the configurations
it contains. In this constellation not all methods defined for hierarchical
configurations - especially methods that update the stored properties - can
be implemented in a consistent manner. Using such methods (like
addProperty(), or
clearProperty() on a
CombinedConfiguration is not strictly forbidden, however,
depending on the current
NodeCombiner and the involved
properties, the results may be different than expected. Some examples may
illustrate this:
- Imagine a
CombinedConfiguration cc containing
two child configurations with the following content:
user.properties
gui.background = blue
gui.position = (10, 10, 400, 200)
default.properties
gui.background = black
gui.foreground = white
home.dir = /data
As a
NodeCombiner a
org.apache.commons.configuration.tree.OverrideCombineris used. This combiner will ensure that defined user settings take precedence
over the default values. If the resulting
CombinedConfigurationis queried for the background color,
blue will be returned
because this value is defined in
user.properties. Now
consider what happens if the key
gui.background is removed
from the
CombinedConfiguration:
cc.clearProperty("gui.background");
Will a
cc.containsKey("gui.background") now return false?
No, it won't! The
clearProperty() operation is executed on the
node set of the combined configuration, which was constructed from the nodes
of the two child configurations. It causes the value of the
background node to be cleared, which is also part of the first
child configuration. This modification of one of its child configurations
causes the
CombinedConfiguration to be re-constructed. This
time the
OverrideCombiner cannot find a
gui.background property in the first child configuration, but
it finds one in the second, and adds it to the resulting combined
configuration. So the property is still present (with a different value now).
-
addProperty() can also be problematic: Most node
combiners use special view nodes for linking parts of the original
configurations' data together. If new properties are added to such a special
node, they do not belong to any of the managed configurations and thus hang
in the air. Using the same configurations as in the last example, the
statement
addProperty("database.user", "scott");
would cause such a hanging property. If now one of the child configurations
is changed and the
CombinedConfiguration is re-constructed,
this property will disappear! (Add operations are not problematic if they
result in a child configuration being updated. For instance an
addProperty("home.url", "localhost"); will alter the second
child configuration - because the prefix home is here already
present; when the
CombinedConfiguration is re-constructed,
this change is taken into account.)
Because of such problems it is recommended to perform updates only on the
managed child configurations.
Whenever the node structure of a
CombinedConfiguration becomes
invalid (either because one of the contained configurations was modified or
because the
invalidate() method was directly called) an event
is generated. So this can be detected by interested event listeners. This
also makes it possible to add a combined configuration into another one.
Implementation note: Adding and removing configurations to and from a
combined configuration is not thread-safe. If a combined configuration is
manipulated by multiple threads, the developer has to take care about
properly synchronization.