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
