Events
Many Java libraries support the observer pattern to send
notifications about state changes to registered observers. The domain
configuration data has also some important use cases for such
notifications. For instance, an application may want to be notified when
certain changes on configuration data are done or when a configuration
file was modified by an external source. For such requirements
Commons Configuration offers a powerful event mechanism.
Event Sources and Listeners
In Commons Configuration, there is a central interface for all
objects that can generate events:
EventSource
. Here methods for adding and removing event listeners
are defined with the following signatures:
<T extends Event> void addEventListener(EventType<T> eventType,
EventListener<? super T> listener);
<T extends Event> boolean removeEventListener(EventType<T> eventType,
EventListener<? super T> listener);
Users who are familiar with JavaFX will recognize some similarities to
the event mechanism used in this UI library. In the generation of
notifications the following components are involved:
- An event object which contains all information about a
specific change which has happened.
- Each event is associated with a specific event type. The
event type also determines the class of the event. For different kinds
of notifications, different event classes exist which also define their
own specific set of properties.
- An event listener which is invoked with an event object when
something happens for which it has been registered.
The type parameters in the methods of the EventSource
interface ensure a type-safe registration of event listeners. As we will
see in the next section, events are organized in a logic hierarchy. An
event listener has to implement the
EventListener
interface. This interface has a type parameter
for the event type which can be processed. The listener can process events
of this specific type and also events derived from this type. So listeners
can be registered for very generic events, but also for specific ones.
This allows for filtering of events in a pretty natural way. The
EventListener
interface defines a single method
onEvent()
which expects an event of the type defined by the
generic parameter of the interface.
Commons Configuration provides the following implementations of
the Event Source
interface:
- Configuration objects
- Each configuration allows registering event listeners and generates
events when it is updated.
- Configuration builders
- A typical configuration builder sends out events when its managed
configuration becomes invalid or when a new managed instance was
created.
- Reloading controllers
- Here events are generated when a change in a monitored configuration
source was detected.
In the following sections these event sources are discussed in more
detail. But first we have to elaborate a bit more on the hierarchical
nature of events and how this is related to event listeners.
The Hierarchy of Events
All events generated by components in Commons Configuration are
derived from the
Event
class. This base class is pretty simple. It has a
property for the source - inherited from the java.util.EventObject
super class - and an event type. The event type can be used for instance
in an event listener that has been registered for multiple events to
find out what actually happened.
Event types are an interesting concept. They are represented by the
EventType
class. At first, an event type has a type parameter
which associates it with a specific event class. This parameter is checked
by the compiler to validate event listener registrations via the methods
of the EventSource
interface. Second, an event type can have
a super type. With this information event types actually form a logic
hierarchy. This is taken into account to find out which event listeners
have to be invoked when an event of a specific type is received.
As an example, below is an excerpt of the hierarchy of event types for
events related to configuration updates (the full hierarchy is described
in a later section):
- Event.ANY
- ConfigurationEvent.ANY
- ConfigurationEvent.ADD_PROPERTY
- ConfigurationEvent.SET_PROPERTY
- ...
An EventListener<ConfigurationEvent>
can be
registered for the generic event type ConfigurationEvent.ANY
or for one of the specific event types (like ADD_PROPERTY
or
SET_PROPERTY
). If it has been registered for the generic
type, it is triggered for all events whose type is derived from this
generic type. For instance, it would be called for an event of type
ConfigurationEvent.ADD_PROPERTY
. In contrast, if a specific
event type is used for the event listener registration, only events of
this type trigger this listener. The following code fragment shows how
an event listener is registered at a configuration object for a specific
event type:
EventListener<ConfigurationEvent> listener = new MyListner();
config.addEventListener(ConfigurationEvent.ADD_PROPERTY, listener);
This listener would be called for events of type ADD_PROPERTY
,
but not for SET_PROPERTY
events. Note that it would not be
possible to register this event listener for the base type
Event.ANY
. Because the listener is of type
EventListener<ConfigurationEvent>
its onEvent()
method expects a ConfigurationEvent
; so the basic type
Event
is not sufficient to invoke this listener.
Event types are defined as constants in the event classes they are related
to. Typically, there are multiple event type constants per event class
because an event object (viewed as a container of related properties) can
occur in multiple concrete contexts or be used for different operations.
Configuration Update Events
All configuration implementations derived from
AbstractConfiguration
can generate events of type
ConfigurationEvent
when they are manipulated. In addition to
the properties inherited from the Event
base class, a
configuration event contains all information available about the
modification:
- If available, the name of the property whose modification caused the
event.
- If available, the value of the property that caused this event.
- A flag whether this event was generated before or after the update
of the source configuration. A modification of a configuration typically
causes two events: one event before and one event after the modification
is performed. This allows event listeners to react at the correct point
of time.
Depending on the concrete event type not all of this data may be available.
ConfigurationEvent
defines a number of EventType
constants for the possible types of such an event. These constants
correspond to the methods available for updating a configuration. The
hierarchy of these event types is listed below, together with a
description of the specific types and their available properties:
- Event.ANY
- ConfigurationEvent.ANY is a placeholder for all types
of configuration update events. A listener registered for this
event type is actually invoked for each manipulation of the source
configuration.
- ADD_PROPERTY A property was added to the
configuration. The event object contains the name of the affected
property and the value passed to the
addProperty()
method.
- SET_PROPERTY The value of a property was
changed. The event contains the name of the affected property and
its new value.
- CLEAR_PROPERTY A property was removed from the
configuration. In the event the name of the removed property is
stored.
- CLEAR The whole configuration was cleared. The
event object does not contain any additional information.
- ANY_HIERARCHICAL This is a common super type
for all events specific to hierarchical configurations. The event
types derived from this type correspond to the special update
methods supported by hierarchical configurations.
- ADD_NODES A collection of nodes was added to
a hierarchical configuration. The event contains the key passed
to the
addNodes()
method and the collection of new
nodes as value.
- CLEAR_TREE This event is triggered by the
clearTree()
method. It contains the key of the
sub tree which was removed; it has no value.
- SUBNODE_CHANGED This event indicates that a
SubnodeConfiguration
created by this configuration
was changed. The value property of the event object
contains the original event object as it was sent by the subnode
configuration.
After all the theory about the different event types, let's come to a
concrete example. Implementing an event listener for configuration
events is quite easy. To prove this and as a kind of "Hello world" use
case, we are going to define an event listener which logs all received
configuration events to the console. The class could look as follows:
import org.apache.commons.configuration2.event.ConfigurationEvent;
import org.apache.commons.configuration2.event.EventListener;
public class ConfigurationLogListener implements EventListener<ConfigurationEvent>
{
@Override
public void onEvent(ConfigurationEvent event)
{
if (!event.isBeforeUpdate())
{
// only display events after the modification was done
System.out.println("Received event!");
System.out.println("Type = " + event.getEventType());
if (event.getPropertyName() != null)
{
System.out.println("Property name = " + event.getPropertyName());
}
if (event.getPropertyValue() != null)
{
System.out.println("Property value = " + event.getPropertyValue());
}
}
}
}
Now an instance of this event listener class has to be registered at a
configuration object (Note: in a later section we will learn how event
listeners can be added to configurations via their associated builders;
this is the preferred way):
AbstractConfiguration config = ... // somehow create the configuration
EventListener<ConfigurationEvent> listener = new ConfigurationLogListener();
config.addEventListener(ConfigurationEvent.ANY, listener);
...
config.addProperty("newProperty", "newValue"); // will fire an event
Because our implementation is a very generic event listener it has been
registered for all kinds of configuration update events - the event type
ConfigurationEvent.ANY
was used. Now consider the case that
we only want to log events about cleared properties. This can be easily
achieved - without having to modify the event listener implementation -
by just changing the registration code in the following way:
config.addEventListener(ConfigurationEvent.CLEAR_PROPERTY, listener);
...
config.addProperty("newProperty", "newValue"); // will NOT fire an event
config.clearProperty("removedProperty"); // but this one will
Configuration Error Events
Some implementations of the Configuration
interface operate
on underlying storages that can throw exceptions on each property access.
As an example consider
DatabaseConfiguration
: this configuration class issues an SQL
statement for each accessed property, which can potentially cause a
SQLException
.
Because the Configuration
interface does not define checked
exceptions for the methods which access properties such exceptions
thrown from the underlying property store have to be handled somehow.
One way would be to re-throw them as runtime exceptions. This is
possible, a description how to enable this feature can be found in the
Tips and
Tricks chapter. An alternative way of dealing with such exceptions is
to register an event listener for error events.
When a configuration implementation encounters an exception on accessing
its data it generates an event of class
ConfigurationErrorEvent
. This event class has similar properties
as
ConfigurationEvent
. Especially the name and the value of the
property which was accessed when the error occurred can be retrieved.
In addition, there is the getCause()
method which returns the
exception causing this event.
ConfigurationErrorEvent
defines some new event type constants.
They build up the following hierarchy:
- Event.ANY
- ConfigurationErrorEvent.ANY The common super type
of all error events. An event listener registered for this type can
be sure to be notified for all kind of error events.
- ConfigurationErrorEvent.READ A sub type indicating
that the error occurred while reading a property.
- ConfigurationErrorEvent.WRITE A sub type
indicating that the error occurred on an update operation. In this
case, an additional property of the event can be used to find out
which operation was performed:
errorOperationType
returns an EventType
object corresponding to the failed
update method (e.g. ConfigurationEvent.ADD_PROPERTY if a
property could not be added).
We could now continue the example from the previous section and make our
sample logging event listener also capable of tracing error events.
However, this would not earn us that much. There is no principle difference
in the handling of configuration update events and error events;
therefore, there is nothing new to learn. If the logging functionality
should be implemented in a single listener class, the only tricky part is
that ConfigurationEvent
and ConfigurationErrorEvent
do not stand in a super/extends relation with each other; they are both
derived from the type Event.ANY. So a generic logging listener
would have to be of type EventListener<Event>
, and it
would have to use the event's type to determine how to handle this
concrete event. Creating a separate event listener class for logging error
events is certainly easier.
Note: AbstractConfiguration
already implements a mechanism
for writing internal errors to a logger object: It has the
addErrorLogListener()
method that can be called from derived
classes to register a listener that will output all occurring internal
errors using the default logger. Configuration implementations like
DatabaseConfiguration
that are affected by potential internal
errors call this method during their initialization. So the default
behavior of Commons Configuration for these classes is to
catch occurring exceptions and log them. However, by
registering specific error listeners it is possible for clients to
implement their own handling of such errors.
Configuration Builders and Events
Configuration builders also offer
support for the event mechanism in Commons Configuration. There
are the following aspects:
- Configuration builders can generate events themselves; for instance,
events are fired when a managed configuration is created or reset.
- Configuration builders can be used to register event listeners at
managed configurations. Although it is possible to register event
listeners directly at a configuration object obtained from a builder,
this is not necessarily the best option. Consider for instance the case
that a reloading configuration builder
is used. When a need for a reload is detected the managed configuration
is replaced by a new instance. Event listener registrations directly
done at the old instance are no longer active. In contrast, for event
listener registrations done via the configuration builder, the builder
ensures that all listeners are automatically added to a newly created
configuration instance and removed from an obsolete instance.
For the events generated by a configuration builder a new event class is
introduced:
ConfigurationBuilderEvent
. This class extends the base
Event
class, but does not define any new properties. However,
it overrides the getSource()
method to return an instance
of
ConfigurationBuilder
. This event class is used to send
notifications which do not require additional information; the event itself
is sufficient to find out what has happened. Derived from
ConfigurationBuilderEvent
is the
ConfigurationBuilderResultCreatedEvent
event class. It is used
to indicate that a new managed configuration object was created. A
reference to this object can be queried using the
getConfiguration()
method.
The full hierarchy of event types related to configuration builders is
shown below. As usual, EventType
constants are defined in
the event classes:
- Event.ANY
- ConfigurationBuilderEvent.ANY A common super type
for all events produced by a configuration builder. An event listener
registered for this event type receives all notifications about a
configuration builder.
- ConfigurationBuilderEvent.RESET The managed
configuration of a builder has been reset. This means that the
configuration is now obsolete. A new object is created the next time
the builder's
getConfiguration()
method is called.
- ConfigurationBuilderEvent.CONFIGURATION_REQUEST
This event is generated when the builder's
getConfiguration()
method is entered, but before the managed configuration is actually
accessed. This is an opportunity to perform some manipulations which
might also affect the managed configuration. One use case is to
trigger a reloading check at this point of time. If it turns out that
a reload is required, the managed configuration gets invalidated and
is replaced by a new object - which is then directly returned by
the current method call.
- ConfigurationBuilderResultCreatedEvent.RESULT_CREATED
A new managed configuration object has been created. This event is
fired initially on first invocation of the
getConfiguration()
method, and then again after the managed configuration has been reset
and created anew. A reference to the new configuration object can be
obtained from the event so that specific initializations can be
performed.
As an example of how to use event listeners for builders we are going to
make use of the RESULT_CREATED event type: every time a managed
configuration is created, a special property is set with the creation
date. This information can then be evaluated by client code. To achieve
this, a special event listener class is created:
public class NewConfigurationInitListener
implements EventListener<ConfigurationBuilderResultCreatedEvent>
{
@Override
public void onEvent(ConfigurationBuilderResultCreatedEvent event)
{
event.getConfiguration().addProperty("creationDate", new Date());
}
}
This is pretty straight-forward. Now an instance of this listener class
has to be registered at the configuration builder in question. We can
directly use the method from the EventSource
interface:
ConfigurationBuilder<Configuration> builder = ...;
builder.addEventListener(ConfigurationBuilderResultCreatedEvent.RESULT_CREATED,
new NewConfigurationInitListener());
Now every time the builder creates a new managed configuration object,
the creation date is automatically stored in a property. This may be
indeed useful, for instance in a scenario supporting automatic reloads.
Here the application can determine when the configuration was updated
the last time.
All event listeners passed to a configuration builder's
addEventListener()
method are not only registered at the
builder itself but are also propagated to the managed configuration
object. This is the recommended way of adding listeners for events
generated by the builder's managed configuration. The builder ensures that
- even if the configuration instance is replaced by another one - all
listeners are correctly registered and unregistered. In section
Configuration Update Events
we created a simple configuration event listener which just logged
occurring update events. We can now show how this listener is
registered via the configuration builder:
ConfigurationBuilder<Configuration> builder = ...;
builder.addEventListener(ConfigurationEvent.ANY,
new ConfigurationLogListener());
As can be seen, the same pattern is always used for all kinds of event
listeners. Configuration builders offer a fluent API for setting up
builder objects and setting required initialization parameters. This
also includes the registration of event listeners. For this purpose the
EventListenerParameters
class has been created. It allows setting an
arbitrary number of event listeners using method chaining. An instance
configured with the event listeners to be registered can then be passed
to the configuration builder's configure()
method like
normal builder parameters objects. Here is an example how a configuration
builder for an XML configuration source can be constructed and initialized
- including an event listener - in a single expression:
Parameters params = new Parameters();
FileBasedConfigurationBuilder<Configuration> builder =
new FileBasedConfigurationBuilder<Configuration>(XMLConfiguration.class)
.configure(
// Regular builder initialization parameters
params.fileBased()
.setFile(new File("config.xml")),
// Event listener registration
new EventListenerParameters()
.addEventListener(ConfigurationEvent.ANY, new ConfigurationLogListener())
);
Configuration config = builder.getConfiguration();
Here the feature is used that the configure()
method takes an
arbitrary number of initialization parameter objects.
EventListenerParameters()
is a special parameters
implementation which takes care of event listener registrations, but does
not set any other initialization properties.
Reloading Events
Another source of events is the
ReloadingController
class which was introduced in the chapter
Automatic Reloading of Configuration Sources.
A reloading controller generates events of type
ReloadingEvent
whenever it detects the need for a reloading
operation. Currently, there is only a single event type for reloading
events: ReloadingEvent.ANY which is directly derived from the
base type Event.ANY. The usage for registering listeners for
this event is analogous to the other event sources covered so far.
In a typical usage scenario a mechanism is set up which periodically
triggers a reloading controller to perform a reloading check. If one of
these checks detects a change in a monitored configuration source, a
ReloadingEvent
is fired to all registered listeners. The
message of this event is basically, that updated configuration information
is now available. An event listener for reloading events may react
accordingly, e.g. obtain the updated configuration object from its
builder and trigger some updates.