TAMAYA-354 Support atomic configuration, similar to config JSR.

Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/5f3f86b0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/5f3f86b0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/5f3f86b0

Branch: refs/heads/master
Commit: 5f3f86b01582b19debbe98dd4bf056e48a3654cd
Parents: 21808ea
Author: Anatole Tresch <[email protected]>
Authored: Sun Nov 18 22:15:47 2018 +0100
Committer: Anatole Tresch <[email protected]>
Committed: Sun Nov 18 22:15:47 2018 +0100

----------------------------------------------------------------------
 .../java/org/apache/tamaya/Configuration.java   | 166 ++++++++++++++-
 .../apache/tamaya/ConfigurationSnapshot.java    |  92 ++++++++
 .../org/apache/tamaya/spi/ChangeSupport.java    |  41 ++++
 .../apache/tamaya/spi/ConfigurationBuilder.java |   2 +-
 .../apache/tamaya/spi/ConfigurationContext.java |   1 -
 .../org/apache/tamaya/spi/PropertySource.java   |  47 ++++
 .../org/apache/tamaya/TestConfiguration.java    |   5 +
 .../tamaya/spisupport/DefaultConfiguration.java |  11 +-
 .../spisupport/DefaultConfigurationBuilder.java |   6 +-
 .../spisupport/DefaultConfigurationContext.java |   6 +-
 .../DefaultConfigurationSnapshot.java           | 212 +++++++++++++++++++
 .../DefaultPropertySourceSnapshot.java          | 211 ++++++++++++++++++
 .../spisupport/PropertySourceChangeSupport.java | 171 +++++++++++++++
 .../propertysource/BasePropertySource.java      |  53 ++++-
 .../propertysource/BuildablePropertySource.java |   5 +-
 .../propertysource/CLIPropertySource.java       |   6 +
 .../EnvironmentPropertySource.java              |   6 +
 .../JavaConfigurationPropertySource.java        |  10 +-
 .../propertysource/MapPropertySource.java       |  40 ++--
 .../PropertiesResourcePropertySource.java       |  69 ++++--
 .../propertysource/SimplePropertySource.java    |   6 +
 .../propertysource/SystemPropertySource.java    | 110 +++-------
 .../propertysource/WrappedPropertySource.java   |  29 +++
 .../DefaultConfigurationBuilderTest.java        |  12 +-
 .../DefaultConfigurationSnapshotTest.java       |  93 ++++++++
 .../spisupport/DefaultConfigurationTest.java    |  48 ++++-
 .../DefaultPropertySourceSnapshotTest.java      | 111 ++++++++++
 27 files changed, 1417 insertions(+), 152 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/Configuration.java
----------------------------------------------------------------------
diff --git a/code/api/src/main/java/org/apache/tamaya/Configuration.java 
b/code/api/src/main/java/org/apache/tamaya/Configuration.java
index e84add0..6b15a05 100644
--- a/code/api/src/main/java/org/apache/tamaya/Configuration.java
+++ b/code/api/src/main/java/org/apache/tamaya/Configuration.java
@@ -23,10 +23,7 @@ import org.apache.tamaya.spi.ConfigurationContext;
 import org.apache.tamaya.spi.ConfigurationProviderSpi;
 import org.apache.tamaya.spi.ServiceContextManager;
 
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
+import java.util.*;
 import java.util.function.Function;
 import java.util.function.UnaryOperator;
 
