Tamaya Events (Extension Module)

Overview

Tamaya Events is an extension module. Refer to the extensions documentation for further details about modules.

Tamaya Events provides an abstraction for events like change events, when configuration has bee changed.

Compatibility

The module is based on Java 7, so it can be used with Java 7 and beyond.

Installation

To benefit from configuration event support you only must add the corresponding dependency to your module:

<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-events</artifactId>
  <version>{tamayaVersion}</version>
</dependency>

Core Architecture

The core of the module are the ConfigEventListener interface and the ConfigEvent class, which defines an abstraction for event handling and observation:

ConfigEvent
public final interface ConfigEvent<T> {

    Class<T> getResourceType();
    T getResource();
    String getVersion();
    long getTimestamp();
}

// @FunctionalInterface
public interface ConfigEventListener {

    void onConfigEvent(ConfigEvent<?> event);

}

This mechanism can now be used to propagate configuration changes to all interested stakeholders. Hereby the payloed can be basically arbitrary as long as it implements the ConfigEvent interface. The next sections give more details on the the provided event implementations and abstractions that are used to implement such features.

Modelling Configuration Changes

This module provides a serializable and thread-safe abstraction modlling a configuration change. A change hereby may be

  • additional configuration entries

  • removed configuration entries

  • changes on entries

This is also reflected in the ChangeType enum

public enum ChangeType {
    NEW,
    DELETED,
    UPDATED,
}

This enum type is used within the ConfigurationChange class, which implements the event sent for a changed Configuration:

public final class ConfigurationChange implements ConfigEvent<Configuration>, Serializable{

    public static ConfigurationChange emptyChangeSet(Configuration configuration);

    @Override
    public Configuration getResource();
    @Override
    public Class<Configuration> getResourceType();
    @Override
    public String getVersion();
    @Override
    public long getTimestamp();
    @Override
    public long getTimestamp();

    // Event specific methods

    public Collection<PropertyChangeEvent> getChanges();
    public int getRemovedSize();
    public int getAddedSize();
    public int getUpdatedSize();

    public boolean isKeyAffected(String key);
    public boolean isRemoved(String key);
    public boolean isAdded(String key);
    public boolean isUpdated(String key);
    public boolean containsKey(String key);
    public boolean isEmpty();
}

New instances of this class hereby are created using a fluent builder:

Configuration config = ...;
ConfigurationChange change = ConfigurationChangeBuilder.of(config)
  .addChange("MyKey", "newValue")
  .removeKeys("myRemovedKey").build();

Also it is possible to directly compare 2 instances of configurations to create a matching ConfigurationChange instance:

Comparing 2 configurations
-------------------------------------------------------
Configuration config = ...;
Configuration changedConfig = ...;
ConfigurationChange change = ConfigurationChangeBuilder.of(config)
  .addChanges(changedConfig).build();
-------------------------------------------------------

So a ConfigurationChange allows you to evaluate the changes on a configuration. This allows you to listen to changes and react in your client code as useful, once you encounter changes that are relevant to you, e.g. by reconfiguring your component. Of course, your code has to register itself to listen for appropriate changes by implementing a ConfigEventListener:

Implementing a ConfigChangeListener
public final class MyConfigChangeListener implements ConfigChangeListener<ConfigurationChange>{

  private Configuration config = ConfigurationProvider.getConfiguration();

  public void onConfigEvent(ConfigEvent<?> event){
     if(event.getResourceTspe()==Configuration.class){
         if(event.getConfiguration()==config){
           // do something
         }
     }
  }

}

You can register your implementation in 2 ways:

  1. Manually by calling ConfigEventManager.addListener(new MyConfigChangeListener())

  2. Automatically by registering your listener using the ServiceLoader under META-INF/services/org.apache.tamaya.events.ConfigEventListener

Modelling PropertySource Changes

Beside that a whole configuration changes, also PropertySource instance can change, e.g. by a configuration file edited on the fly. This is similarly to a ConfigurationChange reflected by the classes PropertySourceChange, PropertySourceChangeBuilder.

Modelling Configuration Context Changes

The ConfigurationContext models the container that manages all subcomponents that are used to define and evalaute a Configuration. In the case where configuration is dynamically loaded, e.g. by observing changes on a file folder, the ConfigurationContext may change, so a corresponding ConfigurationContextChange event is defined:

public final class ConfigurationContextChange implements ConfigEvent<ConfigurationContext>, Serializable{

    public static ConfigurationContextChange emptyChangeSet();

    @Override
    public ConfigurationContext getResource();
    @Override
    public Class<ConfigurationContext> getResourceType();
    @Override
    public String getVersion();
    @Override
    public long getTimestamp();

    // specific methods
    public Collection<PropertySourceChange> getPropertySourceChanges();
    public Collection<PropertySourceChange> getPropertySourceUpdates();
    public Collection<PropertySource> getRemovedPropertySources();
    public Collection<PropertySource> getAddedPropertySources();
    public Collection<PropertySource> getUpdatedPropertySources();
    public boolean isAffected(PropertySource propertySource);
    public boolean isEmpty();
}

Similar to the ConfigurationChange class you also must use a ConfigurationContextChangeBuilder to create instances of ConfigurationContextChange.

The ConfigEventManager Singleton

Main entry point of the events module is the ConfigEventManager singleton class, which provides static accessor methods to the extension’s functionality:

