TAMAYA-175 Moved the events module to the directory events after extracting it for the Tamaya main repository.
Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/commit/89223dcd Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/tree/89223dcd Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/diff/89223dcd Branch: refs/heads/master Commit: 89223dcd49f9045f01ed04b803add7cabe746cbd Parents: 9bf98fe Author: Oliver B. Fischer <ple...@apache.org> Authored: Tue Sep 27 00:17:11 2016 +0200 Committer: Oliver B. Fischer <ple...@apache.org> Committed: Fri Sep 30 21:29:37 2016 +0200 ---------------------------------------------------------------------- events/pom.xml | 100 +++++++ .../org/apache/tamaya/events/ChangeType.java | 31 +++ .../org/apache/tamaya/events/ConfigEvent.java | 55 ++++ .../tamaya/events/ConfigEventListener.java | 31 +++ .../tamaya/events/ConfigEventManager.java | 186 +++++++++++++ .../tamaya/events/ConfigurationChange.java | 218 +++++++++++++++ .../events/ConfigurationChangeBuilder.java | 274 +++++++++++++++++++ .../events/ConfigurationContextChange.java | 210 ++++++++++++++ .../ConfigurationContextChangeBuilder.java | 174 ++++++++++++ .../tamaya/events/FrozenConfiguration.java | 194 +++++++++++++ .../tamaya/events/FrozenPropertySource.java | 126 +++++++++ .../tamaya/events/PropertySourceChange.java | 242 ++++++++++++++++ .../events/PropertySourceChangeBuilder.java | 263 ++++++++++++++++++ .../tamaya/events/delta/package-info.java | 23 ++ .../folderobserver/FileChangeListener.java | 144 ++++++++++ .../folderobserver/FileChangeObserver.java | 33 +++ .../ObservingPropertySourceProvider.java | 209 ++++++++++++++ .../events/folderobserver/package-info.java | 24 ++ .../internal/DefaultConfigChangeObserver.java | 111 ++++++++ .../internal/DefaultConfigEventManagerSpi.java | 202 ++++++++++++++ ...faultConfigurationContextChangeListener.java | 74 +++++ .../events/internal/LoggingConfigListener.java | 40 +++ .../tamaya/events/internal/package-info.java | 22 ++ .../org/apache/tamaya/events/package-info.java | 24 ++ .../tamaya/events/spi/BaseConfigEvent.java | 69 +++++ .../events/spi/ConfigEventManagerSpi.java | 128 +++++++++ .../apache/tamaya/events/spi/package-info.java | 23 ++ ...org.apache.tamaya.events.ConfigEventListener | 19 ++ ...ache.tamaya.events.spi.ConfigEventManagerSpi | 19 ++ .../events/ChangeableGlobalPropertySource.java | 62 +++++ .../ChangeableThreadLocalPropertySource.java | 57 ++++ .../tamaya/events/ConfigEventManagerTest.java | 66 +++++ .../tamaya/events/FrozenPropertySourceTest.java | 106 +++++++ .../tamaya/events/ObservedConfigTest.java | 69 +++++ .../tamaya/events/RandomPropertySource.java | 66 +++++ .../org/apache/tamaya/events/SimpleEvent.java | 32 +++ .../apache/tamaya/events/TestConfigView.java | 156 +++++++++++ .../tamaya/events/TestObservingProvider.java | 92 +++++++ .../events/delta/ConfigurationChangeTest.java | 163 +++++++++++ .../delta/ConfigurationContextChangeTest.java | 138 ++++++++++ .../events/delta/PropertySourceChangeTest.java | 209 ++++++++++++++ .../DefaultConfigEventManagerSpiTest.java | 66 +++++ ...org.apache.tamaya.events.ConfigEventListener | 19 ++ .../org.apache.tamaya.spi.PropertySource | 19 ++ ...org.apache.tamaya.spi.PropertySourceProvider | 19 ++ events/src/test/resources/data/test1.properties | 20 ++ .../src/test/resources/data/test1b.properties | 20 ++ events/src/test/resources/data/test2.properties | 20 ++ events/src/test/resources/data/test3.properties | 20 ++ events/src/test/resources/test.properties | 21 ++ pom.xml | 100 ------- .../org/apache/tamaya/events/ChangeType.java | 31 --- .../org/apache/tamaya/events/ConfigEvent.java | 55 ---- .../tamaya/events/ConfigEventListener.java | 31 --- .../tamaya/events/ConfigEventManager.java | 186 ------------- .../tamaya/events/ConfigurationChange.java | 218 --------------- .../events/ConfigurationChangeBuilder.java | 274 ------------------- .../events/ConfigurationContextChange.java | 210 -------------- .../ConfigurationContextChangeBuilder.java | 174 ------------ .../tamaya/events/FrozenConfiguration.java | 194 ------------- .../tamaya/events/FrozenPropertySource.java | 126 --------- .../tamaya/events/PropertySourceChange.java | 242 ---------------- .../events/PropertySourceChangeBuilder.java | 263 ------------------ .../tamaya/events/delta/package-info.java | 23 -- .../folderobserver/FileChangeListener.java | 144 ---------- .../folderobserver/FileChangeObserver.java | 33 --- .../ObservingPropertySourceProvider.java | 209 -------------- .../events/folderobserver/package-info.java | 24 -- .../internal/DefaultConfigChangeObserver.java | 111 -------- .../internal/DefaultConfigEventManagerSpi.java | 202 -------------- ...faultConfigurationContextChangeListener.java | 74 ----- .../events/internal/LoggingConfigListener.java | 40 --- .../tamaya/events/internal/package-info.java | 22 -- .../org/apache/tamaya/events/package-info.java | 24 -- .../tamaya/events/spi/BaseConfigEvent.java | 69 ----- .../events/spi/ConfigEventManagerSpi.java | 128 --------- .../apache/tamaya/events/spi/package-info.java | 23 -- ...org.apache.tamaya.events.ConfigEventListener | 19 -- ...ache.tamaya.events.spi.ConfigEventManagerSpi | 19 -- .../events/ChangeableGlobalPropertySource.java | 62 ----- .../ChangeableThreadLocalPropertySource.java | 57 ---- .../tamaya/events/ConfigEventManagerTest.java | 66 ----- .../tamaya/events/FrozenPropertySourceTest.java | 106 ------- .../tamaya/events/ObservedConfigTest.java | 69 ----- .../tamaya/events/RandomPropertySource.java | 66 ----- .../org/apache/tamaya/events/SimpleEvent.java | 32 --- .../apache/tamaya/events/TestConfigView.java | 156 ----------- .../tamaya/events/TestObservingProvider.java | 92 ------- .../events/delta/ConfigurationChangeTest.java | 163 ----------- .../delta/ConfigurationContextChangeTest.java | 138 ---------- .../events/delta/PropertySourceChangeTest.java | 209 -------------- .../DefaultConfigEventManagerSpiTest.java | 66 ----- ...org.apache.tamaya.events.ConfigEventListener | 19 -- .../org.apache.tamaya.spi.PropertySource | 19 -- ...org.apache.tamaya.spi.PropertySourceProvider | 19 -- src/test/resources/data/test1.properties | 20 -- src/test/resources/data/test1b.properties | 20 -- src/test/resources/data/test2.properties | 20 -- src/test/resources/data/test3.properties | 20 -- src/test/resources/test.properties | 21 -- 100 files changed, 4708 insertions(+), 4708 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/pom.xml ---------------------------------------------------------------------- diff --git a/events/pom.xml b/events/pom.xml new file mode 100644 index 0000000..d274633 --- /dev/null +++ b/events/pom.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy current the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-extensions</artifactId> + <version>0.3-incubating-SNAPSHOT</version> + </parent> + + <artifactId>tamaya-events</artifactId> + <name>Apache Tamaya Modules - Event and dynamic Update Extensions</name> + <packaging>bundle</packaging> + + <properties> + <jdkVersion>1.7</jdkVersion> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-functions</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-spisupport</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package> + org.apache.tamaya.events, + org.apache.tamaya.events.delta, + org.apache.tamaya.events.folderobserver, + org.apache.tamaya.events.spi + </Export-Package> + <Private-Package> + org.apache.tamaya.events.internal + </Private-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + + +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ChangeType.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ChangeType.java b/events/src/main/java/org/apache/tamaya/events/ChangeType.java new file mode 100644 index 0000000..2059017 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ChangeType.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +/** + * Enum describing the type of configuration change. + */ +public enum ChangeType { + /** Configuration hase been added. */ + NEW, + /** Configuration hase been removed. */ + DELETED, + /** Configuration hase been changed. */ + UPDATED, +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigEvent.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigEvent.java b/events/src/main/java/org/apache/tamaya/events/ConfigEvent.java new file mode 100644 index 0000000..5a713d7 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigEvent.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + + +/** + * Event that contains a set current changes that were applied or could be applied. + * @param <T> the resource type. + */ +public interface ConfigEvent<T>{ + + /** + * Access the type of resource. This allows to easily determine the resource an event wants to observe. + * @return the resource type. + */ + Class<T> getResourceType(); + + /** + * Get the underlying property provider/configuration. + * @return the underlying property provider/configuration, never null. + */ + T getResource(); + + /** + * Get the version relative to the observed resource. The version is required to be unique for + * each change emmitted for a resource. There is no further requirement how this uniqueness is + * modelled, so returning a UUID is a completely valid strategy. + * @return the base version. + */ + String getVersion(); + + /** + * Get the timestamp in millis from the current epoch. it is expected that the timestamp and the version are unique to + * identify a changeset. + * @return the timestamp, when this changeset was created. + */ + long getTimestamp(); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigEventListener.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigEventListener.java b/events/src/main/java/org/apache/tamaya/events/ConfigEventListener.java new file mode 100644 index 0000000..7fb32c8 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigEventListener.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +/** + * Interface to be implemented for listening on changes on {@link org.apache.tamaya.Configuration} instances. + */ +public interface ConfigEventListener { + /** + * Called if an event occurred. + * @param event the event, not null. + */ + void onConfigEvent(ConfigEvent<?> event); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigEventManager.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigEventManager.java b/events/src/main/java/org/apache/tamaya/events/ConfigEventManager.java new file mode 100644 index 0000000..f6bd3da --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigEventManager.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.events.spi.ConfigEventManagerSpi; +import org.apache.tamaya.spi.ServiceContextManager; + +import java.util.Collection; + +/** + * Singleton accessor for accessing the event support component that distributes change events of + * {@link org.apache.tamaya.spi.PropertySource} and {@link org.apache.tamaya.Configuration}. + */ +public final class ConfigEventManager { + /** + * The backing SPI. + */ + private static final ConfigEventManagerSpi SPI = ServiceContextManager.getServiceContext() + .getService(ConfigEventManagerSpi.class); + + /** + * Private singleton constructor. + */ + private ConfigEventManager() { + } + + /** + * Adds a Config listener that listens to all kind of {@link ConfigEvent}. + * @param l the listener not null. + */ + public static void addListener(ConfigEventListener l) { + if (SPI == null) { + throw new ConfigException("No SPI registered for " + + ConfigEventManager.class.getName()); + } + SPI.addListener(l); + } + + /** + * Adds a Config listener that listens to all kind of {@link ConfigEvent}. + * @param <T> the type of the event. + * @param l the listener not null. + * @param eventType the event type to which this listener listens to. + */ + public static <T extends ConfigEvent> void addListener(ConfigEventListener l, Class<T> eventType) { + if (SPI == null) { + throw new ConfigException("No SPI registered for " + + ConfigEventManager.class.getName()); + } + SPI.addListener(l); + } + + /** + * Removes a listener registered globally. + * + * @param l the listener not null. + */ + public static void removeListener(ConfigEventListener l) { + if (SPI == null) { + throw new ConfigException("No SPI registered for " + + ConfigEventManager.class.getName()); + } + SPI.removeListener(l); + } + + /** + * Removes a listener registered for the given event type. + * + * @param <T> the type of the event. + * @param l the listener, not null. + * @param eventType the event type to which this listener listens to. + */ + public static <T extends ConfigEvent> void removeListener(ConfigEventListener l, Class<T> eventType) { + if (SPI == null) { + throw new ConfigException("No SPI registered for " + + ConfigEventManager.class.getName()); + } + SPI.removeListener(l); + } + + /** + * Access all registered ConfigEventListeners listening to a given event type. + * @param type the event type + * @param <T> type param + * @return a list with the listeners found, never null. + */ + public static <T extends ConfigEvent> + Collection<? extends ConfigEventListener> getListeners(Class<T> type) { + return SPI.getListeners(type); + } + + /** + * Access all registered ConfigEventListeners listening to a all kind of event types globally. + * + * @param <T> the type of the event. + * @return a list with the listeners found, never null. + */ + public static <T extends ConfigEvent> + Collection<? extends ConfigEventListener> getListeners() { + return SPI.getListeners(); + } + + /** + * Publishes a {@link ConfigurationChange} synchronously to all interested listeners. + * + * @param <T> the type of the event. + * @param event the event, not null. + */ + public static <T> void fireEvent(ConfigEvent<?> event) { + SPI.fireEvent(event); + } + + /** + * Publishes a {@link ConfigurationChange} asynchronously/multithreaded to all interested listeners. + * + * @param <T> the type of the event. + * @param event the event, not null. + */ + public static <T> void fireEventAsynch(ConfigEvent<?> event) { + SPI.fireEventAsynch(event); + } + + /** + * Start/Stop the change monitoring service, which will observe/reevaluate the current configuration regularly + * and trigger ConfigurationChange events if something changed. This is quite handy for publishing + * configuration changes to whatever systems are interested in. Hereby the origin of a configuration change + * can be on this machine, or also remotely. For handling corresponding {@link ConfigEventListener} have + * to be registered, e.g. listening on {@link org.apache.tamaya.events.ConfigurationChange} events. + * + * @param enable whether to enable or disable the change monitoring. + * + * @see #isChangeMonitoring() + * @see #getChangeMonitoringPeriod() + */ + public static void enableChangeMonitoring(boolean enable) { + SPI.enableChangeMonitor(enable); + } + + /** + * Check if the observer is running currently. + * + * @return true, if the change monitoring service is currently running. + * @see #enableChangeMonitoring(boolean) + */ + public static boolean isChangeMonitoring() { + return SPI.isChangeMonitorActive(); + } + + /** + * Get the current check period to check for configuration changes. + * + * @return the check period in ms. + */ + public static long getChangeMonitoringPeriod(){ + return SPI.getChangeMonitoringPeriod(); + } + + /** + * Sets the current monitoring period and restarts the monitor. You still have to enable the monitor if + * it is currently not enabled. + * @param millis the monitoring period in ms. + * @see #enableChangeMonitoring(boolean) + * @see #isChangeMonitoring() + */ + public static void setChangeMonitoringPeriod(long millis){ + SPI.setChangeMonitoringPeriod(millis); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigurationChange.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigurationChange.java b/events/src/main/java/org/apache/tamaya/events/ConfigurationChange.java new file mode 100644 index 0000000..c31cda2 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigurationChange.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.Configuration; + +import java.beans.PropertyChangeEvent; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Event that contains a set current changes that were applied or could be applied. + * This class is immutable and thread-safe. To create instances use + * {@link PropertySourceChangeBuilder}. + * + * Created by Anatole on 22.10.2014. + */ +public final class ConfigurationChange implements ConfigEvent<Configuration>, Serializable{ + + private static final long serialVersionUID = 1L; + /** The base property provider/configuration. */ + private final FrozenConfiguration configuration; + /** The base version, usable for optimistic locking. */ + private String version = UUID.randomUUID().toString(); + /** The timestamp of the change set in millis from the epoch. */ + private long timestamp = System.currentTimeMillis(); + /** The recorded changes. */ + private final Map<String,PropertyChangeEvent> changes = new HashMap<>(); + + /** + * Get an empty change set for the given provider. + * @param configuration The configuration changed, not null. + * @return an empty ConfigurationChangeSet instance. + */ + public static ConfigurationChange emptyChangeSet(Configuration configuration){ + return ConfigurationChangeBuilder.of(configuration).build(); + } + + /** + * Constructor used by {@link PropertySourceChangeBuilder}. + * @param builder The builder used, not null. + */ + ConfigurationChange(ConfigurationChangeBuilder builder) { + this.configuration = FrozenConfiguration.of(builder.source); + for(PropertyChangeEvent ev:builder.delta.values()){ + this.changes.put(ev.getPropertyName(), ev); + } + if(builder.version!=null){ + this.version = builder.version; + } + if(builder.timestamp!=null){ + this.timestamp = builder.timestamp; + } + } + + @Override + public Class<Configuration> getResourceType() { + return Configuration.class; + } + + /** + * Get the underlying property provider/configuration. + * @return the underlying property provider/configuration, never null. + */ + @Override + public Configuration getResource(){ + return this.configuration; + } + + /** + * Get the base version, usable for optimistic locking. + * @return the base version. + */ + @Override + public String getVersion(){ + return version; + } + + /** + * Get the timestamp in millis from the current epoch. it is expected that the timestamp and the version are unique to + * identify a changeset. + * @return the timestamp, when this changeset was created. + */ + @Override + public long getTimestamp(){ + return timestamp; + } + + /** + * Get the changes recorded. + * @return the recorded changes, never null. + */ + public Collection<PropertyChangeEvent> getChanges(){ + return Collections.unmodifiableCollection(this.changes.values()); + } + + /** + * Access the number current removed entries. + * @return the number current removed entries. + */ + public int getRemovedSize() { + int removedCount = 0; + for(PropertyChangeEvent ev:this.changes.values()){ + if(ev.getNewValue() == null){ + removedCount++; + } + } + return removedCount; + } + + /** + * Access the number current added entries. + * @return the number current added entries. + */ + public int getAddedSize() { + int addedCount = 0; + for(PropertyChangeEvent ev:this.changes.values()){ + if(ev.getOldValue() == null && + ev.getNewValue() != null){ + addedCount++; + } + } + return addedCount; + } + + /** + * Access the number current updated entries. + * @return the number current updated entries. + */ + public int getUpdatedSize() { + int updatedCount = 0; + for(PropertyChangeEvent ev:this.changes.values()){ + if( ev.getOldValue()!=null && ev.getNewValue()!=null){ + updatedCount++; + } + } + return updatedCount; + } + + + /** + * Checks if the given key was removed. + * @param key the target key, not null. + * @return true, if the given key was removed. + */ + public boolean isRemoved(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getNewValue() == null; + } + + /** + * Checks if the given key was added. + * @param key the target key, not null. + * @return true, if the given key was added. + */ + public boolean isAdded(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getOldValue() == null; + } + + /** + * Checks if the given key was updated. + * @param key the target key, not null. + * @return true, if the given key was updated. + */ + public boolean isUpdated(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getOldValue() != null && change.getNewValue() != null; + } + + /** + * Checks if the given key is added, or updated AND NOT removed. + * @param key the target key, not null. + * @return true, if the given key was added, or updated BUT NOT removed. + */ + public boolean isKeyAffected(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getNewValue() != null; + } + + /** + * CHecks if the current change set does not contain any changes. + * @return tru, if the change set is empty. + */ + public boolean isEmpty(){ + return this.changes.isEmpty(); + } + + + @Override + public String toString() { + return "ConfigurationChange{" + + "configuration=" + configuration + + ", version='" + version + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigurationChangeBuilder.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigurationChangeBuilder.java b/events/src/main/java/org/apache/tamaya/events/ConfigurationChangeBuilder.java new file mode 100644 index 0000000..1fd228a --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigurationChangeBuilder.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; + +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Models a set current changes applied to a {@link org.apache.tamaya.spi.PropertySource}. Consumers of these events + * can observing changes to property sources and + * <ol> + * <li>Check if their current configuration instance ({@link org.apache.tamaya.spi.ConfigurationContext} + * contains the changed {@link org.apache.tamaya.spi.PropertySource} (Note: the reference tova property source is never affected by a + * change, its only the data of the property source).</li> + * <li>If so corresponding action may be taken, such as reevaluating the configuration values (depending on + * the update policy) or reevaluating the complete {@link org.apache.tamaya.Configuration} to create a change + * event on configuration level. + * </ol> + */ +public final class ConfigurationChangeBuilder { + /** + * The recorded changes. + */ + final SortedMap<String, PropertyChangeEvent> delta = new TreeMap<>(); + /** + * The underlying configuration/provider. + */ + final Configuration source; + /** + * The version configured, or null, for generating a default. + */ + String version; + /** + * The optional timestamp in millis of this epoch. + */ + Long timestamp; + + /** + * Constructor. + * + * @param configuration the underlying configuration, not null. + */ + private ConfigurationChangeBuilder(Configuration configuration) { + this.source = Objects.requireNonNull(configuration); + } + + /** + * Creates a new instance current this builder using the current COnfiguration as root resource. + * + * @return the builder for chaining. + */ + public static ConfigurationChangeBuilder of() { + return new ConfigurationChangeBuilder(ConfigurationProvider.getConfiguration()); + } + + /** + * Creates a new instance current this builder. + * + * @param configuration the configuration changed, not null. + * @return the builder for chaining. + */ + public static ConfigurationChangeBuilder of(Configuration configuration) { + return new ConfigurationChangeBuilder(configuration); + } + + /** + * Compares the two property config/configurations and creates a collection current all changes + * that must be appied to render {@code map1} into {@code map2}. + * + * @param map1 the source map, not null. + * @param map2 the target map, not null. + * @return a collection current change events, never null. + */ + public static Collection<PropertyChangeEvent> compare(Configuration map1, Configuration map2) { + List<PropertyChangeEvent> changes = new ArrayList<>(); + for (Map.Entry<String, String> en : map1.getProperties().entrySet()) { + String val = map2.get(en.getKey()); + if (val == null) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), null, en.getValue())); + } else if (!val.equals(en.getValue())) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), val, en.getValue())); + } + } + for (Map.Entry<String, String> en : map2.getProperties().entrySet()) { + String val = map1.get(en.getKey()); + if (val == null) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), null, en.getValue())); + } else if (!val.equals(en.getValue())) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), val, en.getValue())); + } + } + return changes; + } + + /* + * Apply a version/UUID to the set being built. + * @param version the version to apply, or null, to let the system generate a version for you. + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder setVersion(String version) { + this.version = version; + return this; + } + + /* + * Apply given timestamp to the set being built. + * @param version the version to apply, or null, to let the system generate a version for you. + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + /** + * This method records all changes to be applied to the base property provider/configuration to + * achieve the given target state. + * + * @param newState the new target state, not null. + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder addChanges(Configuration newState) { + for (PropertyChangeEvent c : compare(newState, this.source)) { + this.delta.put(c.getPropertyName(), c); + } + return this; + } + + /** + * Applies a single key/value change. + * + * @param key the changed key + * @param value the new value. + * @return this instance for chining. + */ + public ConfigurationChangeBuilder addChange(String key, String value) { + this.delta.put(key, new PropertyChangeEvent(this.source, key, this.source.get(key), value)); + return this; + } + + /** + * Get the current values, also considering any changes recorded within this change set. + * + * @param key the key current the entry, not null. + * @return the keys, or null. + */ + public String get(String key) { + PropertyChangeEvent change = this.delta.get(key); + if (change != null && !(change.getNewValue() == null)) { + return (String) change.getNewValue(); + } + return null; + } + + /** + * Marks the given key(s) fromMap the configuration/properties to be removed. + * + * @param key the key current the entry, not null. + * @param otherKeys additional keys to be removed (convenience), not null. + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder removeKey(String key, String... otherKeys) { + String oldValue = this.source.get(key); + if (oldValue == null) { + this.delta.remove(key); + } + this.delta.put(key, new PropertyChangeEvent(this.source, key, oldValue, null)); + for (String addKey : otherKeys) { + oldValue = this.source.get(addKey); + if (oldValue == null) { + this.delta.remove(addKey); + } + this.delta.put(addKey, new PropertyChangeEvent(this.source, addKey, oldValue, null)); + } + return this; + } + + /** + * Apply all the given values to the base configuration/properties. + * Note that all values passed must be convertible to String, either + * <ul> + * <li>the registered codecs provider provides codecs for the corresponding keys, or </li> + * <li>default codecs are present for the given type, or</li> + * <li>the value is an instanceof String</li> + * </ul> + * + * @param changes the changes to be applied, not null. + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder putAll(Map<String, String> changes) { + for (Map.Entry<String, String> en : changes.entrySet()) { + this.delta.put(en.getKey(), new PropertyChangeEvent(this.source, en.getKey(), null, en.getValue())); + } + return this; + } + + /** + * This method will create a change set that clears all entries fromMap the given base configuration/properties. + * + * @return the builder for chaining. + */ + public ConfigurationChangeBuilder removeAllKeys() { + this.delta.clear(); + for (Map.Entry<String, String> en : this.source.getProperties().entrySet()) { + this.delta.put(en.getKey(), new PropertyChangeEvent(this.source, en.getKey(), en.getValue(), null)); + } +// this.source.getProperties().forEach((k, v) -> +// this.delta.put(k, new PropertyChangeEvent(this.source, k, v, null))); + return this; + } + + /** + * Checks if the change set is empty, i.e. does not contain any changes. + * + * @return true, if the set is empty. + */ + public boolean isEmpty() { + return this.delta.isEmpty(); + } + + /** + * Resets this change set instance. This will clear all changes done to this builder, so the + * set will be empty. + */ + public void reset() { + this.delta.clear(); + } + + /** + * Builds the corresponding change set. + * + * @return the new change set, never null. + */ + public ConfigurationChange build() { + return new ConfigurationChange(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ConfigurationChangeSetBuilder [config=" + source + ", " + + ", delta=" + delta + "]"; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChange.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChange.java b/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChange.java new file mode 100644 index 0000000..4e12d42 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChange.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.PropertySource; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +/** + * Event that contains a set of current changes that were applied or can be applied. + * This class is immutable and thread-safe. To create instances use + * {@link PropertySourceChangeBuilder}. + * + * Created by Anatole on 22.10.2014. + */ +public final class ConfigurationContextChange implements ConfigEvent<ConfigurationContext>, Serializable{ + + private static final long serialVersionUID = 1L; + /** The base property provider/configuration. */ + private final List<PropertySourceChange> changedPropertySources = new ArrayList<>(); + /** The base version, usable for optimistic locking. */ + private String version = UUID.randomUUID().toString(); + /** The timestamp of the change set in millis from the epoch. */ + private long timestamp = System.currentTimeMillis(); + /** The configuration context. */ + private final ConfigurationContext configurationContext; + + /** + * Get an empty change set for the given provider. + * + * @param configurationContext context to use for creating changesets. + * @return an empty ConfigurationContextChange instance. + */ + public static ConfigurationContextChange emptyChangeSet(ConfigurationContext configurationContext){ + return ConfigurationContextChangeBuilder.of(configurationContext).build(); + } + + /** + * Constructor used by {@link PropertySourceChangeBuilder}. + * @param builder The builder used, not null. + */ + ConfigurationContextChange(ConfigurationContextChangeBuilder builder) { + this.changedPropertySources.addAll(builder.changedPropertySources); + if(builder.version!=null){ + this.version = builder.version; + } + if(builder.timestamp!=null){ + this.timestamp = builder.timestamp; + } + this.configurationContext = builder.configurationContext; + } + + @Override + public Class<ConfigurationContext> getResourceType() { + return ConfigurationContext.class; + } + + @Override + public ConfigurationContext getResource() { + return configurationContext; + } + + /** + * Get the base version, usable for optimistic locking. + * @return the base version. + */ + @Override + public String getVersion(){ + return version; + } + + /** + * Get the timestamp in millis from the current epoch. it is expected that the timestamp and the version are unique to + * identify a changeset. + * @return the timestamp, when this changeset was created. + */ + @Override + public long getTimestamp(){ + return timestamp; + } + + /** + * Get the changes recorded. + * @return the recorded changes, never null. + */ + public Collection<PropertySourceChange> getPropertySourceChanges(){ + return Collections.unmodifiableCollection(this.changedPropertySources); + } + + /** + * Get the property source updates. + * @return the recorded changes, never null. + */ + public Collection<PropertySourceChange> getPropertySourceUpdates(){ + List<PropertySourceChange> result = new ArrayList<>(); + for (PropertySourceChange pc : this.changedPropertySources) { + if (pc.getChangeType() == ChangeType.UPDATED) { + result.add(pc); + } + } + return result; +// return Collections.unmodifiableCollection(this.changedPropertySources).stream() +// .filter(pc -> pc.getChangeType()==ChangeType.UPDATED).collect(Collectors.toList()); + } + + /** + * Get the property sources to be removed. + * @return the recorded changes, never null. + */ + public Collection<PropertySource> getRemovedPropertySources(){ + List<PropertySource> result = new ArrayList<>(); + for (PropertySourceChange pc : this.changedPropertySources) { + if (pc.getChangeType() == ChangeType.DELETED) { + result.add(pc.getResource()); + } + } + return result; +// return getPropertySourceChanges().stream().filter(pc -> pc.getChangeType()==ChangeType.DELETED). +// map(ps -> ps.getPropertySource()).collect(Collectors.toList()); + } + + /** + * Get the property sources to be added. + * @return the recorded changes, never null. + */ + public Collection<PropertySource> getAddedPropertySources(){ + List<PropertySource> result = new ArrayList<>(); + for (PropertySourceChange pc : this.changedPropertySources) { + if (pc.getChangeType() == ChangeType.NEW) { + result.add(pc.getResource()); + } + } + return result; +// return getPropertySourceChanges().stream().filter(pc -> pc.getChangeType()==ChangeType.NEW). +// map(ps -> ps.getPropertySource()).collect(Collectors.toList()); + } + + /** + * Get the property sources to be updated. + * @return the recorded changes, never null. + */ + public Collection<PropertySource> getUpdatedPropertySources(){ + List<PropertySource> result = new ArrayList<>(); + for (PropertySourceChange pc : this.changedPropertySources) { + if (pc.getChangeType() == ChangeType.UPDATED) { + result.add(pc.getResource()); + } + } + return result; +// return getPropertySourceChanges().stream().filter(pc -> pc.getChangeType()==ChangeType.UPDATED). +// map(ps -> ps.getPropertySource()).collect(Collectors.toList()); + } + + /** + * Checks if the given propertySource is affected (added, changed or removed). + * @param propertySource the propertySource, not null. + * @return true, if the given propertySource ia affected. + */ + public boolean isAffected(PropertySource propertySource) { + for (PropertySourceChange ps : this.changedPropertySources) { + if (ps.getResource() == propertySource || + ps.getResource().getName().equals(propertySource.getName())) { + return true; + } + } + return false; +// return this.changedPropertySources.stream().filter(ps -> ps.getPropertySource()==propertySource || +// ps.getPropertySource().getName().equals(propertySource.getName())).findAny().isPresent(); + } + + /** + * CHecks if the current change set does not contain any changes. + * @return tru, if the change set is empty. + */ + public boolean isEmpty(){ + return this.changedPropertySources.isEmpty(); + } + + + @Override + public String toString() { + return "ConfigurationContextChange{" + + "changedPropertySources=" + changedPropertySources + + ", version='" + version + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChangeBuilder.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChangeBuilder.java b/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChangeBuilder.java new file mode 100644 index 0000000..b586428 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/ConfigurationContextChangeBuilder.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.PropertySource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Models a set of current changes applied to a {@link org.apache.tamaya.spi.PropertySource}. Consumers of these events + * can observe changes to property sources and + * <ol> + * <li>check if their current configuration instance ({@link org.apache.tamaya.spi.ConfigurationContext} + * contains the changed {@link org.apache.tamaya.spi.PropertySource} (Note: the reference to a property source is never affected by a + * change, it is the data of the property source only).</li> + * <li>if so, a corresponding action may be taken, such as reevaluating the configuration values (depending on + * the update policy) or reevaluating the complete {@link org.apache.tamaya.Configuration} to create a change + * event on configuration level. + * </ol> + */ +public final class ConfigurationContextChangeBuilder { + /** + * The recorded changes. + */ + final List<PropertySourceChange> changedPropertySources = new ArrayList<>(); + /** + * The version configured, or null, for generating a default. + */ + String version; + /** + * The optional timestamp in millis of this epoch. + */ + Long timestamp; + + final ConfigurationContext configurationContext; + + /** + * Constructor. + */ + private ConfigurationContextChangeBuilder(ConfigurationContext configurationContext) { + this.configurationContext = Objects.requireNonNull(configurationContext); + } + + /** + * Just creates a new ConfigurationContextBuilder using the current COnfigurationContext has root resource. + * @return a new ConfigurationContextBuilder, never null. + */ + public static ConfigurationContextChangeBuilder of() { + return of(ConfigurationProvider.getConfigurationContext()); + } + + /** + * Creates a new instance current this builder. + * + * @param context context to use for creating changesets. + * @return the builder for chaining. + */ + public static ConfigurationContextChangeBuilder of(ConfigurationContext context) { + return new ConfigurationContextChangeBuilder(context); + } + + /** + * Apply a version/UUID to the set being built. + * @param version the version to apply, or null, to let the system generate a version for you. + * @return the builder for chaining. + */ + public ConfigurationContextChangeBuilder setVersion(String version) { + this.version = version; + return this; + } + + /** + * Apply given timestamp to the set being built. + * @param timestamp timestamp to set. + * @return the builder for chaining. + */ + public ConfigurationContextChangeBuilder setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + /** + * This method records all changes to be applied to the base property provider/configuration to + * achieve the given target state. + * + * @param propertySource the new target state, not null. + * @return the builder for chaining. + */ + public ConfigurationContextChangeBuilder newPropertySource(PropertySource propertySource) { + this.changedPropertySources.add(PropertySourceChange.ofAdded(propertySource)); + return this; + } + + /** + * This method records all changes to be applied to the base property provider/configuration to + * achieve the given target state. + * + * @param propertySource the new target state, not null. + * @return the builder for chaining. + */ + public ConfigurationContextChangeBuilder removedPropertySource(PropertySource propertySource) { + this.changedPropertySources.add(PropertySourceChange.ofDeleted(propertySource)); + return this; + } + + /** + * This method records all changes to be applied to the base property provider/configuration to + * achieve the given target state. + * + * @param propertySourceChange the change state, not null. + * @return the builder for chaining. + */ + public ConfigurationContextChangeBuilder changedPropertySource(PropertySourceChange propertySourceChange) { + this.changedPropertySources.add(Objects.requireNonNull(propertySourceChange)); + return this; + } + + /** + * Checks if the change set is empty, i.e. does not contain any changes. + * + * @return true, if the set is empty. + */ + public boolean isEmpty() { + return this.changedPropertySources.isEmpty(); + } + + /** + * Resets this change set instance. This will clear all changes done to this builder, so the + * set will be empty. + */ + public void reset() { + this.changedPropertySources.clear(); + } + + /** + * Builds the corresponding change set. + * + * @return the new change set, never null. + */ + public ConfigurationContextChange build() { + return new ConfigurationContextChange(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ConfigurationContextChangeBuilder [propertySources=" + changedPropertySources + "]"; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/FrozenConfiguration.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/FrozenConfiguration.java b/events/src/main/java/org/apache/tamaya/events/FrozenConfiguration.java new file mode 100644 index 0000000..304ddba --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/FrozenConfiguration.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.ConfigOperator; +import org.apache.tamaya.ConfigQuery; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.functions.ConfigurationFunctions; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * /** + * Configuration implementation that stores all current values of a given (possibly dynamic, contextual and non server + * capable instance) and is fully serializable. Note that hereby only the scannable key/value pairs are considered. + */ +public final class FrozenConfiguration implements Configuration, Serializable { + private static final long serialVersionUID = -6373137316556444171L; + /** + * The properties frozen. + */ + private Map<String, String> properties = new HashMap<>(); + + /** + * Constructor. + * + * @param config The base configuration. + */ + private FrozenConfiguration(Configuration config) { + this.properties.putAll(config.getProperties()); + this.properties.put("[meta]frozenAt", String.valueOf(System.currentTimeMillis())); + this.properties = Collections.unmodifiableMap(this.properties); + } + + /** + * Creates a new FrozenConfiguration instance based on a Configuration given. + * + * @param config the configuration to be frozen, not null. + * @return the frozen Configuration. + */ + public static FrozenConfiguration of(Configuration config) { + if (config instanceof FrozenConfiguration) { + return (FrozenConfiguration) config; + } + return new FrozenConfiguration(config); + } + + @Override + public String get(String key) { + return this.properties.get(key); + } + + @Override + public String getOrDefault(String key, String defaultValue) { + String val = get(key); + if(val==null){ + return defaultValue; + } + return val; + } + + @Override + public <T> T getOrDefault(String key, Class<T> type, T defaultValue) { + T val = get(key, type); + if(val==null){ + return defaultValue; + } + return val; + } + + @Override + public <T> T get(String key, Class<T> type) { + return (T) get(key, TypeLiteral.of(type)); + } + + /** + * Accesses the current String value for the given key and tries to convert it + * using the {@link org.apache.tamaya.spi.PropertyConverter} instances provided by the current + * {@link org.apache.tamaya.spi.ConfigurationContext}. + * + * @param key the property's absolute, or relative path, e.g. @code + * a/b/c/d.myProperty}. + * @param type The target type required, not null. + * @param <T> the value type + * @return the converted value, never null. + */ + @Override + public <T> T get(String key, TypeLiteral<T> type) { + String value = get(key); + if (value != null) { + List<PropertyConverter<T>> converters = ConfigurationProvider.getConfigurationContext() + .getPropertyConverters(type); + ConversionContext context = new ConversionContext.Builder(this, + ConfigurationProvider.getConfigurationContext(), key,type).build(); + for (PropertyConverter<T> converter : converters) { + try { + T t = converter.convert(value, context); + if (t != null) { + return t; + } + } catch (Exception e) { + Logger.getLogger(getClass().getName()) + .log(Level.FINEST, "PropertyConverter: " + converter + " failed to convert value: " + value, + e); + } + } + throw new ConfigException("Unparseable config value for type: " + type.getRawType().getName() + ": " + key + + ", supported formats: " + context.getSupportedFormats()); + } + + return null; + } + + @Override + public <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue) { + T val = get(key, type); + if(val==null){ + return defaultValue; + } + return val; + } + + @Override + public Map<String, String> getProperties() { + return properties; + } + + @Override + public Configuration with(ConfigOperator operator) { + return operator.operate(this); + } + + @Override + public <T> T query(ConfigQuery<T> query) { + return query.query(this); + } + + @Override + public ConfigurationContext getContext() { + return ConfigurationFunctions.emptyConfigurationContext(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FrozenConfiguration that = (FrozenConfiguration) o; + return properties.equals(that.properties); + } + + @Override + public int hashCode() { + return properties.hashCode(); + } + + @Override + public String toString() { + return "FrozenConfiguration{" + + "properties=" + properties + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/FrozenPropertySource.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/FrozenPropertySource.java b/events/src/main/java/org/apache/tamaya/events/FrozenPropertySource.java new file mode 100644 index 0000000..81e6dca --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/FrozenPropertySource.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * PropertySource implementation that stores all current values of a given (possibly dynamic, contextual and non server + * capable instance) and is fully serializable. Note that hereby only the scannable key/value pairs are considered. + */ +public final class FrozenPropertySource implements PropertySource, Serializable { + private static final long serialVersionUID = -6373137316556444171L; + /** + * The ordinal. + */ + private final int ordinal; + /** + * The properties read. + */ + private Map<String, String> properties = new HashMap<>(); + /** + * The PropertySource's name. + */ + private final String name; + + /** + * Constructor. + * + * @param propertySource The base PropertySource. + */ + private FrozenPropertySource(PropertySource propertySource) { + this.properties.putAll(propertySource.getProperties()); + this.properties.put("[meta]frozenAt", String.valueOf(System.currentTimeMillis())); + this.properties = Collections.unmodifiableMap(this.properties); + this.ordinal = propertySource.getOrdinal(); + this.name = propertySource.getName(); + } + + /** + * Creates a new FrozenPropertySource instance based on a PropertySource given. + * + * @param propertySource the property source to be frozen, not null. + * @return the frozen property source. + */ + public static FrozenPropertySource of(PropertySource propertySource) { + if (propertySource instanceof FrozenPropertySource) { + return (FrozenPropertySource) propertySource; + } + return new FrozenPropertySource(propertySource); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public int getOrdinal() { + return this.ordinal; + } + + @Override + public PropertyValue get(String key) { + return PropertyValue.of(key, this.properties.get(key), getName()); + } + + @Override + public Map<String, String> getProperties() { + return properties; + } + + @Override + public boolean isScannable() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FrozenPropertySource)) { + return false; + } + FrozenPropertySource that = (FrozenPropertySource) o; + return ordinal == that.ordinal && properties.equals(that.properties); + } + + @Override + public int hashCode() { + int result = ordinal; + result = 31 * result + properties.hashCode(); + return result; + } + + @Override + public String toString() { + return "FrozenPropertySource{" + + "name=" + name + + ", ordinal=" + ordinal + + ", properties=" + properties + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/PropertySourceChange.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/PropertySourceChange.java b/events/src/main/java/org/apache/tamaya/events/PropertySourceChange.java new file mode 100644 index 0000000..063612c --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/PropertySourceChange.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.spi.PropertySource; + +import java.beans.PropertyChangeEvent; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Event that contains a set current changes that were applied or could be applied. + * This class is immutable and thread-safe. To create instances use + * {@link PropertySourceChangeBuilder}. + * + * Created by Anatole on 22.10.2014. + */ +public final class PropertySourceChange implements ConfigEvent<PropertySource>, Serializable{ + + private static final long serialVersionUID = 1L; + /** The base property provider/configuration. */ + private final FrozenPropertySource propertySource; + /** The base version, usable for optimistic locking. */ + private String version = UUID.randomUUID().toString(); + /** The timestamp of the change set in millis from the epoch. */ + private long timestamp = System.currentTimeMillis(); + /** The recorded changes. */ + private final Map<String,PropertyChangeEvent> changes = new HashMap<>(); + /** The overall type of change. */ + private final ChangeType changeType; + + /** + * Constructor used by {@link PropertySourceChangeBuilder}. + * @param builder The builder used, not null. + */ + PropertySourceChange(PropertySourceChangeBuilder builder) { + this.propertySource = FrozenPropertySource.of(builder.source); + for (PropertyChangeEvent c : builder.delta.values()) { + this.changes.put(c.getPropertyName(), c); + } + if(builder.version!=null){ + this.version = builder.version; + } + if(builder.timestamp!=null){ + this.timestamp = builder.timestamp; + } + this.changeType = builder.changeType; + } + + /** + * Gets the type of change for this PropertySource. + * @return the type of change for this PropertySource, never null. + */ + public ChangeType getChangeType(){ + return this.changeType; + } + + @Override + public Class<PropertySource> getResourceType() { + return PropertySource.class; + } + + /** + * Get the underlying property provider/configuration. + * @return the underlying property provider/configuration, or null, if the change instance was deserialized. + */ + @Override + public PropertySource getResource(){ + return this.propertySource; + } + + /** + * Get the base version, usable for optimistic locking. + * @return the base version. + */ + @Override + public String getVersion(){ + return version; + } + + /** + * Get the timestamp in millis from the current epoch. it is expected that the timestamp and the version are unique to + * identify a changeset. + * @return the timestamp, when this changeset was created. + */ + @Override + public long getTimestamp(){ + return timestamp; + } + + /** + * Get the changes recorded. + * @return the recorded changes, never null. + */ + public Collection<PropertyChangeEvent> getChanges(){ + return Collections.unmodifiableCollection(this.changes.values()); + } + + /** + * Access the number current removed entries. + * @return the number current removed entries. + */ + public int getRemovedSize() { + int removedCount = 0; + for (PropertyChangeEvent ev : this.changes.values()) { + if (ev.getNewValue() == null) { + removedCount++; + } + } + return removedCount; +// return (int) this.changes.values().stream().filter((e) -> e.getNewValue() == null).count(); + } + + /** + * Access the number current added entries. + * @return the number current added entries. + */ + public int getAddedSize() { + int addedCount = 0; + for (PropertyChangeEvent ev : this.changes.values()) { + if (ev.getOldValue() == null && + ev.getNewValue() != null) { + addedCount++; + } + } + return addedCount; +// return (int) this.changes.values().stream().filter((e) -> e.getOldValue() == null).count(); + } + + /** + * Access the number current updated entries. + * @return the number current updated entries. + */ + public int getUpdatedSize() { + int updatedCount = 0; + for (PropertyChangeEvent ev : this.changes.values()) { + if (ev.getOldValue() != null && ev.getNewValue() != null) { + updatedCount++; + } + } + return updatedCount; +// return (int) this.changes.values().stream().filter((e) -> e.getOldValue()!=null && e.getNewValue()!=null).count(); + } + + + /** + * Checks if the given key was removed. + * @param key the target key, not null. + * @return true, if the given key was removed. + */ + public boolean isRemoved(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getNewValue() == null; + } + + /** + * Checks if the given key was added. + * @param key the target key, not null. + * @return true, if the given key was added. + */ + public boolean isAdded(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getOldValue() == null; + } + + /** + * Checks if the given key was updated. + * @param key the target key, not null. + * @return true, if the given key was updated. + */ + public boolean isUpdated(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getOldValue() != null && change.getNewValue() != null; + } + + /** + * Checks if the given key is added, or updated AND NOT removed. + * @param key the target key, not null. + * @return true, if the given key was added, or updated BUT NOT removed. + */ + public boolean isKeyAffected(String key) { + PropertyChangeEvent change = this.changes.get(key); + return change != null && change.getNewValue() != null; + } + + /** + * CHecks if the current change set does not contain any changes. + * @return tru, if the change set is empty. + */ + public boolean isEmpty(){ + return this.changes.isEmpty(); + } + + + /** + * Create a change event for a new PropertySource that was added. + * @param propertySource the new property source, not null. + * @return a new PropertySourceChange, representing a PropertySource that was added. + */ + public static PropertySourceChange ofAdded(PropertySource propertySource) { + return PropertySourceChangeBuilder.of(propertySource, ChangeType.NEW).build(); + } + + /** + * Create a change event for a deleted PropertySource. + * @param propertySource the deleted property source, not null. + * @return a new PropertySourceChange, representing a PropertySource that was deleted. + */ + public static PropertySourceChange ofDeleted(PropertySource propertySource) { + return PropertySourceChangeBuilder.of(propertySource, ChangeType.DELETED).build(); + } + + @Override + public String toString() { + return "PropertySourceChange{" + + "changeType=" + changeType + + ", propertySource=" + propertySource + + ", version='" + version + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/PropertySourceChangeBuilder.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/PropertySourceChangeBuilder.java b/events/src/main/java/org/apache/tamaya/events/PropertySourceChangeBuilder.java new file mode 100644 index 0000000..b7a4483 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/PropertySourceChangeBuilder.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tamaya.events; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Models a set current changes applied to a {@link org.apache.tamaya.spi.PropertySource}. Consumers of these events + * can observing changes to property sources and + * <ol> + * <li>Check if their current configuration instance ({@link org.apache.tamaya.spi.ConfigurationContext} + * contains the changed {@link org.apache.tamaya.spi.PropertySource} (Note: the reference tova property source is never affected by a + * change, its only the data of the property source).</li> + * <li>If so corresponding action may be taken, such as reevaluating the configuration values (depending on + * the update policy) or reevaluating the complete {@link org.apache.tamaya.Configuration} to create a change + * event on configuration level. + * </ol> + */ +public final class PropertySourceChangeBuilder { + /** + * The recorded changes. + */ + final SortedMap<String, PropertyChangeEvent> delta = new TreeMap<>(); + /** + * The underlying configuration/provider. + */ + final PropertySource source; + /** + * The version configured, or null, for generating a default. + */ + String version; + /** + * The optional timestamp in millis of this epoch. + */ + Long timestamp; + + /** The type of change. */ + ChangeType changeType; + + /** + * Constructor. + * + * @param source the underlying configuration/provider, not null. + * @param changeType kind of change. + */ + private PropertySourceChangeBuilder(PropertySource source, ChangeType changeType) { + this.source = Objects.requireNonNull(source); + this.changeType = Objects.requireNonNull(changeType); + } + + /** + * Creates a new instance of this builder. + * + * @param source the underlying property provider/configuration, not null. + * @param changeType kind of change. + * @return the builder for chaining. + */ + public static PropertySourceChangeBuilder of(PropertySource source, ChangeType changeType) { + return new PropertySourceChangeBuilder(source, changeType); + } + + /** + * Compares the two property config/configurations and creates a collection current all changes + * that must be applied to render {@code map1} into {@code map2}. + * + * @param map1 the source map, not null. + * @param map2 the target map, not null. + * @return a collection current change events, never null. + */ + public static Collection<PropertyChangeEvent> compare(PropertySource map1, PropertySource map2) { + List<PropertyChangeEvent> changes = new ArrayList<>(); + for (Map.Entry<String, String> en : map1.getProperties().entrySet()) { + PropertyValue val = map2.get(en.getKey()); + if (val == null) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), null, en.getValue())); + } else if (!val.getValue().equals(en.getValue())) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), val.getValue(), en.getValue())); + } + } + for (Map.Entry<String, String> en : map2.getProperties().entrySet()) { + PropertyValue val = map1.get(en.getKey()); + if (val == null) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), en.getValue(), null)); + } else if (!val.equals(en.getValue())) { + changes.add(new PropertyChangeEvent(map1, en.getKey(), en.getValue(), val.getValue())); + } + } + return changes; + } + + /* + * Apply a version/UUID to the set being built. + * @param version the version to apply, or null, to let the system generate a version for you. + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder setVersion(String version) { + this.version = version; + return this; + } + + /* + * Apply given timestamp to the set being built. + * @param version the version to apply, or null, to let the system generate a version for you. + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + /** + * This method records all changes to be applied to the base property provider/configuration to + * achieve the given target state. + * + * @param newState the new target state, not null. + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder addChanges(PropertySource newState) { + Collection<PropertyChangeEvent> events = PropertySourceChangeBuilder.compare(newState, this.source); + for (PropertyChangeEvent c : events) { + this.delta.put(c.getPropertyName(), c); + } + return this; + } + + /** + * Get the current values, also considering any changes recorded within this change set. + * + * @param key the key current the entry, not null. + * @return the keys, or null. + */ + public String get(String key) { + PropertyChangeEvent change = this.delta.get(key); + if (change != null && !(change.getNewValue() == null)) { + return (String) change.getNewValue(); + } + return null; + } + + /** + * Marks the given key(s) fromMap the configuration/properties to be removed. + * + * @param key the key current the entry, not null. + * @param otherKeys additional keys to be removed (convenience), not null. + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder remove(String key, String... otherKeys) { + PropertyValue oldValue = this.source.get(key); + if (oldValue == null) { + this.delta.remove(key); + } + this.delta.put(key, new PropertyChangeEvent(this.source, key, oldValue, null)); + for (String addKey : otherKeys) { + oldValue = this.source.get(addKey); + if (oldValue == null) { + this.delta.remove(addKey); + } + this.delta.put(addKey, new PropertyChangeEvent(this.source, addKey, oldValue, null)); + } + return this; + } + + /** + * Apply all the given values to the base configuration/properties. + * Note that all values passed must be convertible to String, either + * <ul> + * <li>the registered codecs provider provides codecs for the corresponding keys, or </li> + * <li>default codecs are present for the given type, or</li> + * <li>the value is an instanceof String</li> + * </ul> + * + * @param changes the changes to be applied, not null. + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder putAll(Map<String, String> changes) { + for (Map.Entry<String, String> en : this.source.getProperties().entrySet()) { + this.delta.put(en.getKey(), new PropertyChangeEvent(this.source, en.getKey(), null, en.getValue())); + } + return this; + } + + /** + * This method will create a change set that clears all entries fromMap the given base configuration/properties. + * + * @return the builder for chaining. + */ + public PropertySourceChangeBuilder deleteAll() { + this.delta.clear(); + for (Map.Entry<String, String> en : this.source.getProperties().entrySet()) { + this.delta.put(en.getKey(), new PropertyChangeEvent(this.source, en.getKey(), en.getValue(), null)); + } + return this; + } + + /** + * Checks if the change set is empty, i.e. does not contain any changes. + * + * @return true, if the set is empty. + */ + public boolean isEmpty() { + return this.delta.isEmpty(); + } + + /** + * Resets this change set instance. This will clear all changes done to this builder, so the + * set will be empty. + */ + public void reset() { + this.delta.clear(); + } + + public PropertySourceChangeBuilder setChangeType(ChangeType changeType) { + this.changeType = changeType; + return this; + } + + /** + * Builds the corresponding change set. + * + * @return the new change set, never null. + */ + public PropertySourceChange build() { + return new PropertySourceChange(this); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "PropertiesChangeBuilder [source=" + source + ", " + + ", delta=" + delta + "]"; + } + + +}