@@ -68,6 +65,16 @@ public interface Configuration {
     /**
      * Access a property.
      *
+     * @param keys the property's keys, in order of evaluation, not {@code 
null}.
+     * @return the property's createValue.
+     */
+    default String get(Iterable<String> keys){
+        return get(keys, TypeLiteral.of(String.class));
+    }
+
+    /**
+     * Access a property.
+     *
      * @param key the property's key, not {@code null}.
      * @param defaultValue createValue to be returned, if no createValue is 
present, not {@code null}
      * @return the property's keys.
@@ -77,6 +84,17 @@ public interface Configuration {
     }
 
     /**
+     * Access a property.
+     *
+     * @param keys the property's keys, in order of evaluation, not {@code 
null}.
+     * @param defaultValue createValue to be returned, if no createValue is 
present, not {@code null}
+     * @return the property's keys.
+     */
+    default String getOrDefault(Iterable<String> keys, String defaultValue){
+        return getOrDefault(keys, TypeLiteral.of(String.class), defaultValue);
+    }
+
+    /**
      * Access a String property, using an an {@link Optional} instance.
      *
      * @param key the property's key, not {@code null}.
@@ -87,6 +105,16 @@ public interface Configuration {
     }
 
     /**
+     * Access a String property, using an an {@link Optional} instance.
+     *
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @return the property's keys.
+     */
+    default Optional<String> getOptional(Iterable<String> keys){
+        return Optional.ofNullable(getOrDefault(keys, String.class, null));
+    }
+
+    /**
      * Access a property, using an an {@link Optional} instance.
      *
      * @param key the property's key, not {@code null}.
@@ -101,6 +129,18 @@ public interface Configuration {
     /**
      * Access a property, using an an {@link Optional} instance.
      *
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type the target type, not null.
+     * @param <T> the type of the class modeled by the type parameter
+     * @return the property's keys.
+     */
+    default <T> Optional<T> getOptional(Iterable<String> keys, Class<T> type){
+        return Optional.ofNullable(getOrDefault(keys, TypeLiteral.of(type), 
null));
+    }
+
+    /**
+     * Access a property, using an an {@link Optional} instance.
+     *
      * @param key the property's key, not {@code null}.
      * @param type the target type, not null.
      * @param <T> the type of the class modeled by the type parameter
@@ -111,6 +151,18 @@ public interface Configuration {
     }
 
     /**
+     * Access a property, using an an {@link Optional} instance.
+     *
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type the target type, not null.
+     * @param <T> the type of the class modeled by the type parameter
+     * @return the property's keys.
+     */
+    default <T> Optional<T> getOptional(Iterable<String> keys, TypeLiteral<T> 
type){
+        return Optional.ofNullable(getOrDefault(keys, type, null));
+    }
+
+    /**
      * Gets the property keys as type T. This will implicitly require a 
corresponding {@link
      * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
      * fromMap for the given String keys.
@@ -133,6 +185,22 @@ public interface Configuration {
      * fromMap for the given String keys.
      *
      * @param <T> the type of the class modeled by the type parameter
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type         The target type required, not  {@code null}.
+     * @param defaultValue createValue to be used, if no createValue is 
present, not {@code null}
+     * @return the property createValue, never {@code null}.
+     * @throws ConfigException if the keys could not be converted to the 
required target type.
+     */
+    default <T> T getOrDefault(Iterable<String> keys, Class<T> type, T 
defaultValue){
+        return getOrDefault(keys, TypeLiteral.of(type), defaultValue);
+    }
+
+    /**
+     * Gets the property keys as type T. This will implicitly require a 
corresponding {@link
+     * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
+     * fromMap for the given String keys.
+     *
+     * @param <T> the type of the class modeled by the type parameter
      * @param key          the property's absolute, or relative path, e.g. 
@code
      *                     a/b/c/d.myProperty}.
      * @param type         The target type required, not {@code null}.
@@ -144,6 +212,23 @@ public interface Configuration {
     }
 
     /**
+     * Gets the property keys as type T. This will implicitly require a 
corresponding {@link
+     * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
+     * fromMap for the given String keys.
+     *
+     * @param <T> the type of the class modeled by the type parameter
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type         The target type required, not {@code null}.
+     * @return the property createValue, never {@code null}.
+     * @throws ConfigException if the keys could not be converted to the 
required target type.
+     */
+    default <T> T get(Iterable<String> keys, Class<T> type){
+        return get(keys, TypeLiteral.of(type));
+    }
+
+
+
+    /**
      * Get the property keys as type T. This will implicitly require a 
corresponding {@link
      * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
      * literals for the given key.
@@ -163,6 +248,27 @@ public interface Configuration {
      * literals for the given key.
      *
      * @param <T> the type of the type literal
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type         The target type required, not {@code null}.
+     * @return the property createValue, never {@code null}.
+     * @throws ConfigException if the keys could not be converted to the 
required target type.
+     */
+    default <T> T get(Iterable<String> keys, TypeLiteral<T> type){
+        for(String k:keys){
+            T t = getOrDefault(k, type, null);
+            if(t!=null){
+                return t;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the property keys as type T. This will implicitly require a 
corresponding {@link
+     * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
+     * literals for the given key.
+     *
+     * @param <T> the type of the type literal
      * @param key          the property's absolute, or relative path, e.g.
      *                     {@code a/b/c/d.myProperty}, not {@code null}.
      * @param type         The target type required, not {@code null}.
@@ -173,6 +279,28 @@ public interface Configuration {
     <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue);
 
     /**
+     * Get the property keys as type T. This will implicitly require a 
corresponding {@link
+     * org.apache.tamaya.spi.PropertyConverter} to be available that is 
capable of providing type T
+     * literals for the given key.
+     *
+     * @param <T> the type of the type literal
+     * @param keys the property's keys, in evaluation order, not {@code null}.
+     * @param type         The target type required, not {@code null}.
+     * @param defaultValue default createValue to be used, if no createValue 
is present.
+     * @return the property createValue, never null.
+     * @throws ConfigException if the keys could not be converted to the 
required target type.
+     */
+    default <T> T getOrDefault(Iterable<String> keys, TypeLiteral<T> type, T 
defaultValue){
+        for(String k:keys){
+            T t = getOrDefault(k, type, null);
+            if(t!=null){
+                return t;
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
      * Access all currently known configuration properties as a full {@code 
Map<String,String>}.
      * Be aware that entries from non scannable parts of the registered {@link 
org.apache.tamaya.spi.PropertySource}
      * instances may not be contained in the result, but nevertheless be 
accessible by calling one of the
@@ -240,6 +368,24 @@ public interface Configuration {
     ConfigurationContext getContext();
 
     /**
+     * Create a snapshot, which contains the given keys.
+     *
+     * @param keys the keys, not null. If empty a full snapshot with all known 
keys is returned.
+     * @return a corresponding snapshot instance.
+     */
+    ConfigurationSnapshot getSnapshot(Iterable<String> keys);
+
+    /**
+     * Create a snapshot, which contains all known keys.
+     *
+     * @param keys the target key. If empty a full snapshot with all known 
keys is returned.
+     * @return a corresponding snapshot instance.
+     */
+    default ConfigurationSnapshot getSnapshot(String... keys){
+        return getSnapshot(Arrays.asList(keys));
+    }
+
+    /**
      * Create a new builder using this instance as its base.
      * @return a new builder, never null.
      */
@@ -356,8 +502,18 @@ public interface Configuration {
         }
 
         @Override
+        public ConfigurationSnapshot getSnapshot(Iterable<String> keys) {
+            return ConfigurationSnapshot.EMPTY;
+        }
+
+        @Override
+        public ConfigurationSnapshot getSnapshot(String... keys) {
+            return ConfigurationSnapshot.EMPTY;
+        }
+
+        @Override
         public String toString(){
-            return "Configuration<empty>";
+            return "Configuration<EMPTY>";
         }
     };
 

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/ConfigurationSnapshot.java
----------------------------------------------------------------------
diff --git 
a/code/api/src/main/java/org/apache/tamaya/ConfigurationSnapshot.java 
b/code/api/src/main/java/org/apache/tamaya/ConfigurationSnapshot.java
new file mode 100644
index 0000000..06b8a3a
--- /dev/null
+++ b/code/api/src/main/java/org/apache/tamaya/ConfigurationSnapshot.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.apache.tamaya.spi.ConfigurationContext;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * An immutable configuration snapshot containing the given keys only.
+ */
+public interface ConfigurationSnapshot extends Configuration{
+
+
+    /**
+     * The requested keys. Note that not all keys must be accessible as a 
property.
+     * @return the requested keys.
+     */
+    Set<String> getKeys();
+
+    /**
+     * Get the timestamp, when this snapshot has been taken.
+     * @return the timestamp
+     */
+    long getTimestamp();
+
+    /**
+     * An empty snapshot.
+     */
+    ConfigurationSnapshot EMPTY = new ConfigurationSnapshot() {
+
+        @Override
+        public <T> T get(String key, TypeLiteral<T> type) {
+            return null;
+        }
+
+        @Override
+        public <T> T getOrDefault(String key, TypeLiteral<T> type, T 
defaultValue) {
+            return defaultValue;
+        }
+
+        @Override
+        public Map<String, String> getProperties() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public ConfigurationContext getContext() {
+            return ConfigurationContext.EMPTY;
+        }
+
+        @Override
+        public ConfigurationSnapshot getSnapshot(Iterable<String> keys) {
+            return this;
+        }
+
+        @Override
+        public Set<String> getKeys() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public long getTimestamp() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "ConfigurationSnapshot<EMPTY>";
+        }
+    };
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/spi/ChangeSupport.java
----------------------------------------------------------------------
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/ChangeSupport.java 
b/code/api/src/main/java/org/apache/tamaya/spi/ChangeSupport.java
new file mode 100644
index 0000000..43ef685
--- /dev/null
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ChangeSupport.java
@@ -0,0 +1,41 @@
+/*
+ * 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.spi;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Enum type that describges the config change capabilities of a property 
source.
+ */
+@Experimental
+public enum ChangeSupport {
+    /**
+     * Config change is supported, this config source supports registering 
ConfigChangeListeners.
+     * {@link PropertySource#addChangeListener(BiConsumer)} .
+     */
+    SUPPORTED,
+    /**
+     * Config change is not supported. Configuration values may change, though 
changes are not reported.
+     */
+    UNSUPPORTED,
+    /**
+     * Configuration properties of this property source cannot change for the 
lifetime of this {@link PropertySource}.
+     */
+    IMMUTABLE
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
----------------------------------------------------------------------
diff --git 
a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java 
b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
index 7ce4547..b4d67ce 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
@@ -180,7 +180,7 @@ public interface ConfigurationBuilder {
      *
      * @return the current registered property converters.
      */
-    Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> 
getPropertyConverter();
+    Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverter();
 
     /**
      * Increases the priority of the given property source, by moving it 
towards the end

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
----------------------------------------------------------------------
diff --git 
a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java 
b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
index a9ecda1..86c3831 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
@@ -189,5 +189,4 @@ public interface ConfigurationContext {
         }
     };
 
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/main/java/org/apache/tamaya/spi/PropertySource.java
----------------------------------------------------------------------
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/PropertySource.java 
b/code/api/src/main/java/org/apache/tamaya/spi/PropertySource.java
index 9ede6ce..b25bbb4 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/PropertySource.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/PropertySource.java
@@ -23,6 +23,8 @@ import org.apache.tamaya.Configuration;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
 
 
 /**
@@ -172,4 +174,49 @@ public interface PropertySource{
         return true;
     }
 
+    /**
+     * Get the support for reporting changes to property sources provided by 
this instance. This support
+     * type should never change during a property source's lifetime.
+     *
+     * @return the change support of this property source, not null.
+     */
+    @Experimental
+    default ChangeSupport getChangeSupport(){
+        return ChangeSupport.UNSUPPORTED;
+    }
+
+    /**
+     * Get the current version. A new version signals that it is known that 
properties have changed for this property
+     * source. This is especially useful, when {@link #getChangeSupport()} is 
{@link ChangeSupport#SUPPORTED}.
+     * The content and format of the version String is imeplemtation specific. 
We recommend to add information
+     * such as the loading timestamp, the source systems read or whatever is 
appropriate. By default this
+     * method returns {@code "N/A"}.
+     * @return the version this property source, never null.
+     */
+    @Experimental
+    default String getVersion(){
+        return "N/A";
+    }
+
+    /**
+     * Add a change listener for this properrty source.
+     * @param l the listner, not null.
+     */
+    @Experimental
+    default void addChangeListener(BiConsumer<Set<String>, PropertySource> l){}
+
+    /**
+     * Removes a change listener for this properrty source.
+     * @param l the listner, not null.
+     */
+    @Experimental
+    default void removeChangeListener(BiConsumer<Set<String>, PropertySource> 
l){}
+
+    /**
+     * Removes all registered change listeners, if any.
+     */
+    @Experimental
+    default void removeAllChangeListeners(){}
+
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
----------------------------------------------------------------------
diff --git a/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java 
b/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
index bb68697..cd6eeaf 100644
--- a/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
+++ b/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
@@ -99,6 +99,11 @@ public class TestConfiguration implements Configuration {
     }
 
     @Override
+    public ConfigurationSnapshot getSnapshot(Iterable<String> keys) {
+        return ConfigurationSnapshot.EMPTY;
+    }
+
+    @Override
     public Map<String, String> getProperties() {
         // run toString on each createValue of the (key, createValue) 
setCurrent in VALUES
         return VALUES.entrySet().stream().collect(

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java
index f5f65e3..47a3b4c 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfiguration.java
@@ -18,11 +18,7 @@
  */
 package org.apache.tamaya.spisupport;
 
-import org.apache.tamaya.ConfigException;
-import org.apache.tamaya.ConfigOperator;
-import org.apache.tamaya.ConfigQuery;
-import org.apache.tamaya.Configuration;
-import org.apache.tamaya.TypeLiteral;
+import org.apache.tamaya.*;
 import org.apache.tamaya.spi.*;
 
 import java.util.*;
@@ -263,6 +259,11 @@ public class DefaultConfiguration implements Configuration 
{
     }
 
     @Override
+    public ConfigurationSnapshot getSnapshot(Iterable<String> keys) {
+        return new DefaultConfigurationSnapshot(this, keys);
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
index 7be4fd8..1276daa 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
@@ -42,7 +42,7 @@ public class DefaultConfigurationBuilder implements 
ConfigurationBuilder {
     protected ServiceContext serviceContext = 
ServiceContextManager.getServiceContext();
     protected List<PropertyFilter> propertyFilters = new ArrayList<>();
     protected List<PropertySource> propertySources = new ArrayList<>();
-    protected Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> 
propertyConverters = new HashMap<>();
+    protected Map<TypeLiteral<?>, List<PropertyConverter<?>>> 
propertyConverters = new HashMap<>();
     protected MetadataProvider metaDataProvider = 
serviceContext.create(MetadataProvider.class, DefaultMetaDataProvider::new);
 
     /**
@@ -293,7 +293,7 @@ public class DefaultConfigurationBuilder implements 
ConfigurationBuilder {
         checkBuilderState();
         Objects.requireNonNull(type);
         Objects.requireNonNull(propertyConverters);
-        Collection<PropertyConverter<?>> converters = 
this.propertyConverters.get(type);
+        List<PropertyConverter<?>> converters = 
this.propertyConverters.get(type);
         if(converters==null){
             converters = new ArrayList<>();
             this.propertyConverters.put(type, converters);
@@ -345,7 +345,7 @@ public class DefaultConfigurationBuilder implements 
ConfigurationBuilder {
     }
 
     @Override
-    public Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> 
getPropertyConverter() {
+    public Map<TypeLiteral<?>, List<PropertyConverter<?>>> 
getPropertyConverter() {
         return this.propertyConverters;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
index f24be88..a6d9612 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
@@ -75,7 +75,7 @@ public class DefaultConfigurationContext implements 
ConfigurationContext {
         immutablePropertyFilters = 
Collections.unmodifiableList(propertyFilters);
 
         // Finally add the converters
-        for(Map.Entry<TypeLiteral<?>, Collection<PropertyConverter<?>>> 
en:builder.getPropertyConverter().entrySet()) {
+        for(Map.Entry<TypeLiteral<?>, List<PropertyConverter<?>>> 
en:builder.getPropertyConverter().entrySet()) {
             for (@SuppressWarnings("rawtypes") PropertyConverter converter : 
en.getValue()) {
                 this.propertyConverterManager.register(en.getKey(), converter);
             }
@@ -86,7 +86,7 @@ public class DefaultConfigurationContext implements 
ConfigurationContext {
 
     public DefaultConfigurationContext(ServiceContext serviceContext,
                                        List<PropertyFilter> propertyFilters, 
List<PropertySource> propertySources,
-                                       Map<TypeLiteral<?>, 
Collection<PropertyConverter<?>>> propertyConverters,
+                                       Map<TypeLiteral<?>, 
List<PropertyConverter<?>>> propertyConverters,
                                        MetadataProvider metaDataProvider) {
         this.serviceContext = Objects.requireNonNull(serviceContext);
         this.immutablePropertyFilters = Collections.unmodifiableList(new 
ArrayList<>(propertyFilters));
@@ -94,7 +94,7 @@ public class DefaultConfigurationContext implements 
ConfigurationContext {
         this.metaDataProvider = Objects.requireNonNull(metaDataProvider);
         this.metaDataProvider.init(this);
         propertyConverterManager = new 
PropertyConverterManager(serviceContext);
-        for(Map.Entry<TypeLiteral<?>, Collection<PropertyConverter<?>>> 
en:propertyConverters.entrySet()) {
+        for(Map.Entry<TypeLiteral<?>, List<PropertyConverter<?>>> 
en:propertyConverters.entrySet()) {
             for (@SuppressWarnings("rawtypes") PropertyConverter converter : 
en.getValue()) {
                 this.propertyConverterManager.register(en.getKey(), converter);
             }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshot.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshot.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshot.java
new file mode 100644
index 0000000..4d86ce6
--- /dev/null
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshot.java
@@ -0,0 +1,212 @@
+/*
+ * 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.spisupport;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.ConfigurationSnapshot;
+import org.apache.tamaya.TypeLiteral;
+import org.apache.tamaya.spi.ConfigurationContext;
+import org.apache.tamaya.spi.PropertyConverter;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * /**
+ * 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/createValue pairs are considered.
+ */
+public class DefaultConfigurationSnapshot implements ConfigurationSnapshot, 
Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The properties frozen.
+     */
+    private Configuration snapshot;
+    private long frozenAt = System.nanoTime();
+    private UUID id = UUID.randomUUID();
+    private transient ConfigurationContext context;
+    private Set<String> keys = new HashSet<>();
+
+    /**
+     * Constructor.
+     *
+     * @param config The base configuration.
+     * @param keys The keys to evaluate, not null.
+     */
+    public DefaultConfigurationSnapshot(Configuration config, Iterable<String> 
keys) {
+        for(String k:keys) {
+            this.keys.add(k);
+        }
+        ConfigurationContext ctx = config.getContext();
+        MetadataProvider metadataProvider = 
ctx.getServiceContext().getService(MetadataProvider.class,
+                DefaultMetaDataProvider::new);
+        context = new DefaultConfigurationContext(ctx.getServiceContext(),
+                ctx.getPropertyFilters(),
+                ctx.getPropertySources().stream()
+                        .map(ps -> DefaultPropertySourceSnapshot.of(ps, 
this.keys)).collect(Collectors.toList()),
+                ctx.getPropertyConverters(),
+                metadataProvider);
+        this.snapshot = new DefaultConfiguration(context);
+        if(this.keys.isEmpty()){
+            this.keys.addAll(this.snapshot.getProperties().keySet());
+        }
+        this.keys = Collections.unmodifiableSet(this.keys);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param config The base configuration.
+     */
+    public DefaultConfigurationSnapshot(Configuration config) {
+        ConfigurationContext ctx = config.getContext();
+        MetadataProvider metadataProvider = 
ctx.getServiceContext().getService(MetadataProvider.class,
+                DefaultMetaDataProvider::new);
+        context = new DefaultConfigurationContext(ctx.getServiceContext(),
+                ctx.getPropertyFilters(),
+                ctx.getPropertySources().stream()
+                        .map(ps -> new 
DefaultPropertySourceSnapshot(ps)).collect(Collectors.toList()),
+                ctx.getPropertyConverters(),
+                metadataProvider);
+        this.snapshot = new DefaultConfiguration(context);
+        this.keys = 
Collections.unmodifiableSet(this.snapshot.getProperties().keySet());
+    }
+
+
+    @Override
+    public ConfigurationSnapshot getSnapshot(Iterable<String> keys) {
+        return new DefaultConfigurationSnapshot(this, keys);
+    }
+
+    /**
+     * Get the evaluated keys of this frozen coinfiguration.
+     * @return the keys, not null.
+     */
+    public Set<String> getKeys() {
+        return keys;
+    }
+
+    @Override
+    public String get(String key) {
+        return this.snapshot.get(key);
+    }
+
+    @Override
+    public String getOrDefault(String key, String defaultValue) {
+        return this.snapshot.getOrDefault(key, defaultValue);
+    }
+
+    @Override
+    public <T> T getOrDefault(String key, Class<T> type, T defaultValue) {
+        return this.snapshot.getOrDefault(key, type, defaultValue);
+    }
+
+    @SuppressWarnings("unchecked")
+       @Override
+    public <T> T get(String key, Class<T> type) {
+        return snapshot.get(key, type);
+    }
+
+    /**
+     * Accesses the current String createValue for the given key and tries to 
convert it
+     * using the {@link PropertyConverter} instances provided by the current
+     * {@link 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 createValue type
+     * @return the converted createValue, never null.
+     */
+    @Override
+    public <T> T get(String key, TypeLiteral<T> type) {
+        return snapshot.get(key, type);
+    }
+
+    @Override
+    public <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue) 
{
+        return snapshot.getOrDefault(key, type, defaultValue);
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        return snapshot.getProperties();
+    }
+
+    @Override
+    public ConfigurationContext getContext() {
+        return snapshot.getContext();
+    }
+
+    /**
+     * <p>Returns the moment in time when this frozen configuration has been 
created.</p>
+     *
+     * <p>The time is taken from {@linkplain System#currentTimeMillis()}</p>
+     *
+     * @see System#currentTimeMillis()
+     * @return the moment in time when this configuration has been created
+     */
+    @Override
+    public long getTimestamp() {
+        return frozenAt;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultConfigurationSnapshot that = (DefaultConfigurationSnapshot) o;
+
+        if (frozenAt != that.frozenAt) {
+            return false;
+        }
+        return Objects.equals(snapshot, that.snapshot);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(frozenAt, snapshot);
+    }
+
+    @Override
+    public String toString() {
+        return "FrozenConfiguration{" +
+                "id=" + getId() + "," +
+                "frozenAt=" + frozenAt + "," +
+                "config=" + snapshot +
+                '}';
+    }
+
+    /**
+     * <p>Returns the unique id of this frozen configuration.</p>
+     *
+     * @return the unique id of this frozen configuration, never {@code null}
+     */
+    public UUID getId() {
+        return id;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultPropertySourceSnapshot.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultPropertySourceSnapshot.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultPropertySourceSnapshot.java
new file mode 100644
index 0000000..7c14f5a
--- /dev/null
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultPropertySourceSnapshot.java
@@ -0,0 +1,211 @@
+/*
+ * 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.spisupport;
+
+import org.apache.tamaya.spi.ChangeSupport;
+import org.apache.tamaya.spi.PropertySource;
+import org.apache.tamaya.spi.PropertyValue;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * 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/createValue pairs are considered.
+ */
+public class DefaultPropertySourceSnapshot implements PropertySource, 
Serializable {
+    private static final long serialVersionUID = -6373137316556444171L;
+    private static final int MAX_SYNCH_CHECKS = 10;
+    private static final Logger LOG = 
Logger.getLogger(DefaultPropertySourceSnapshot.class.getName());
+
+    /**
+     * The PropertySource's name.
+     */
+    private String name;
+    /**
+     * The ordinal.
+     */
+    private int ordinal;
+    /**
+     * The properties read.
+     */
+    private Map<String, PropertyValue> properties = new HashMap<>();
+
+    private Set<String> keys = new HashSet<>();
+
+    private long frozenAt = System.currentTimeMillis();
+
+    /**
+     * Constructor.
+     *
+     * @param propertySource The base PropertySource.
+     */
+    public DefaultPropertySourceSnapshot(PropertySource propertySource) {
+        this(propertySource, propertySource.getProperties().keySet());
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param propertySource The base PropertySource.
+     * @param keys
+     */
+    public DefaultPropertySourceSnapshot(PropertySource propertySource, 
Iterable<String> keys) {
+        for(String k:keys){
+            this.keys.add(k);
+        }
+        if(this.keys.isEmpty()){
+            this.keys.addAll(getProperties().keySet());
+        }
+        this.keys = Collections.unmodifiableSet(this.keys);
+        this.ordinal = PropertySourceComparator.getOrdinal(propertySource);
+        this.name = propertySource.getName();
+        if(propertySource.getChangeSupport().equals(ChangeSupport.UNSUPPORTED) 
||
+            propertySource.getChangeSupport().equals(ChangeSupport.IMMUTABLE)){
+            // simply get the keys and we are done. We cant do more...
+            this.properties = initProperties(propertySource, false);
+        }else{
+            this.properties = initProperties(propertySource, true);
+        }
+    }
+
+    private Map<String, PropertyValue> initProperties(PropertySource 
propertySource, boolean checkVersion) {
+        Map<String, PropertyValue> properties = new HashMap<>();
+        if(!checkVersion){
+            // Simply collect values and we are done
+            for(String key:keys) {
+                PropertyValue val = propertySource.get(key);
+                if(val != null) {
+                    properties.put(key, val);
+                }
+            }
+        }else {
+            // Collect values, but ensure, the propert
+            String version = propertySource.getVersion();
+            String newVersion = null;
+            int checksDone = 0;
+            while (!Objects.equals(newVersion, version)) {
+                for (String key : keys) {
+                    PropertyValue val = propertySource.get(key);
+                    if (val != null) {
+                        properties.put(key, val);
+                    }
+                }
+                newVersion = propertySource.getVersion();
+                if (checksDone++ > MAX_SYNCH_CHECKS) {
+                    LOG.info("Property Source is instable, will abort freeze, 
but inconsistent config may be possible: " + propertySource.getName());
+                    break;
+                }
+
+            }
+        }
+        return Collections.unmodifiableMap(properties);
+    }
+
+    /**
+     * Creates a new FrozenPropertySource instance based on a PropertySource 
and the target key set given. This method
+     * uses all keys available in the property map.
+     *
+     * @param propertySource the property source to be frozen, not null.
+     * @return the frozen property source.
+     */
+    public static DefaultPropertySourceSnapshot of(PropertySource 
propertySource) {
+        Set<String> keySet = propertySource.getProperties().keySet();
+        return DefaultPropertySourceSnapshot.of(propertySource, keySet);
+    }
+
+    /**
+     * Creates a new FrozenPropertySource instance based on a PropertySource 
and the target key set given.
+     *
+     * @param propertySource the property source to be frozen, not null.
+     * @param keys the keys to be evaluated for the snapshot. Only these keys 
will be contained in the resulting
+     *             snapshot.
+     * @return the frozen property source.
+     */
+    public static DefaultPropertySourceSnapshot of(PropertySource 
propertySource, Iterable<String> keys) {
+        if (propertySource instanceof DefaultPropertySourceSnapshot) {
+            DefaultPropertySourceSnapshot fps = 
(DefaultPropertySourceSnapshot) propertySource;
+            if(fps.getKeys().equals(keys)){
+                return fps;
+            }
+        }
+        return new DefaultPropertySourceSnapshot(propertySource, keys);
+    }
+
+    public Set<String> getKeys() {
+        return keys;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    public int getOrdinal() {
+        return this.ordinal;
+    }
+
+    /**
+     * Get the creation timestamp of this instance.
+     * @return the creation timestamp
+     */
+    public long getFrozenAt(){
+        return frozenAt;
+    }
+
+    @Override
+    public PropertyValue get(String key) {
+        return this.properties.get(key);
+    }
+
+    @Override
+    public Map<String, PropertyValue> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof DefaultPropertySourceSnapshot)) {
+            return false;
+        }
+        DefaultPropertySourceSnapshot that = (DefaultPropertySourceSnapshot) 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 +
+                ", frozenAt=" + frozenAt +
+                '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceChangeSupport.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceChangeSupport.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceChangeSupport.java
new file mode 100644
index 0000000..b587f91
--- /dev/null
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertySourceChangeSupport.java
@@ -0,0 +1,171 @@
+/*
+ * 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.spisupport;
+
+import org.apache.tamaya.spi.ChangeSupport;
+import org.apache.tamaya.spi.PropertySource;
+import org.apache.tamaya.spi.PropertyValue;
+
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Simple support class for helping with change management on property sources.
+ */
+public final class PropertySourceChangeSupport {
+
+    private static final Logger LOG = 
Logger.getLogger(PropertySourceChangeSupport.class.getName());
+
+    private ChangeSupport changeSupport;
+    private PropertySource propertySource;
+    private AtomicLong version = new AtomicLong();
+    private List<BiConsumer<Set<String>, PropertySource>> listeners  = new 
ArrayList<>();
+    private int oldHash = 0;
+    private Map<String, PropertyValue> valueMap;
+    private long timestamp;
+    private ScheduledFuture scheduleTask;
+
+    private static ScheduledExecutorService executorService = 
Executors.newScheduledThreadPool(4);
+
+    /**
+     * Create a new property change support instance.
+     * @param changeSupport the support type, not null.
+     * @param propertySource the propertySource, not null.
+     */
+    public PropertySourceChangeSupport(ChangeSupport changeSupport, 
PropertySource propertySource){
+        this.changeSupport = Objects.requireNonNull(changeSupport);
+        this.propertySource = Objects.requireNonNull(propertySource);
+    }
+
+    public ChangeSupport getChangeSupport() {
+        return changeSupport;
+    }
+
+    public String getVersion() {
+        return version.get() + ": timestamp="+timestamp;
+    }
+
+    public void addChangeListener(BiConsumer<Set<String>, PropertySource> l){
+        switch(changeSupport){
+            case SUPPORTED:
+                if(!listeners.contains(l)){
+                    listeners.add(l);
+                }
+                break;
+            case UNSUPPORTED:
+            case IMMUTABLE:
+                default:
+                break;
+        }
+    }
+
+    public void removeChangeListener(BiConsumer<Set<String>, PropertySource> 
l){
+        listeners.remove(l);
+    }
+
+    public void removeAllChangeListeners(){
+        listeners.clear();
+    }
+
+    public long update(Map<String, PropertyValue> props){
+        Set<String> changedKeys = calculateChangedKeys(this.valueMap, props);
+        if(!changedKeys.isEmpty()) {
+            this.valueMap = props;
+            long newVersion = version.incrementAndGet();
+            fireListeners(changedKeys);
+            return newVersion;
+        }
+        return version.get();
+    }
+
+    private Set<String> calculateChangedKeys(Map<String, PropertyValue> 
valueMap, Map<String, PropertyValue> newValues) {
+        Set<String> result = new HashSet<>();
+        if(this.valueMap!=null) {
+            for (Map.Entry<String, PropertyValue> en : valueMap.entrySet()) {
+                if (!newValues.containsKey(en.getKey())) {
+                    result.add(en.getKey()); // removed
+                }
+            }
+        }
+        for(Map.Entry<String, PropertyValue> en:newValues.entrySet()){
+            if(valueMap != null){
+                if(!valueMap.containsKey(en.getKey())) {
+                    result.add(en.getKey()); // added
+                }if(!Objects.equals(valueMap.get(en.getKey()), en.getValue())){
+                    result.add(en.getKey()); // changed
+                }
+            }else{
+                result.add(en.getKey()); // added
+            }
+        }
+        return result;
+    }
+
+    private void fireListeners(Set<String> changedKeys) {
+        for(BiConsumer<Set<String>, PropertySource> l:this.listeners){
+            try{
+                l.accept(changedKeys, propertySource);
+            }catch(Exception e){
+                LOG.log(Level.WARNING, "Failed to update listener on property 
source change: " + l, e);
+            }
+        }
+    }
+
+    public PropertyValue getValue(String key){
+       return valueMap.get(key);
+    }
+
+    public Map<String, PropertyValue> getProperties(){
+        if(valueMap==null){
+            return Collections.emptyMap();
+        }
+        return Collections.unmodifiableMap(valueMap);
+    }
+
+    public void scheduleChangeMonitor(Supplier<Map<String, PropertyValue>> 
propsSupplier, long duration, TimeUnit timeUnit){
+        scheduleTask = executorService.schedule(() -> {
+            update(propsSupplier.get());
+        }, duration, timeUnit);
+    }
+
+    public void cancelSchedule(){
+        if(scheduleTask!=null){
+            scheduleTask.cancel(false);
+        }
+    }
+
+    private int hashCode(Map<String, PropertyValue> valueMap) {
+        int result = 0;
+        for(Map.Entry<String,PropertyValue> en:valueMap.entrySet()) {
+            result = 31 * result + en.getKey().hashCode();
+            String value = en.getValue().getValue();
+            result = 31 * result + (value!=null?value.hashCode():0);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java
index bf75fb9..cca4d96 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BasePropertySource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.logging.Level;
@@ -37,6 +38,14 @@ public abstract class BasePropertySource implements 
PropertySource{
     private volatile Integer ordinal;
     /** The name of the property source. */
     private String name;
+    /** The optional prefix. */
+    private String prefix;
+    /**
+     * If true, this property source does not return any properties. This is 
useful since this
+     * property source is applied by default, but can be switched off by 
setting the
+     * {@code tamaya.envprops.disable} system/environment property to {@code 
true}.
+     */
+    private boolean disabled = false;
 
     /**
      * Constructor.
@@ -66,7 +75,6 @@ public abstract class BasePropertySource implements 
PropertySource{
         this.defaultOrdinal = defaultOrdinal;
     }
 
-
     /**
      * Constructor, using a default ordinal of 0.
      */
@@ -76,6 +84,9 @@ public abstract class BasePropertySource implements 
PropertySource{
 
     @Override
     public String getName() {
+        if(disabled){
+            return name + "(disabled)";
+        }
         return name;
     }
 
@@ -142,6 +153,22 @@ public abstract class BasePropertySource implements 
PropertySource{
         return val;
     }
 
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+    public boolean isDisabled() {
+        return disabled;
+    }
+
+    public void setDisabled(boolean disabled) {
+        this.disabled = disabled;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -167,6 +194,30 @@ public abstract class BasePropertySource implements 
PropertySource{
     protected String toStringValues() {
         return  "  defaultOrdinal=" + defaultOrdinal + '\n' +
                 "  ordinal=" + ordinal  + '\n' +
+                "  prefix=" + prefix + '\n' +
+                "  disabled=" + disabled + '\n' +
                 "  name='" + name + '\''  + '\n';
     }
+
+    protected Map<String,PropertyValue> mapProperties(Map<String, String> 
props, long timestamp) {
+        Map<String,PropertyValue> result = new HashMap<>();
+        String timestampVal = String.valueOf(timestamp);
+        if (prefix == null) {
+            for (Map.Entry<String, String> en : props.entrySet()) {
+                result.put(en.getKey(),
+                        PropertyValue.createValue(en.getKey(), en.getValue())
+                                .setMeta("source", getName())
+                                .setMeta("timestamp", timestampVal));
+            }
+        } else {
+            for (Map.Entry<String, String> en : props.entrySet()) {
+                result.put(prefix + en.getKey(),
+                        PropertyValue.createValue(prefix + en.getKey(), 
en.getValue())
+                                .setMeta("source", getName())
+                                .setMeta("timestamp", timestampVal));
+            }
+        }
+        return result;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java
index a5a5a05..df2a311 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/BuildablePropertySource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertySource;
 import org.apache.tamaya.spi.PropertyValue;
 
@@ -63,8 +64,8 @@ public class BuildablePropertySource implements 
PropertySource{
     }
 
     @Override
-    public boolean isScannable() {
-        return true;
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java
index f7bf8a3..502af4b 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/CLIPropertySource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertyValue;
 
 import java.util.*;
@@ -130,6 +131,11 @@ public class CLIPropertySource extends BasePropertySource {
     }
 
     @Override
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
+    }
+
+    @Override
     protected String toStringValues() {
         return  super.toStringValues() +
                 "  args=" + Arrays.toString(args) + '\n';

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java
index f16903f..5f981ec 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/EnvironmentPropertySource.java
@@ -18,6 +18,7 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertyValue;
 
 import java.util.Collections;
@@ -266,6 +267,11 @@ public class EnvironmentPropertySource extends 
BasePropertySource {
         return disabled;
     }
 
+    @Override
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
+    }
+
     /**
      * <p>Provides access to the system properties used to configure
      * {@linkplain EnvironmentPropertySource}.</p>

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java
index b0348f6..0759918 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/JavaConfigurationPropertySource.java
@@ -19,10 +19,7 @@
 package org.apache.tamaya.spisupport.propertysource;
 
 import org.apache.tamaya.ConfigException;
-import org.apache.tamaya.spi.ClassloaderAware;
-import org.apache.tamaya.spi.PropertySource;
-import org.apache.tamaya.spi.PropertyValue;
-import org.apache.tamaya.spi.ServiceContextManager;
+import org.apache.tamaya.spi.*;
 import org.apache.tamaya.spisupport.PropertySourceComparator;
 
 import java.io.IOException;
@@ -127,6 +124,11 @@ public class JavaConfigurationPropertySource extends 
BasePropertySource implemen
     }
 
     @Override
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
+    }
+
+    @Override
     public String toString() {
         return "JavaConfigurationPropertySource{" +
                 "enabled=" + enabled +

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java
index a4a9bd7..e93e79f 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/MapPropertySource.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertyValue;
 
 import java.util.Collections;
@@ -47,37 +48,33 @@ public class MapPropertySource extends BasePropertySource {
 
     /**
      * Creates a new instance, hereby using the default mechanism for 
evaluating the property source's
-     * priority, but applying a custom mapping {@code prefix} to the entries 
provided.
+     * priority, but applying a custom mapping {@code rootContext} to the 
entries provided.
      *
-     * @param name        unique name of this source.
+     * @param name unique name of this source.
      * @param props       the properties
      * @param prefix      the prefix context mapping, or null (for no mapping).
      */
-    public MapPropertySource(String name, Map<String, String> props, String 
prefix) {
-        super(name);
-        if (prefix == null) {
-            for (Map.Entry<String, String> en : props.entrySet()) {
-                this.props.put(en.getKey(),
-                        PropertyValue.of(en.getKey(), en.getValue(), name));
-            }
-        } else {
-            for (Map.Entry<String, String> en : props.entrySet()) {
-                this.props.put(prefix + en.getKey(),
-                        PropertyValue.of(prefix + en.getKey(), en.getValue(), 
name));
-            }
-        }
+    public MapPropertySource(String name, Properties props, String prefix) {
+        this(name, getMap(props), prefix);
     }
 
     /**
      * Creates a new instance, hereby using the default mechanism for 
evaluating the property source's
-     * priority, but applying a custom mapping {@code rootContext} to the 
entries provided.
+     * priority, but applying a custom mapping {@code prefix} to the entries 
provided.
      *
-     * @param name unique name of this source.
+     * @param name        unique name of this source.
      * @param props       the properties
      * @param prefix      the prefix context mapping, or null (for no mapping).
      */
-    public MapPropertySource(String name, Properties props, String prefix) {
-        this(name, getMap(props), prefix);
+    public MapPropertySource(String name, Map<String, String> props, String 
prefix) {
+        super(name);
+        setPrefix(prefix);
+        this.props.putAll(mapProperties(props, System.currentTimeMillis()));
+    }
+
+    @Override
+    public Map<String, PropertyValue> getProperties() {
+        return Collections.unmodifiableMap(this.props);
     }
 
     /**
@@ -93,10 +90,9 @@ public class MapPropertySource extends BasePropertySource {
         return result;
     }
 
-
     @Override
-    public Map<String, PropertyValue> getProperties() {
-        return Collections.unmodifiableMap(this.props);
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java
index e9f70cf..f9704d7 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/PropertiesResourcePropertySource.java
@@ -18,23 +18,30 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
+import org.apache.tamaya.spi.PropertySource;
+import org.apache.tamaya.spi.PropertyValue;
 import org.apache.tamaya.spi.ServiceContextManager;
+import org.apache.tamaya.spisupport.PropertySourceChangeSupport;
 
 import java.io.InputStream;
 import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
  * Simple {@link org.apache.tamaya.spi.PropertySource}, with a fixed ordinal 
that reads a .properties file from a given URL.
  */
-public class PropertiesResourcePropertySource extends MapPropertySource {
+public class PropertiesResourcePropertySource extends BasePropertySource {
     /** The logger used. */
     private static final Logger LOGGER = 
Logger.getLogger(PropertiesResourcePropertySource.class.getName());
 
+    private volatile PropertySourceChangeSupport cachedProperties = new 
PropertySourceChangeSupport(
+            ChangeSupport.SUPPORTED, this);
+
     /**
      * Creates a new instance.
      * @param url the resource URL, not null.
@@ -49,7 +56,11 @@ public class PropertiesResourcePropertySource extends 
MapPropertySource {
      * @param url the resource URL, not null.
      */
     public PropertiesResourcePropertySource(URL url, String prefix){
-        super(url.toExternalForm(), loadProps(url), prefix);
+        super(url.toExternalForm());
+        setPrefix(prefix);
+        this.cachedProperties.update(loadProps(url));
+        this.cachedProperties.scheduleChangeMonitor(() -> loadProps(url),
+                120, TimeUnit.SECONDS);
     }
 
     /**
@@ -58,7 +69,7 @@ public class PropertiesResourcePropertySource extends 
MapPropertySource {
      * @param path the resource path, not null.
      */
     public PropertiesResourcePropertySource(String path, String prefix){
-        super(path, loadProps(path, 
Thread.currentThread().getContextClassLoader()), prefix);
+        this(path, prefix, ServiceContextManager.getDefaultClassLoader());
     }
 
     /**
@@ -68,7 +79,11 @@ public class PropertiesResourcePropertySource extends 
MapPropertySource {
      * @param cl the class loader.
      */
     public PropertiesResourcePropertySource(String path, String prefix, 
ClassLoader cl){
-        super(path, loadProps(path, cl), prefix);
+        super(path);
+        setPrefix(prefix);
+        this.cachedProperties.update(loadProps(path, cl));
+        this.cachedProperties.scheduleChangeMonitor(() -> loadProps(path, cl),
+                120, TimeUnit.SECONDS);
     }
 
     /**
@@ -76,7 +91,7 @@ public class PropertiesResourcePropertySource extends 
MapPropertySource {
      * @param path the resource classpath, not null.
      * @return the loaded properties.
      */
-    private static Map<String, String> loadProps(String path, ClassLoader cl) {
+    private Map<String, PropertyValue> loadProps(String path, ClassLoader cl) {
         URL url = 
ServiceContextManager.getServiceContext(cl).getResource(path);
         return loadProps(url);
     }
@@ -86,22 +101,48 @@ public class PropertiesResourcePropertySource extends 
MapPropertySource {
      * @param url the resource URL, not null.
      * @return the loaded properties.
      */
-    private static Map<String, String> loadProps(URL url) {
-        Map<String,String> result = new HashMap<>();
+    private Map<String, PropertyValue> loadProps(URL url) {
         if(url!=null) {
             try (InputStream is = url.openStream()) {
                 Properties props = new Properties();
                 props.load(is);
-                for (Map.Entry<?,?> en : props.entrySet()) {
-                    result.put(en.getKey().toString(), 
en.getValue().toString());
-                }
+                return mapProperties(MapPropertySource.getMap(props), 
System.currentTimeMillis());
             } catch (Exception e) {
                 LOGGER.log(Level.WARNING, "Failed to read properties from " + 
url, e);
             }
         }else{
             LOGGER.log(Level.WARNING, "No properties found at " + url);
         }
-        return result;
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public void addChangeListener(BiConsumer<Set<String>, PropertySource> l) {
+        this.cachedProperties.addChangeListener(l);
+    }
+
+    @Override
+    public void removeChangeListener(BiConsumer<Set<String>, PropertySource> 
l) {
+        this.cachedProperties.removeChangeListener(l);
+    }
+
+    @Override
+    public void removeAllChangeListeners() {
+        this.cachedProperties.removeAllChangeListeners();
+    }
+
+    @Override
+    public Map<String, PropertyValue> getProperties() {
+        return cachedProperties.getProperties();
     }
 
+    @Override
+    public String getVersion() {
+        return cachedProperties.getVersion();
+    }
+
+    @Override
+    public ChangeSupport getChangeSupport() {
+        return ChangeSupport.SUPPORTED;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SimplePropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SimplePropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SimplePropertySource.java
index 690a809..8a7567b 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SimplePropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SimplePropertySource.java
@@ -19,6 +19,7 @@
 package org.apache.tamaya.spisupport.propertysource;
 
 import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertyValue;
 
 import java.io.File;
@@ -122,6 +123,11 @@ public class SimplePropertySource extends 
BasePropertySource {
         return this.properties;
     }
 
+    @Override
+    public ChangeSupport getChangeSupport(){
+        return ChangeSupport.IMMUTABLE;
+    }
+
     /**
      * loads the Properties from the given URL
      *

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SystemPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SystemPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SystemPropertySource.java
index f3e9dd1..57c18b2 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SystemPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/SystemPropertySource.java
@@ -18,12 +18,16 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertyValue;
+import org.apache.tamaya.spisupport.PropertySourceChangeSupport;
 
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This {@link org.apache.tamaya.spi.PropertySource} manages the system 
properties. You can disable this feature by
@@ -36,25 +40,10 @@ public class SystemPropertySource extends 
BasePropertySource {
      */
     public static final int DEFAULT_ORDINAL = 1000;
 
-    private volatile Map<String,PropertyValue> cachedProperties;
+    private volatile PropertySourceChangeSupport cachedProperties = new 
PropertySourceChangeSupport(
+            ChangeSupport.SUPPORTED, this);
+    private AtomicInteger savedHashcode = new AtomicInteger();
 
-    /**
-     * previous System.getProperties().hashCode()
-     * so we can check if we need to reload
-     */
-    private volatile int previousHash;
-
-    /**
-     * Prefix that allows system properties to virtually be mapped on 
specified sub section.
-     */
-    private String prefix;
-
-    /**
-     * If true, this property source does not return any properties. This is 
useful since this
-     * property source is applied by default, but can be switched off by 
setting the
-     * {@code tamaya.envprops.disable} system/environment property to {@code 
true}.
-     */
-    private boolean disabled = false;
 
     /**
      * Creates a new instance. Also initializes the {@code prefix} and {@code 
disabled} properties
@@ -67,8 +56,8 @@ public class SystemPropertySource extends BasePropertySource {
     public SystemPropertySource(){
         super("system-properties", DEFAULT_ORDINAL);
         initFromSystemProperties();
-        if(!disabled){
-            cachedProperties = Collections.unmodifiableMap(loadProperties());
+        if(!isDisabled()){
+            reload();
         }
     }
 
@@ -83,8 +72,9 @@ public class SystemPropertySource extends BasePropertySource {
     private void initFromSystemProperties() {
         String value = System.getProperty("tamaya.sysprops.prefix");
         if(value==null){
-            prefix = System.getenv("tamaya.sysprops.prefix");
+            value = System.getenv("tamaya.sysprops.prefix");
         }
+        setPrefix(value);
         value = System.getProperty("tamaya.sysprops.disable");
         if(value==null){
             value = System.getenv("tamaya.sysprops.disable");
@@ -96,7 +86,7 @@ public class SystemPropertySource extends BasePropertySource {
             value = System.getenv("tamaya.defaults.disable");
         }
         if(value!=null && !value.isEmpty()) {
-            this.disabled = Boolean.parseBoolean(value);
+            setDisabled(Boolean.parseBoolean(value));
         }
     }
 
@@ -114,7 +104,7 @@ public class SystemPropertySource extends 
BasePropertySource {
      * @param ordinal the ordinal to be used.
      */
     public SystemPropertySource(String prefix, int ordinal){
-        this.prefix = prefix;
+        setPrefix(prefix);
         setOrdinal(ordinal);
     }
 
@@ -123,86 +113,48 @@ public class SystemPropertySource extends 
BasePropertySource {
      * @param prefix the prefix to be used, or null.
      */
     public SystemPropertySource(String prefix){
-        this.prefix = prefix;
+        setPrefix(prefix);
     }
 
 
     private Map<String, PropertyValue> loadProperties() {
-        Properties sysProps = System.getProperties();
-        previousHash = System.getProperties().hashCode();
-        final String prefix = this.prefix;
-        Map<String, PropertyValue> entries = new HashMap<>();
-        for (Map.Entry<Object,Object> entry : sysProps.entrySet()) {
-            if(entry.getKey() instanceof String && entry.getValue() instanceof 
String) {
-                if (prefix == null) {
-                    entries.put((String) entry.getKey(),
-                            PropertyValue.of((String) entry.getKey(),
-                                    (String) entry.getValue(),
-                                    getName()));
-                } else {
-                    entries.put(prefix + entry.getKey(),
-                            PropertyValue.of(prefix + entry.getKey(),
-                                    (String) entry.getValue(),
-                                    getName()));
-                }
-            }
-        }
-        return entries;
-    }
-
-    @Override
-    public String getName() {
-        if(disabled){
-            return super.getName() + "(disabled)";
-        }
-        return super.getName();
+        Map<String, String> props = 
MapPropertySource.getMap(System.getProperties());
+        return mapProperties(props, System.currentTimeMillis());
     }
 
     @Override
     public PropertyValue get(String key) {
-        if(disabled){
+        if(isDisabled()){
             return null;
         }
         reload();
-        String prefix = this.prefix;
-        if(prefix==null) {
-            String value =  System.getProperty(key);
-            if(value == null){
-                return null;
-            }
-            return PropertyValue.of(key, value, getName());
+        return this.cachedProperties.getValue(key);
+    }
+
+    public void reload() {
+        int hashCode = System.getProperties().hashCode();
+        if(hashCode!=this.savedHashcode.get()) {
+            this.savedHashcode.set(hashCode);
+            this.cachedProperties.update(loadProperties());
         }
-        return PropertyValue.of(key, 
System.getProperty(key.substring(prefix.length())), getName());
     }
 
     @Override
     public Map<String, PropertyValue> getProperties() {
-        if(disabled){
+        if(isDisabled()){
             return Collections.emptyMap();
         }
         reload();
-        return this.cachedProperties;
+        return cachedProperties.getProperties();
     }
 
-    public void reload() {
-        // only need to reload and fill our map if something has changed
-        // synchronization was removed, Instance was marked as volatile. In 
the worst case it
-        // is reloaded twice, but the values will be the same.
-        if (previousHash != System.getProperties().hashCode()) {
-            Map<String, PropertyValue> properties = loadProperties();
-            this.cachedProperties = Collections.unmodifiableMap(properties);
-        }
+    public String getVersion(){
+        return cachedProperties.getVersion();
     }
 
     @Override
-    public boolean isScannable() {
-        return true;
+    public ChangeSupport getChangeSupport() {
+        return ChangeSupport.SUPPORTED;
     }
 
-    @Override
-    protected String toStringValues() {
-        return  super.toStringValues() +
-                "  prefix=" + prefix + '\n' +
-                "  disabled=" + disabled + '\n';
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/WrappedPropertySource.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/WrappedPropertySource.java
 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/WrappedPropertySource.java
index feaaf7b..265dbb4 100644
--- 
a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/WrappedPropertySource.java
+++ 
b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/propertysource/WrappedPropertySource.java
@@ -18,12 +18,15 @@
  */
 package org.apache.tamaya.spisupport.propertysource;
 
+import org.apache.tamaya.spi.ChangeSupport;
 import org.apache.tamaya.spi.PropertySource;
 import org.apache.tamaya.spi.PropertyValue;
 import org.apache.tamaya.spisupport.PropertySourceComparator;
 
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiConsumer;
 
 /**
  * Property source effectively managed by the configuration context, allowing 
resetting of ordinal and its
@@ -94,6 +97,31 @@ class WrappedPropertySource implements PropertySource{
         return delegate.isScannable();
     }
 
+    @Override
+    public ChangeSupport getChangeSupport() {
+        return delegate.getChangeSupport();
+    }
+
+    @Override
+    public String getVersion() {
+        return delegate.getVersion();
+    }
+
+    @Override
+    public void addChangeListener(BiConsumer<Set<String>, PropertySource> l) {
+        delegate.addChangeListener(l);
+    }
+
+    @Override
+    public void removeChangeListener(BiConsumer<Set<String>, PropertySource> 
l) {
+        delegate.removeChangeListener(l);
+    }
+
+    @Override
+    public void removeAllChangeListeners() {
+        delegate.removeAllChangeListeners();
+    }
+
     public PropertySource getDelegate() {
         return delegate;
     }
@@ -119,6 +147,7 @@ class WrappedPropertySource implements PropertySource{
                 "name=" + getName() +
                 ", ordinal=" + getOrdinal() +
                 ", scannable=" + isScannable() +
+                ", changeSupport=" + getChangeSupport() +
                 ", loadedAt=" + loaded +
                 ", delegate-class=" + delegate.getClass().getName() +
                 '}';

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilderTest.java
 
b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilderTest.java
index a4487e7..a756b14 100644
--- 
a/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilderTest.java
+++ 
b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilderTest.java
@@ -18,8 +18,8 @@
  */
 package org.apache.tamaya.spisupport;
 
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.*;
+
 import org.apache.tamaya.Configuration;
 import org.apache.tamaya.ConfigurationProvider;
 import org.apache.tamaya.TypeLiteral;
@@ -27,10 +27,6 @@ import org.apache.tamaya.spi.ConfigurationBuilder;
 import org.apache.tamaya.spi.*;
 import org.junit.Test;
 
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Map;
-
 import static org.assertj.core.api.Assertions.*;
 
 /**
@@ -312,7 +308,7 @@ public class DefaultConfigurationBuilderTest {
                 .addPropertyConverters(TypeLiteral.of(String.class), 
converter1, converter2);
         Configuration cfg = b.build();
         ConfigurationContext ctx = cfg.getContext();
-        Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> buildConverters 
= b.getPropertyConverter();
+        Map<TypeLiteral<?>, List<PropertyConverter<?>>> buildConverters = 
b.getPropertyConverter();
         
assertThat(ctx.getPropertyConverters(TypeLiteral.of(String.class)).contains(converter1)).isTrue();
         
assertThat(ctx.getPropertyConverters(TypeLiteral.of(String.class)).contains(converter2)).isTrue();
         assertThat(ctx.getPropertyConverters()).hasSize(1);
@@ -346,7 +342,7 @@ public class DefaultConfigurationBuilderTest {
                 .addPropertyConverters(TypeLiteral.of(String.class), 
Arrays.asList(converter1, converter2));
         Configuration cfg = b.build();
         ConfigurationContext ctx = cfg.getContext();
-        Map<TypeLiteral<?>, Collection<PropertyConverter<?>>> buildConverters 
= b.getPropertyConverter();
+        Map<TypeLiteral<?>, List<PropertyConverter<?>>> buildConverters = 
b.getPropertyConverter();
         
assertThat(ctx.getPropertyConverters(TypeLiteral.of(String.class)).contains(converter1)).isTrue();
         
assertThat(ctx.getPropertyConverters(TypeLiteral.of(String.class)).contains(converter2)).isTrue();
         assertThat(ctx.getPropertyConverters()).hasSize(1);

http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/5f3f86b0/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshotTest.java
----------------------------------------------------------------------
diff --git 
a/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshotTest.java
 
b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshotTest.java
new file mode 100644
index 0000000..b351c91
--- /dev/null
+++ 
b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/DefaultConfigurationSnapshotTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.spisupport;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.ConfigurationSnapshot;
+import org.apache.tamaya.spi.ConfigurationContext;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+
+public class DefaultConfigurationSnapshotTest {
+
+    @Test
+    public void getFrozenAtReturnsTheCorrectTimestamp() throws 
InterruptedException {
+        Configuration source = Mockito.mock(Configuration.class);
+        
Mockito.when(source.getContext()).thenReturn(ConfigurationContext.EMPTY);
+        
Mockito.when(source.getSnapshot(Mockito.anyCollection())).thenReturn(ConfigurationSnapshot.EMPTY);
+        
Mockito.when(source.getSnapshot()).thenReturn(ConfigurationSnapshot.EMPTY);
+        
Mockito.when(source.getProperties()).thenReturn(Collections.emptyMap());
+
+        long poiStart = System.nanoTime();
+        Thread.sleep(10L);
+        DefaultConfigurationSnapshot fc = new 
DefaultConfigurationSnapshot(source);
+        Thread.sleep(10L);
+
+        long poiEnd = System.nanoTime();
+
+        assertTrue(fc.getTimestamp()>poiStart);
+        assertTrue(fc.getTimestamp()<poiEnd);
+    }
+
+
+    @Test
+    public void idMustBeNotNull() {
+        Configuration source = Mockito.mock(Configuration.class);
+
+        
Mockito.when(source.getContext()).thenReturn(ConfigurationContext.EMPTY);
+        
Mockito.when(source.getProperties()).thenReturn(Collections.emptyMap());
+
+        DefaultConfigurationSnapshot fc = new 
DefaultConfigurationSnapshot(source);
+
+        assertNotNull(fc);
+    }
+
+    /*
+     * All tests for equals() and hashCode() go here...
+     */
+    @Test
+    public void 
twoFrozenAreDifferentIfTheyHaveADifferentIdAndFrozenAtTimestamp() {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("key", "createValue");
+
+        Configuration configuration = Mockito.mock(Configuration.class);
+        
Mockito.when(configuration.getContext()).thenReturn(ConfigurationContext.EMPTY);
+        doReturn(properties).when(configuration).getProperties();
+
+        DefaultConfigurationSnapshot fcA = new 
DefaultConfigurationSnapshot(configuration);
+        DefaultConfigurationSnapshot fcB = new 
DefaultConfigurationSnapshot(configuration);
+
+        assertNotEquals(fcA, fcB);
+    }
+
+    /*
+     * END OF ALL TESTS for equals() and hashCode()
+     */
+}
\ No newline at end of file


Reply via email to