public final class ConfigEventManager {

    private ConfigEventManager() {}

    public static void addListener(ConfigEventListener l);
    public static <T extends ConfigEvent> void addListener(ConfigEventListener l, Class<T> eventType);
    public static void removeListener(ConfigEventListener l);
    public static <T extends ConfigEvent> void removeListener(ConfigEventListener l, Class<T> eventType);
    public static <T extends ConfigEvent>
        Collection<? extends ConfigEventListener> getListeners();
    public static <T extends ConfigEvent>
        Collection<? extends ConfigEventListener> getListeners(Class<T> type);

    public static <T> void fireEvent(ConfigEvent<?> event);
    public static <T> void fireEventAsynch(ConfigEvent<?> event);

    public static void enableChangeMonitoring(boolean enable);
    public static boolean isChangeMonitoring();
    public long getChangeMonitoringPeriod();
    public void setChangeMonitoringPeriod(long millis);

}

Looking at the methods listed above you see that there is more functionality worth to be mentioned:

  • ConfigCHangeListeners can be registered either globally or for a certain event type only.

  • ConfigEvents can be published within the same thread, or asynchronously.

Monitoring of configuration changes

The ConfigEventManager also supports active monitoring of the current configuration to trigger corresponding change events to listeners registered. This feature is activated by default, but can be deactivated optionally. Nevertheless this feature is quite handy, since regularly polling your local Configuration for any kind of changes is much more simpler than implementing change management on the PropertySource level. With this feature you can easily implement also remote property source, which can deliver different configuration based on any changes done remotedly on another node in your system. If such a change happened Tamaya identifies it and triggers corresponding +ConfigurationChange" events automatically. Similarly changes in a configuration tree, can actively identified and broadcasted to the targeting nodes automatically.

Freezing Configurations and PropertySources

Configuration instances as well as PropertySources are explicitly not required to be serializable. To enable easy serialization of these types as well as to fix a current state (e.g. for later comparison with a newly loaded instance) Tamaya allows to freeze instances of these types. Freezing hereby means

  • all key/values are read-out by calling the getProperties() method.

  • a meta data entry is added of the form [meta]frozenAt=223273777652325677, whichdefines the UTC timestamp in milliseconds when this instance was frozen.

In code this is done easily as follows:

Freezing the current Configuration
Configuration frozenConfig = FrozenConfiguration.of(ConfigurationProvider.getConfiguration());
  1. and similarly for a PropertySource:

Freezing the current Configuration
PropertySource frozenSource = FrozenPropertySource.of(ConfigurationProvider.getConfiguration());

Modelling of an observing PropertySourceProvider.

In Tamaya configuration data is provided by instances of PropertySource, which in case of a configuration directory may be provided by an implementation of PropertySourceProvider, which produces one PropertySource (at least) per file detected. The events module provides a base provider implementation that

  • observes all changes in a Path

  • tries to reevaluate corresponding resources based on the ConfigurationFormats supported.

  • it creates an instance of ConfigurationContextChange reflecting the changed ConfigurationContext and triggers this event by calling ConfigEventManager.fireEvent(contextChange);.

Additionally this module registers an instance of ConfigEventListener<ConfigurationContextChange>+, which listenes to these events. If such an event is triggered the listener tries to apply the changes by

  1. accessing the current Configuration and its ConfigurationContext

  2. checking if the event is affecting the current ConfigurationContext.

  3. in the case the current context is affected, based on the current ConfigurationContext a new context is created, whereas

    1. all PropertySources provided by this provider implementation type are removed.

    2. the new PropertySources loaded are added.

  4. Finally the listener tries to apply the new ConfigurationContext by calling the corresponding API methods of the ConfigurationProvider:

try {
    ConfigurationProvider.setConfigurationContext(newContext);
} catch (Exception e) {
    LOG.log(Level.INFO, "Failed to update the current ConfigurationContext due to config model changes", e);
}

So if the current ConfigurationProvider supports reloading of the current ConfigurationContext this will apply the changes to the current Configuration. Otherwise the change is logged, but no further actions are taken.

SPIs

This component also defines an additional SPI, which allows to adapt the implementation of the main ConfigEventManager singleton. This enables, for example, using external eventing systems, such as CDI, instead of the default provided simple SE based implementation. As normal, implementation mus be registered using the current ServiceContext active, by default using the Java ServiceLoader mechanism.

SPI: ConfigEventSpi
public interface ConfigEventManagerSpi {

        <T> void addListener(ConfigEventListener l);
        <T extends ConfigEvent> void addListener(ConfigEventListener l, Class<T> eventType);
        void removeListener(ConfigEventListener l);
        <T extends ConfigEvent> void removeListener(ConfigEventListener l, Class<T> eventType);
        Collection<? extends ConfigEventListener> getListeners();
        Collection<? extends ConfigEventListener> getListeners(Class<? extends ConfigEvent> eventType);

        void fireEvent(ConfigEvent<?> event);
        void fireEventAsynch(ConfigEvent<?> event);

        long getChangeMonitoringPeriod();
        void setChangeMonitoringPeriod(long millis);
        boolean isChangeMonitorActive();
        void enableChangeMonitor(boolean enable);
}
Last updated 2016-07-13 23:25:59 +02:00

Back to top

Version: 0.3-incubating-SNAPSHOT. Last Published: 2016-07-13.

Reflow Maven skin by Andrius Velykis.