Apache Tamaya — Extension: Events
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:
ConfigEventpublic 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 ConfigChangeListenerpublic 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:
-
Manually by calling ConfigEventManager.addListener(new MyConfigChangeListener())
-
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 ConfigurationConfiguration frozenConfig = FrozenConfiguration.of(ConfigurationProvider.getConfiguration());
-
and similarly for a PropertySource:
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
-
accessing the current Configuration and its ConfigurationContext
-
checking if the event is affecting the current ConfigurationContext.
-
in the case the current context is affected, based on the current ConfigurationContext a new context is created, whereas
-
all PropertySources provided by this provider implementation type are removed.
-
the new PropertySources loaded are added.
-
-
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: ConfigEventSpipublic 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); }