Apache Commons logo Commons Configuration

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.