You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2014/12/26 01:56:10 UTC
[3/4] incubator-tamaya git commit: TAMAYA-19: Streamlined API and
impl.
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/java/org/apache/tamaya/core/spi/DefaultServiceContextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/spi/DefaultServiceContextProvider.java b/core/src/main/java/org/apache/tamaya/core/spi/DefaultServiceContextProvider.java
new file mode 100644
index 0000000..9a2fb1b
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/spi/DefaultServiceContextProvider.java
@@ -0,0 +1,112 @@
+/*
+ * 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.core.spi;
+
+import org.apache.tamaya.spi.ServiceContext;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class implements the (default) {@link org.apache.tamaya.spi.ServiceContext} interface and hereby uses the JDK
+ * {@link java.util.ServiceLoader} to load the services required.
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+class DefaultServiceContextProvider implements ServiceContext {
+ /** List current services loaded, per class. */
+ private final ConcurrentHashMap<Class, List<Object>> servicesLoaded = new ConcurrentHashMap<>();
+ /** Singletons. */
+ private final ConcurrentHashMap<Class, Optional<?>> singletons = new ConcurrentHashMap<>();
+ /** Comparator for ordering of multiple services found. */
+ private DefaultServiceComparator serviceComparator;
+
+ public DefaultServiceContextProvider(){
+ serviceComparator = new DefaultServiceComparator(getServices(OrdinalProvider.class, Collections.emptyList()));
+ }
+
+ @Override
+ public <T> Optional<T> getService(Class<T> serviceType) {
+ Optional<T> cached = (Optional<T>)singletons.get(serviceType);
+ if(cached==null) {
+ List<? extends T> services = getServices(serviceType, Collections.emptyList());
+ if (services.isEmpty()) {
+ cached = Optional.empty();
+ }
+ else{
+ cached = Optional.of(services.get(0));
+ }
+ singletons.put(serviceType, cached);
+ }
+ return cached;
+ }
+
+ /**
+ * Loads and registers services.
+ *
+ * @param serviceType
+ * The service type.
+ * @param <T>
+ * the concrete type.
+ * @param defaultList
+ * the list current items returned, if no services were found.
+ * @return the items found, never {@code null}.
+ */
+ @Override
+ public <T> List<? extends T> getServices(final Class<T> serviceType, final List<? extends T> defaultList) {
+ List<T> found = (List<T>) servicesLoaded.get(serviceType);
+ if (found != null) {
+ return found;
+ }
+ return loadServices(serviceType, defaultList);
+ }
+
+ /**
+ * Loads and registers services.
+ *
+ * @param serviceType The service type.
+ * @param <T> the concrete type.
+ * @param defaultList the list current items returned, if no services were found.
+ *
+ * @return the items found, never {@code null}.
+ */
+ private <T> List<? extends T> loadServices(final Class<T> serviceType, final List<? extends T> defaultList) {
+ try {
+ List<T> services = new ArrayList<>();
+ for (T t : ServiceLoader.load(serviceType)) {
+ services.add(t);
+ }
+ if(services.isEmpty()){
+ services.addAll(defaultList);
+ }
+ if(!serviceType.equals(OrdinalProvider.class)) {
+ services.sort(serviceComparator);
+ }
+ services = Collections.unmodifiableList(services);
+ final List<T> previousServices = (List<T>) servicesLoaded.putIfAbsent(serviceType, (List<Object>)services);
+ return previousServices != null ? previousServices : services;
+ } catch (Exception e) {
+ Logger.getLogger(DefaultServiceContextProvider.class.getName()).log(Level.WARNING,
+ "Error loading services current type " + serviceType, e);
+ return defaultList;
+ }
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/java/org/apache/tamaya/core/spi/Orderable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/spi/Orderable.java b/core/src/main/java/org/apache/tamaya/core/spi/Orderable.java
new file mode 100644
index 0000000..5397776
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/spi/Orderable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.core.spi;
+
+/**
+ * Interface that can be optionally implemented by SPI components to be loaded into
+ * the Tamaya's ServiceContext. The ordinal provided will be used to determine
+ * priority and precedence, when multiple components implement the same
+ * service interface.
+ */
+@FunctionalInterface
+public interface Orderable {
+ /**
+ * Get the ordinal keys for the component, by default 0.
+ * @return the ordinal keys
+ */
+ int order();
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/java/org/apache/tamaya/core/spi/OrdinalProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/spi/OrdinalProvider.java b/core/src/main/java/org/apache/tamaya/core/spi/OrdinalProvider.java
new file mode 100644
index 0000000..2d7f057
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/spi/OrdinalProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.core.spi;
+
+import java.util.OptionalInt;
+
+/**
+ * The ordinal provider is an optional component that provides an abstraction for ordering/prioritizing
+ * services loaded. This can be used to determine, which SPI should be used, if multiple instances are
+ * available, or for ordering chain of services.
+ * @see org.apache.tamaya.spi.ServiceContext
+ */
+public interface OrdinalProvider {
+ /**
+ * Evaluate the ordinal number for the given type.
+ * @param type the target type, not null.
+ * @return the ordinal, if not defined, 0 should be returned.
+ */
+ OptionalInt getOrdinal(Class<?> type);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/java/org/apache/tamaya/core/spi/PropertyAdapterProviderSpi.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/spi/PropertyAdapterProviderSpi.java b/core/src/main/java/org/apache/tamaya/core/spi/PropertyAdapterProviderSpi.java
new file mode 100644
index 0000000..b8e7122
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/spi/PropertyAdapterProviderSpi.java
@@ -0,0 +1,36 @@
+/*
+ * 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.core.spi;
+
+import org.apache.tamaya.PropertyAdapter;
+
+/**
+ * This service provides different {@link org.apache.tamaya.PropertyAdapter} instances for types.
+ */
+public interface PropertyAdapterProviderSpi {
+
+ /**
+ * Called, when a given {@link org.apache.tamaya.Configuration} has to be evaluated.
+ *
+ * @return the corresponding {@link java.util.function.Function<String, T>}, or {@code null}, if
+ * not available for the given target type.
+ */
+ <T> PropertyAdapter<T> getPropertyAdapter(Class<T> type);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.CodecSpi
----------------------------------------------------------------------
diff --git a/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.CodecSpi b/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.CodecSpi
deleted file mode 100644
index c0a61aa..0000000
--- a/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.CodecSpi
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy current the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-org.apache.tamaya.core.internal.config.DefaultCodecSpi
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertyAdapterSpi
----------------------------------------------------------------------
diff --git a/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertyAdapterSpi b/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertyAdapterSpi
new file mode 100644
index 0000000..0554453
--- /dev/null
+++ b/core/src/main/resources/META-INF/services/org.apache.tamaya.spi.PropertyAdapterSpi
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy current the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+org.apache.tamaya.core.internal.config.DefaultPropertyAdapterSpi
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/test/java/org/apache/tamaya/ucs/UC1ReadProperties.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/tamaya/ucs/UC1ReadProperties.java b/core/src/test/java/org/apache/tamaya/ucs/UC1ReadProperties.java
index 7571c1e..50e1753 100644
--- a/core/src/test/java/org/apache/tamaya/ucs/UC1ReadProperties.java
+++ b/core/src/test/java/org/apache/tamaya/ucs/UC1ReadProperties.java
@@ -26,11 +26,10 @@ import static org.junit.Assert.assertNotNull;
import java.util.Map;
import java.util.stream.Collectors;
-import org.apache.tamaya.AggregationPolicy;
import org.apache.tamaya.Configuration;
-import org.apache.tamaya.MetaInfo;
import org.apache.tamaya.PropertySource;
import org.apache.tamaya.core.config.ConfigFunctions;
+import org.apache.tamaya.core.properties.AggregationPolicy;
import org.apache.tamaya.core.properties.PropertySourceBuilder;
import org.junit.Test;
@@ -88,7 +87,6 @@ public class UC1ReadProperties {
assertNotNull(config);
assertTrue(config.isEmpty());
assertTrue(config.getProperties().isEmpty());
- assertFalse(config.isMutable());
}
@Test
@@ -97,7 +95,6 @@ public class UC1ReadProperties {
assertNotNull(provider);
assertTrue(provider.isEmpty());
assertTrue(provider.getProperties().isEmpty());
- assertFalse(provider.isMutable());
}
@Test
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/core/src/test/java/org/apache/tamaya/ucs/UC2CombineProperties.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/tamaya/ucs/UC2CombineProperties.java b/core/src/test/java/org/apache/tamaya/ucs/UC2CombineProperties.java
index 3a6e471..60ab6ea 100644
--- a/core/src/test/java/org/apache/tamaya/ucs/UC2CombineProperties.java
+++ b/core/src/test/java/org/apache/tamaya/ucs/UC2CombineProperties.java
@@ -18,10 +18,9 @@
*/
package org.apache.tamaya.ucs;
-import org.apache.tamaya.AggregationPolicy;
import org.apache.tamaya.ConfigException;
-import org.apache.tamaya.MetaInfo;
import org.apache.tamaya.PropertySource;
+import org.apache.tamaya.core.properties.AggregationPolicy;
import org.apache.tamaya.core.properties.PropertySourceBuilder;
import org.junit.Test;
@@ -34,7 +33,7 @@ import org.junit.Test;
public class UC2CombineProperties {
/**
- * The most common use cases is aggregating two property config to new provider, hereby {@link org.apache.tamaya.AggregationPolicy}
+ * The most common use cases is aggregating two property config to new provider, hereby {@link org.apache.tamaya.core.properties.AggregationPolicy}
* defines the current variants supported.
*/
@Test
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/docs/src/main/asciidoc/design/2_API.adoc
----------------------------------------------------------------------
diff --git a/docs/src/main/asciidoc/design/2_API.adoc b/docs/src/main/asciidoc/design/2_API.adoc
new file mode 100644
index 0000000..69a06c1
--- /dev/null
+++ b/docs/src/main/asciidoc/design/2_API.adoc
@@ -0,0 +1,469 @@
+// 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.
+<<<
+[[API]]
+== The Tamaya API
+=== Overview
+Though Tamaya is a very powerful and flexible solution there are basically only a few simple core concepts required that build
+the base of all the other mechanisms:
+
+The API provides these artifacts, which are:
+
+* A simple but complete SE API for accessing key/value based _Configuration_.
+* _Configuration_ hereby models configuration and as well provides the static entry point to access configuration.
+ _ Configuration_ provides
+ ** access to literal key/value pairs.
+ ** +PropertyAdapter+ support to convert String values to arbitrary non-String types for type safe configuration access.
+ ** functional extension points (+with,query+) based un +UnaryOperator<Configuration>+ (operator) and +Function<Configuration,T>+ (query).
+ ** provides static access to the current +Configuration+ (default configuration)
+ ** provides static access to the additional named +Configuration+ instances
+ ** a service to inject configuration into beans, including listener and callback support
+ ** a service for creating onfiguration _templates_ based on interfaces.
+* +PropertyAdapter+ defines a functional interface for converting String values into any required target types. It also
+ provides static access to the adapters registered to implement transparent type conversion as needed, if possible.
+* _PropertySource:_ is the the SPI for a source that provides configuration data. A +PropertySource+
+ hereby
+ ** is designed as a minimalistic data interface to be implemented by any kind of data providers (local or remote)
+ ** provides data key/value pairs in raw format as String key/values only
+ ** can optionally support scanning of its provided values
+
+* _Annotations_ a set of annotations allows to configure configuration injection on classes or interface (aka config templates).
+
+The SPI contains the following core concepts/artifacts:
+
+* _ServiceContext_ is the delegate singleton that is used by the framework to resolve components. The effective component
+ loading can be accessed by implementing and registering an instance of +ServiceContextProvider+ using +java.util.ServiceLoader+.
+* All the singleton used explicitly (+Configuration, PropertyAdapter+) are backed up corresponding API interfaces
+ (+ConfigurationSpi, PropertyAdapterSpi+).
+ To override a singleton's behaviour the corresponding SPI has to be implemented and registered, so it can be loaded
+ by the current +ServiceContext+ setup (by default ServiceLoader based).
+
+This is also reflected in the main parts of the API, which is quite small:
+
+* +org.apache.tamaya+ contains the main abstractions +Configuration, ConfigQuery, PropertyAdapter,
+ PropertySource+ and +ConfigException+
+* +org.apache.tamaya.spi+ contains the SPI interfaces to be implemented by implementations and the +ServiceContextManager+
+ mechanism (+ConfigurationSpi, PropertyAdapterSpi, ServiceContext+).
++ +org.apache.tamaya.annot+ contains the annotations defined to control configuration injection.
+
+So basically an implementation has to implement the SPIs provided. The +ServiceContext+ only has to be overridden, when
+a default SE +java.util.ServiceLoader+ mechanism is not sufficient.
+
+[[APIKeyValues]]
+=== Key/Value Pairs
+
+Basically configuration is a very generic concept. Therefore it should be modelled in a generic way. The most simple
+and most commonly used approach are simple literal key/value pairs. So the core building block of {name} are key/value pairs.
+You can think of a common +.properties+ file, e.g.
+
+[source,properties]
+.A simple properties file
+--------------------------------------------
+a.b.c=cVal
+a.b.c.1=cVal1
+a.b.c.2=cVal2
+a=aVal
+a.b=abVal
+a.b2=abVal
+--------------------------------------------
+
+Now you can use +java.util.Properties+ to read this file and access the corresponding properties, e.g.
+
+[source,properties]
+.Accessing some properties
+--------------------------------------------
+Properties props = new Properties();
+props.readProperties(...);
+String val = props.getProperty("a.b.c");
+val = props.getProperty("a.b.c.1");
+...
+--------------------------------------------
+
+This looks familiar to most of you. Nevertheless when looking closer to the above key/value pairs,
+there are more things in place: looking at the keys +a.b.c+, +a.b.c.1+, +a.b.c.2+, +a+, +a.b+ we
+see that the key names build up a flattened tree structure. So we can define the following:
+
+Given a key +p1.p2.p3.k=value+:
+
+* +p1.p2.p3.k+ is called the _qualified key_
+* +p1.p2.p3+ is the key's _area_
+* the child areas +p1.p2", "p1+ are called _areas_ as well
+* +k+ is the _(unqualified) key_
+
+This terminology is used also later ta some locations. Nevertheless given that you can perform some very useful actions:
+
+* you can filter the keys with an area. E.g. in the example before you can query for all keys within the area +a.b.c+
+ and map them to new property set.
+* you can access all child keys of an area
+* you can evaluate the areas present.
+* ...and more.
+
+All this kind of actions (and more) must not be included in the API, because they can be modelled as +ConfigQuery+ instances and
+implemented/provided by implementation code.
+
+
+==== Why Using Strings Only
+
+There are good reason to keep of non String-values as core storage representation of configuration. Mostly
+there are several huge advantages:
+
+* Strings are simple to understand
+* Strings are human readable and therefore easy to prove for correctness
+* Strings can easily be used within different language, different VMs, files or network communications.
+* Strings can easily be compared and manipulated
+* Strings can easily be searched, indexed and cached
+* It is very easy to provide Strings as configuration, which gives much flexibility for providing configuration in
+ production as well in testing.
+* and more...
+
+On the other side there are also disadvantages:
+
+* Strings are inherently not type safe, they do not provide validation out of the box for special types, such as
+numbers, dates etc.
+* In many cases you want to access configuration in a typesafe way avoiding conversion to the target types explicitly
+ throughout your code.
+* Strings are neither hierarchical nor multi-valued, so mapping hierarchical and collection structures requires some
+ extra efforts.
+
+Nevertheless most of these advantages can be mitigated easily, hereby still keeping all the benefits from above:
+
+* Adding type safe adapters on top of String allow to add any type easily, that can be directly mapped out of Strings.
+ This includes all common base types such as numbers, dates, time, but also timezones, formatting patterns and more.
+* Also multi-valued, complex and collection types can be defined as a corresponding +PropertyAdapter+ knows how to
+ parse and create the target instance required.
+* String s also can be used as references pointing to other locations and formats, where configuration is
+ accessible.
+
+
+[API PropertySource]
+=== PropertySource
+==== Basic Model
+
+We have seen that constraining configuration aspects to simple literal key/value pairs provides us with an easy to
+understand, generic, flexible, yet expendable mechanism. Looking at the Java language features a +java.util.Map<String,
+String>+ and +java.util.Properties+ basically model these aspects out of the box.
+
+Though there are advantages in using these types as a model, there are some severe drawbacks, mostly implementation
+of these types is far not trivial or the basic model has sever drawbacks, because of backward compatibility with
+the original collection API.
+
+To make implementation of a custom property source as convinient as possible only the following methods were
+identified to be necessary:
+
+[source,java]
+.Interface PropertySource
+--------------------------------------------
+public interface PropertySource{
+
+ Optional<String> get(String key);
+ boolean isBrowseable();
+ Map<String, String> getProperties();
+
+}
+--------------------------------------------
+
+Hereby
+
+* +get+ looks similar to the methods on +Map+, though +get+ uses the +Optional+ type introduced
+ with Java 8. This avoids returning +null+ or throwing exceptions in case no such entry is available and also
+ reduces the API's footprint, since default values can be easily implemented by calling +Optional.orElse+ and
+ similar methods.
+* +getProperties+ allows to extract mapped data to a +Map+. Other methods like +containsKey, keySet+ as well as
+ streaming operations then can be applied on the returned +Map+ instance.
+* But not in all scenarios a property source may be browseable. This can be evaluated by calling +isBrowseable()+.
+
+This interface can be implemented by any kind of logic. It could be a simple in memory map, a distributed configuration
+provided by a data grid, a database, the JNDI tree or other resources. Or it can be a combination of multiple
+property sources with additional combination/aggregation rules in place.
+
+==== Meta Information
+
+Meta information is not explicitly modelled, since it can be easily added by some key naming schemes. E.g. look at
+the example below, which return a map of all metadata keys for +a.b.c+.:
+
+[source,java]
+.Modelling Meta Data
+--------------------------------------------
+PropertySource src = ...;
+Map<String, String> metaData = src.getArea("a.b.c[meta]");
+--------------------------------------------
+
+The API does not provide any explicit support for meta-data, whereas implementations may provide metadata editors
+or readers.
+
+==== Mutability
+
+In general Property sources can be modeled as mutable. Nevertheless the API does not support out of the box mutability,
+due to the following reasons:
+
+* Mutability is rather complex
+* Mutability is only rarely required
+* Mutability can be implemented in various ways
+
+As a consequence mutability mechanisms may be provided by implementations as needed, but are not part of the API.
+
+
+[[API Configuration]]
+=== Configuration
+==== Basic Model: Extending PropertySource
+
++Configuration+ inherits all basic features from +PropertySource+, but additionally adds functionality for
+type safety and external features of any interacting with configuration:
+
+[source,java]
+.Interface Configuration
+--------------------------------------------
+public interface Configuration extends PropertySource{
+ // type support
+ default Optional<Boolean> getBoolean(String key);
+ default OptionalInt getInteger(String key);
+ default OptionalLong getLong(String key);
+ default OptionalDouble getDouble(String key);
+ default <T> Optional<T> getAdapted(String key, PropertyAdapter<T> adapter);
+ <T> Optional<T> get(String key, Class<T> type);
+
+ // extension points
+ default Configuration with(UnaryOperator<Configuration> operator);
+ default <T> T query(ConfigQuery<T> query);
+}
+--------------------------------------------
+
+Hereby
+
+* +XXX getXXX(String)+ provide type safe accessors for all basic wrapper types of the JDK. Basically all this
+ methods delegate to the +getAdapted+ method, additionally passing the required +PropertyAdapter+.
+* +getAdapted+ allow accessing any type, hereby also passing a +PropertyAdapter+ that converts
+ the configured literal value to the type required.
+* +with, query+ provide the extension points for adding additional functionality.
+
+Additionally +Configuration+ provides several access methods:
+
+[source,java]
+.Interface Configuration
+--------------------------------------------
+public interface Configuration extends PropertySource{
+ ...
+
+ // accessors for configuration
+ public static Configuration current();
+ public static Configuration current(String name);
+ public static boolean isAvailable(String name);
+ // accessors for template and injection
+ public static <T> T createTemplate(Class<T> template, Configuration... configurations);
+ public static void configure(Object instance, Configuration... configurations);
+}
+--------------------------------------------
+
+Hereby
+* +current()+ returns the _default_ +Configuration+
+* +current(String name)+ returns a named +Configuration+ (there may be arbitrary additional +Configuration+ instance
+ additionally to the default +Configuration+ instance.
+* +isAvailable(String name)+ allows to determine if a named +Configuration+ is available.
+* +createTemplate(Class<T> template, Configuration... configurations)+ allows to create a new template instance based
+ on a (optionally) annotated interface. The according getter methods are backed up and implemented by Tamaya based
+ on the configuration values available. The +configurations+ parameter allows parts of +Configuration+ instances to be
+ passed that override any instances available through +current(name), current()+.
+* +configure+ performs injection of configured values on a (optionally) annotated non abstract type.
+ The +configurations+ parameter allows parts of +Configuration+ instances to be
+ passed that override any instances available through +current(name), current()+.
+
+
+[[TypeConversion]]
+==== Type Conversion
+
+Configuration extend +PropertySource+ and adds additional support for non String types. This is achieved
+with the help of +PropertyAdapter+ instances:
+
+[source,java]
+.PropertyAdapter
+--------------------------------------------
+@FunctionalInterface
+public interface PropertyAdapter<T>{
+ T adapt(String value);
+}
+--------------------------------------------
+
++PropertyAdapter+ instances can be implemented manually or registered and accessed from the
++PropertyAdaper+ using static methods. Hereby the exact mechanism is determined by the implementation
+of +PropertyAdapterSpi+ backing up the static methods.
+By default corresponding +PropertyAdapter+ instances can be registered using the Java +ServiceLoader+
+mechanism, or programmatically ba calling the +register(Class, PropertyAdapter)+.
+
+[source,java]
+.PropertyAdapter
+--------------------------------------------
+@FunctionalInterface
+public interface PropertyAdapter<T>{
+ T adapt(String value);
+
+ public static <T> PropertyAdapter<T> register(Class<T> targetType, PropertyAdapter<T> adapter);
+ public static boolean isTargetTypeSupported(Class<?> targetType);
+ public static <T> PropertyAdapter<T> getAdapter(Class<T> targetType);
+ public static <T> PropertyAdapter<T> getAdapter(Class<T> targetType, WithPropertyAdapter annotation);
+}
+--------------------------------------------
+
+The now a typed instance of a +Configuration+ is required, by default the +Configuration+ implementation acquires
+a matching +PropertyAdapter+. If one is found it can easily pass the String value from its String property store
+for converting it to the required target type. In the normal case for the mostly needed types this is completely
+transparent to the user.
+But basically this mechanism can also be used for adaptive filtering of values accessed. As an example lets assume
+we want to decode an encryped password on the fly, so we can achieve this with as less code as follows:
+
+[source,java]
+.Simple Filtering Adapter Use Case
+--------------------------------------------
+Configuration config = Configuration.cuirrent();
+String decryptedPassword = config.getAdapted(String.class, "user.password", p -> PKI.decrypt(p));
+--------------------------------------------
+
+[[Injection]]
+=== Inversion of Control
+==== Overview
+
+Inversion of Control (aka IoC/the Hollywood Principle) has proven to be very handy and effective in avoiding boilerplate
+code. In Java there are different frameworks available that all provide IoC mechanisms. Unfortunately IoC is not a
+built-in language feature. So for a portable solution OOTB that works also in Java SE Tamaya itself has to provide the
+according injection services. As an example refer to the following example:
+
+[source,java]
+.Annotated Example Class
+--------------------------------------------
+public class ConfiguredClass{
+
+ // resolved by default, using property name, class and package name
+ private String testProperty;
+
+ @ConfiguredProperty(config="pluginConfig", keys={"a.b.c.key1","a.b.legacyKey"})
+ @ConfiguredProperty(config="productConfig", keys="area1.key2")
+ @DefaultValue("The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.")
+ String value1;
+
+ @ConfiguredProperty(keys="a.b.c.key2")
+ private int value2;
+
+ // resolved by default
+ @DefaultValue("http://127.0.0.1:8080/res/api/v1/info.json")
+ private URL accessUrl;
+
+ // Config injection disabled for this property
+ @NoConfig
+ private Integer int1;
+
+ @ConfiguredProperty("BD")
+ @WithAdapter(MyBigDecimalRoundingAdapter.class)
+ private BigDecimal bigNumber;
+
+ ...
+}
+--------------------------------------------
+
+The class does not show all (but most all) the possibilities that are provided. Configuring an instance of the
+class using Tamaya is very simple:
+
+[source,java]
+.Configuring the +ConfiguredClass+ Instance
+--------------------------------------------
+ConfiguredClass classInstance = new ConfiguredClass();
+Configuration.configure(configuredClass);
+--------------------------------------------
+
+==== The Annotations in detail
+
+tbd
+
+The +Configuration+ interface provides static methods that allow to any kind of instances be configured
+ny just passing the instances calling +Configuration.configure(instance);+. The classes passed hereby must
+be annotated with +@ConfiguredProperty+ to define the configured properties. Hereby this annotation can be
+used in multiple ways and combined with other annotations such as +@DefaultValue+,
++@WithLoadPolicy+, +@WithConfig+, +@WithConfigOperator+, +@WithPropertyAdapter+.
+
+To illustrate the mechanism below the most simple variant of a configured class is given:
+
+[source,java]
+.Most simple configured class
+--------------------------------------------
+pubic class ConfiguredItem{
+ @ConfiguredProperty
+ private String aValue;
+}
+--------------------------------------------
+
+When this class is configured, e.g. by passing it to +Configuration.configure(Object)+,
+the following is happening:
+
+* The current valid +Configuration+ is evaluated by calling +Configuration cfg = Configuration.of();+
+* The current property value (String) is evaluated by calling +cfg.get("aValue");+
+* if not successful, an error is thrown (+ConfigException+)
+* On success, since no type conversion is involved, the value is injected.
+* The configured bean is registered as a weak change listener in the config system's underlying
+ configuration, so future config changes can be propagated (controllable by applying the
+ +@WithLoadPolicy+ annotation).
+
+In the next example we explicitly define the property value:
+[source,java]
+--------------------------------------------
+pubic class ConfiguredItem{
+
+ @ConfiguredProperty
+ @ConfiguredProperty("a.b.value")
+ @configuredProperty("a.b.deprecated.value")
+ @DefaultValue("${env:java.version}")
+ private String aValue;
+}
+--------------------------------------------
+
+Within this example we evaluate multiple possible keys. Evaluation is aborted if a key could be successfully
+resolved. Hereby the ordering of the annotations define the ordering of resolution, so in the example above
+resolution equals to +"aValue", "a.b.value", "a.b.deprecated.value"+. If no value could be read
+from the configuration, it uses the value from the +@DefaultValue+ annotation. Interesting here
+is that this value is not static, it is evaluated by calling +Configuration.evaluateValue(Configuration, String)+.
+
+
+=== Extension Points
+
+We are well aware of the fact that this library will not be able to cover all kinds of use cases. Therefore
+we have added functional extension mechanisms to +Configuration+ that were used in other areas of the Java eco-system as well:
+
+* +with(UnaryOperator<Configuration> operator)+ allows to pass arbitrary functions that take adn return instances of +Configuration+.
+ They can be used to cover use cases such as filtering, configuration views, security interception and more.
+* +query(Function<Configuration,T> query)+ ConfigQuery+ defines a function returning any kind of result based on a
+ configuration instance. Queries are used for accessing/deriving any kind of data of a +Configuration+ instance,
+ e.g. accessing a +Set<String>+ of area keys present.
+
+Both interfaces hereby are functional interfaces, defined in +java.util.function+ and can be applied using Lambdas or
+method references:
+
+[source,java]
+.Applying a Configuration Query
+--------------------------------------------
+ConfigSecurity securityContext = Configuration.current().query(ConfigSecurity::targetSecurityContext);
+--------------------------------------------
+
+NOTE: +ConfigSecurity+ is an arbitrary class.
+
+Or an operator calls looks as follows:
+
+[source,java]
+.Applying a Configuration Operators
+--------------------------------------------
+Configuration secured = Configuration.current().with(ConfigSecurity::secure);
+--------------------------------------------
+
+
+== SPI
+
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/a60570e8/docs/src/main/asciidoc/design/2_CoreConcepts.adoc
----------------------------------------------------------------------
diff --git a/docs/src/main/asciidoc/design/2_CoreConcepts.adoc b/docs/src/main/asciidoc/design/2_CoreConcepts.adoc
deleted file mode 100644
index 831a71f..0000000
--- a/docs/src/main/asciidoc/design/2_CoreConcepts.adoc
+++ /dev/null
@@ -1,849 +0,0 @@
-// 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.
-<<<
-[[CoreConcepts]]
-== {name} Core Concepts
-Though {name} is a very powerful and flexible solution there are basically only a few simple core concepts required that build
-the base of all the other mechanisms:
-
-The API contains the following core concepts/artifacts:
-
-* Literal Key/Value Pairs
-* _PropertySource:_ is the the SPI for a source that provides configuration data. A +PropertySource+
- hereby defines
- ** a minimalistic SPI to be implemented by the config data source
- ** provides data key/value pairs in raw format as String key/values only
- ** providers should not have any dependencies other than to the datasource
- ** providers may read context dependent data, but basically providers themselves are not contextual.
- Context management should be done by the ConfigurationProvider implementation that also is responsible
- for combining a set of property providers to a Configuration.
- _Configuration_ is the API that users of Tamaya will see, when they access configuration in raw format. Hereby +Configuration+
- ** adds type support for non String types
- ** provides functional extension points (+with,query+)
- ** allows registering/deregistering of change listeners
- ** is the entry point for evaluating the current +Configuration+
- ** each +PropertySource+ can be easily converted into a +Configuration+
- ** allows configuration entries to be injected
- ** to access configuration _templates_ (annotated interfaces).
- ** Configuration may support mutability by allowing instances of +ConfigChangeSet+ to be passed.
-* _PropertySourceBuilder_ allows to aggregate different property sources. Hereby property sources are
- seen as sets, which can be combined to new sources using set styled operations (aggregation, intersection, subtraction).
- This allows to model and create composite sources, to build up more complex configuration models
- step by step.
-* _MetaInfo_ is provided by each +Configuration, PropertySource+ and describes the configuration/provider and its entries.
-* _Environment_ is the base model for modelling the environment and the accessor for getting the current +Environment+ instance.
-* _Annotations_ a set of annotations allows to configure configuration injection on classes or interface (aka config templates).
-
-The SPI contains the following core concepts/artifacts:
-
-* _ServiceContext_ is the delegate singleton that is used by the framework to resolve components. The effective component
- loading can be accessed by implementing and registering an instance of +ServiceContextProvider+ using +java.util.ServiceLoader+.
-* All the singleton used explicitly (+Environment,Configuration+ are backed up corresponding API interfaces.
- To override a singleton's behaviour the corresponding SPI has to be implemented and registered, so it can be loaded
- by the current +ServiceContext+ setup (by default ServiceLoader based).
-* Also the singleton used implicitly by +Configuration, Environment+ are backed up corresponding SPI interfaces.
- To override a singleton's behaviour the corresponding SPI has to be implemented and registered, so it can be loaded
- by the current +ServiceContext+ setup (by default ServiceLoader based).
-
-This is also reflected in the main parts of the API, which is quite small:
-
-* +org.apache.tamaya+ contains the main abstractions +Configuration, ConfigQuery, PropertyAdapter,
- Environment, PropertySource, MetaInfo+
-* +org.apache.tamaya.spi+ contains the SPI interfaces to be implemented by implementations and the +ServiceContext+ mechanism.
-+ +org.apache.tamaya.annot+ contains the annotations defined.
-
-In the implementation are there are additional projects:
-
-* +org.apache.tamaya.core+ contains the core implementation of the API. Deploying it together with the API results in a
- flexible framework that can be easily used for configuration of different complexity. But out of the box this framework
- will not do much more than exposing system and environment properties, its power comes when an additional meta-model
- is defined and deployed. Hereby you can write your own, or use on e of the provided ones (see later).
-* the core part is extended by multiple additional modules
- ** CDI integration
- ** Default configuration meta-models and providers for the most common usage scenarios
- *** standalone applications
- *** Java EE
- *** ...
-
-These parts are explained in the following sections. It is recommended that user's of the API read through this part.
-All subsequent parts are building upon this concepts and may be more difficult to understand without having read
-this section.
-
-
-[[APIKeyValues]]
-=== Key/Value Pairs
-
-Basically configuration is a very generic concept. Therefore it should be modelled in a generic way. The most simple
-and similarly most commonly used are simple literal key/value pairs. So the core building block of {name} are key/value pairs.
-You can think of a common +.properties+ file, e.g.
-
-[source,properties]
-.A simple properties file
---------------------------------------------
-a.b.c=cVal
-a.b.c.1=cVal1
-a.b.c.2=cVal2
-a=aVal
-a.b=abVal
-a.b2=abVal
---------------------------------------------
-
-Now you can use +java.util.Properties+ to read this file and access the corresponding properties, e.g.
-
-[source,properties]
-.Accessing some properties
---------------------------------------------
-Properties props = new Properties();
-props.readProperties(...);
-String val = props.getProperty("a.b.c");
-val = props.getProperty("a.b.c.1");
-...
---------------------------------------------
-
-This looks familiar to most of you. Nevertheless when looking closer to the above key/value pairs,
-there are more concepts in place: looking at the keys +a.b.c+, +a.b.c.1+, +a.b.c.2+, +a+, +a.b+ we
-see that the key names build up a flattened tree structure. So we can define the following:
-
-Given a key +p1.p2.p3.k=value+:
-
-* +p1.p2.p3.k+ is called the _qualified key_
-* +p1.p2.p3+ is the key's _area_
-* the child areas +p1.p2", "p1+ are called _areas_ as well
-* +k+ is the _(unqualified) key_
-
-Given that you can perform some very useful actions:
-
-* you can filter the keys with an area. E.g. in the example before you can query for all keys within the area +a.b.c+
- and map them to new properties set as follows:
-
-[source,properties]
-.Accessing an area
---------------------------------------------
-1=cVal1
-2=cVal2
---------------------------------------------
-
-Similarly accessing the area +a+ results in the following properties:
-
-[source,properties]
-.Accessing the area +a+
---------------------------------------------
-b=abVal
-b2=abVal
---------------------------------------------
-
-Additionally you can access all values of an area recursively, so accessing +a+ recursively results in
-the following properties:
-
-[source,properties]
-.Accessing area +a+ recursively
---------------------------------------------
-b.c=cVal
-b.c.1=cVal1
-b.c.2=cVal2
-b=abVal
-b2=abVal
---------------------------------------------
-
-Why this is useful? Well there are different use cases:
-
-* you can segregate your configuration properties, e.g. a module can access its module configuration by
- querying all properties under the area +config.modules.myModule+ (or whatever would be appropriate).
-* you can use this mechanism to configure maps (or more generally: collections).
-* you can easily filter parts of configuration
-* ...and more.
-
-==== Why Using Strings Only
-
-Using Strings as base representation of configuration comes with several huge advantages:
-
-* Strings are simple to understand
-* Strings are human readable and therefore easy to prove for correctness
-* Strings can easily be used within different language, different VMs, files or network communications.
-* Strings can easily be compared and manipulated
-* Strings can easily be searched, indexed and cached
-* It is very easy to provide Strings as configuration, which gives much flexibility for providing configuration in
- production as well in testing.
-* and more
-
-On the other side there are also disadvantages:
-
-* Strings are inherently not type safe, they do not provide validation out of the box for special types, such as
-numbers,
- dates etc.
-* Often you want not to work with Strings, but with according types.
-* Strings are not hierarchical, so mapping hierarchical structures requires some extra efforts.
-
-Nevertheless most of these advantages can be mitigated easily, hereby still keeping all the benefits from above:
-
-* Adding type safe converters on top of String allow to add any type easily, that can be directly mapped out of Strings.
- This includes all common base types such as numbers, dates, time, but also timezones, formatting patterns and more.
-* Even more complex mappings can be easily realized, by using String not as a direct representation of configuration,
- but a reference that defines where the more complex configuration artifact is available. This mechanism is similarly
- easy to understand as parsing Strings to numbers, but is powerful enough to provide e.g. all kind of deployment
- descriptors in Java EE.
-* Hierarchical and collection types can be mapped in different ways:
-** The keys of configuration can have additional syntax/semantics. E.g. when adding dor-separating path semantics
-*** trees/maps can also simply be mapped.
-
-[API PropertySource]
-=== PropertySource
-==== Basic Model
-
-We have seen that constrain configuration aspects to simple literal key/value pairs provides us with an easy to
-understand, generic, flexible, yet expendable mechanism. Looking at the Java language features a +java.util.Map<String,
-String>+ and +java.util.Properties+ basically model these quite well out of the box.
-So it would make sense to build configuration on top of the JDK's +Map+ interface. This creates immediately additional
-benefits:
-
-* we inherit full Lambda and collection support
-* Maps are widely known and well understood
-
-Nevertheless there are some severe drawbacks:
-
-* +Configuration+ also requires meta-data, such as
-** the origin of a certain configuration entry and how it was derived from other values
-** the sensitivity of some data
-** the provider that have read the data
-** the time, when the data was read
-** the timestamp, when some data may be outdated
-** ...
-
-Basically the same is also the not related to some single configuration key, but also to a whole map.
-The +PropertySource+ interface models exact these aspects and looks as illustrated below:
-
-[source,java]
-.Interface PropertySource
---------------------------------------------
-public interface PropertySource{
-
- Optional<String> get(String key);
- boolean containsKey(String key);
- Map<String, String> toMap();
- MetaInfo getMetaInfo();
-
- default Set<String> keySet();
- default ConfigChangeSet load();
- default boolean isMutable();
- default void apply(ConfigChangeSet change);
-}
---------------------------------------------
-
-Hereby
-
-* +getMetaInfo()+ return the meta information for the property provider, as well as for individual property key/value pairs.
-* +get+ look similar to the methods on +Map+, though +get+ uses the +Optional+ type introduced
- with Java 8. This avoids returning +null+ or throwing exceptions in case no such entry is available and also
- reduced the API's footprint, since default values can be easily implemented by calling +Optional.orElse+.
-* +containsKey, keySet+ are as well methods similar to +java.util.Map+ though implementations may only returns
- limited data, especially when the underlying map storage does not support iteration.
-* +isMutable()+ allows to easy check, if a property provider is mutable, which is more elegant than catching
- +NonSupportedOperation+ exception thrown on the according methods of +Map+.
-* +load()+ finally allows to (re)load a property map. It depends on the implementing source, if this operation
- has any effect. If the map changes an according +ConfigChange+ must be returned, describing the
- changes applied.
-* +toMap+ allows to extract thing to a +Map+. Similar to +containsKey, keySet+ implementations may only return
- a limited data map, especially when the underlying map storage does not support iteration.
-
-This simple model will be used within the spi, where configuration can be injected/provided from external resources.
-But we have seen, that we have to consider additional aspects, such as extendability and type safety. Therefore we
-extend +PropertySource+ and hereby also apply the 'composite pattern', which results in the following key abstraction.
-
-==== Meta Information
-
-Each instance also provides an instance of +MetaInfo+, which provides meta information for the providers and its properties:
-
-[source,java]
-.Accessing Meta Information
---------------------------------------------
-PropertySource prov = ...;
-MetaInfo metaInfo = prov.getMetaInfo();
-Set<String> keys = metaInfo.keySet(); // returns the attribute keys, for which meta-information is accessible.
-String metaData = metaInfo.get("a.b.c.value"); // access meta information
-String itemName = metaInfo.getName(); // access meta information for the provider
---------------------------------------------
-
-As we have seen above there is as well a +MetaInfoBuilder+, which must be used to create instances of
-+MetaInfo+.
-
-==== Mutability
-
-Property sources optionally may be mutable. This can be checked by calling +boolean isMutable()+. If a source
-is mutable a +ConfigChangeSet+ can be passed. This change set can then be applied by the source. On creation
-of the +ConfigChangeSetBuilder+ a source can pass version information, so _optimistic locking_ can be implemented
-easily:
-
-[source,java]
-.Creating and applying a +ConfigChangeSet+ to a PropertySource
---------------------------------------------
-PropertySource source = ...;
-ConfigChangeSet changeSet = ConfigChangeSetBuilder.of(provider) // creating a default version
- .remove("key1ToBeRemoved", +key2ToBeRemoved")
- .put("key2", "key2Value")
- .put("key3", 12345)
- .put("key4", 123.45)
- .build();
-source.apply(changeSet);
---------------------------------------------
-
-
-[[API Configuration]]
-=== Configuration
-==== Basic Model
-
-Configuration inherits all basic features from +PropertySource+, but additionally adds functionality for
-type safety and extension mechanisms:
-
-[source,java]
-.Interface Configuration
---------------------------------------------
-public interface Configuration extends PropertySource{
-
- default Optional<Boolean> getBoolean(String key);
- default OptionalInt getInteger(String key);
- default OptionalLong getLong(String key);
- default OptionalDouble getDouble(String key);
- default <T> Optional<T> getAdapted(String key, PropertyAdapter<T> adapter);
- <T> Optional<T> get(String key, Class<T> type);
-
- // accessing areas
- default Set<String> getAreas();
- default Set<String> getTransitiveAreas();
- default Set<String> getAreas(final Predicate<String> predicate);
- default Set<String> getTransitiveAreas(Predicate<String> predicate);
- default boolean containsArea(String key);
-
- // extension points
- default Configuration with(UnaryOperator<Configuration> operator);
- default <T> T query(ConfigQuery<T> query);
-
- // versioning
- default String getVersion(){return "N/A";}
-
- // singleton accessors
- public static boolean isDefined(String name);
- public static <T> T current(String name, Class<T> template);
- public static Configuration current(String name);
- public static Configuration current();
- public static <T> T current(Class<T> type){
- public static void configure(Object instance);
- public static String evaluateValue(String expression);
- public static String evaluateValue(Configuration config, String expression);
- public static void addChangeListener(ConfigChangeListener listener);
- public static void removeChangeListener(ConfigChangeListener listener);
-}
---------------------------------------------
-
-Hereby
-
-* +XXX getXXX(String)+ provide type safe accessors for all basic wrapper types of the JDK.
-* +getAdapted+ allow accessing any type, hereby also passing a +PropertyAdapter+ that converts
- the configured literal value to the type required.
-* +getAreas()+, +getTransitiveAreas()+ allow to examine the hierarchical tree modeled by the configuration tree.
- Optionally also predicates can be passed to select only part of the tree to be returned.
-* +containsArea+ allows to check, if an area is defined.
-* +with, query+ provide the extension points for adding additional functionality.
-
-* the static accessor methods define:
- ** +current(), current(Class), current(String), current(String, Class)+ return the configuration valid for the current runtime environment.
- ** +addChangeListener, removeChangeListener+ allow to register or unregister
- global config change listener instances.
- ** evaluateValue allows to evaluate a configuration expression based on a given configuration.
- ** +configure+ performs injection of configured values.
-
-[[TypeConversion]]
-==== Type Conversion
-
-Configuration extend +PropertySource+ and add additional support for non String types. This is achieved
-with the help of +PropertyAdapter+ instances:
-
-[source,java]
-.PropertyAdapter
---------------------------------------------
-@FunctionalInterface
-public interface PropertyAdapter<T>{
- T adapt(String value);
-}
---------------------------------------------
-
-PropertyAdapter instances can be implemented manually or registered and accessed from the
-+PropertyAdapers+ singleton. Hereby the exact mechanism is determined by the API backing up the singleton.
-By default corresponding +PropertyAdapter+ instances can be registered using the Java +ServiceLoader+
-mechanism, or programmatically ba calling the +register(Class, PropertyAdapter)+ method.
-
-[source,java]
---------------------------------------------
-public final class PropertyAdapters{
- public static <T> PropertyAdapter<T> register(Class<T> targetType, PropertyAdapter<T> adapter);
- public static boolean isTargetTypeSupported(Class<?> targetType);
- public static <T> PropertyAdapter<T> getAdapter(Class<T> targetType);
- public static <T> PropertyAdapter<T> getAdapter(Class<T> targetType, WithPropertyAdapter annotation);
-}
---------------------------------------------
-
-Whereas this mechanism per se looks not very useful it's power shows up when combining it with the annotations
-API provided, e.g. look at the following annotated class:
-
-[source,java]
-.Annotated Example Class
---------------------------------------------
-public class ConfiguredClass{
-
- @ConfiguredProperty
- private String testProperty;
-
- @ConfiguredProperty("a.b.c.key1")
- @DefaultValue("The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.")
- String value1;
-
- @ConfiguredProperty("a.b.c.key2")
- private int value2;
-
- @ConfiguredProperty
- @DefaultValue("http://127.0.0.1:8080/res/api/v1/info.json")
- private URL accessUrl;
-
- @ConfiguredProperty
- @DefaultValue("5")
- private Integer int1;
-
- @ConfiguredProperty("a.b.customType")
- private MyCustomType myCustomType;
-
- @ConfiguredProperty("BD")
- private BigDecimal bigNumber;
-
- ...
-}
---------------------------------------------
-
-The class does not show all the possibilities that are provided, but it shows that arbitrary types can be supported easily.
-This applied similarly to collection types, whereas collections are more advanced and therefore described in a separate section
-later.
-
-Given the class above and the current configuration can provide the values required, configuring an instance of the
-class is simple:
-
-[source,java]
-.Configuring the Example Class
---------------------------------------------
-ConfiguredClass classInstance = new ConfiguredClass();
-Configuration.configure(configuredClass);
---------------------------------------------
-
-Additional types can transparently be supported by implementing and registering corresponding SPI instances. This is explained
-in the SPI documentation of {name}.
-
-==== Extension Points
-
-We are well aware of the fact that this library will not be able to cover all kinds of use cases. Therefore
-we have added similar functional extension mechanisms that were used in other areas of the Java eco-system as well:
-
-* +ConfigOperator+ define unary operations on +Configuration+. They can be used for filtering, implementing
- configuration views, security interception etc.
-* +ConfigQuery+ defines a function returning any kind of result based on a configuration instance. Typical
- use cases of queries could be the implementation of configuration SPI instances that are required
- by other libraries or frameworks.
-
-Both interfaces hereby are defined as functional interfaces:
-
-[source,java]
-.ConfigQuery
---------------------------------------------
-@FunctionalInterface
-public interface ConfigQuery<T>{
- T query(Configuration config);
-}
---------------------------------------------
-
-Instances of this interface can be applied on a +Configuration+ instance:
-
-[source,java]
-.Applying Config operators and queries
---------------------------------------------
-ConfigSecurity securityContext = Configuration.current().query(ConfigSecurity::targetSecurityContext);
---------------------------------------------
-
-NOTE: +ConfigSecurity+ is an arbitrary class.
-
-Similarly an instance of +UnaryOpertor<Configuration>+ can be applied as well to decorate an existing +Configuration+
-instance:
-
-[source,java]
-.Applying Config operators
---------------------------------------------
-Configuration secured = Configuration.current().with(ConfigSecurity::secure);
---------------------------------------------
-
-=== Configuration Injection
-
-The +Configuration+ interface provides static methods that allow to anykind of instances be configured
-ny just passing the instances calling +Configuration.configure(instance);+. The classes passed hereby must
-be annotated with +@ConfiguredProperty+ to define the configured properties. Hereby this annotation can be
-used in multiple ways and combined with other annotations such as +@DefaultValue+,
-+@WithLoadPolicy+, +@WithConfig+, +@WithConfigOperator+, +@WithPropertyAdapter+.
-
-To illustrate the mechanism below the most simple variant of a configured class is given:
-
-[source,java]
-.Most simple configured class
---------------------------------------------
-pubic class ConfiguredItem{
- @ConfiguredProperty
- private String aValue;
-}
---------------------------------------------
-
-When this class is configured, e.g. by passing it to +Configuration.configure(Object)+,
-the following is happening:
-
-* The current valid +Configuration+ is evaluated by calling +Configuration cfg = Configuration.of();+
-* The current property value (String) is evaluated by calling +cfg.get("aValue");+
-* if not successful, an error is thrown (+ConfigException+)
-* On success, since no type conversion is involved, the value is injected.
-* The configured bean is registered as a weak change listener in the config system's underlying
- configuration, so future config changes can be propagated (controllable by applying the
- +@WithLoadPolicy+ annotation).
-
-In the next example we explicitly define the property value:
-[source,java]
---------------------------------------------
-pubic class ConfiguredItem{
-
- @ConfiguredProperty
- @ConfiguredProperty("a.b.value")
- @configuredProperty("a.b.deprecated.value")
- @DefaultValue("${env:java.version}")
- private String aValue;
-}
---------------------------------------------
-
-Within this example we evaluate multiple possible keys. Evaluation is aborted if a key could be successfully
-resolved. Hereby the ordering of the annotations define the ordering of resolution, so in the example above
-resolution equals to +"aValue", "a.b.value", "a.b.deprecated.value"+. If no value could be read
-from the configuration, it uses the value from the +@DefaultValue+ annotation. Interesting here
-is that this value is not static, it is evaluated by calling +Configuration.evaluateValue(Configuration, String)+.
-
-[[API ConfigurationBuilder]]
-==== Building Simple Configuration
-
-Looking at the structures of configuration system used by large companies we typically encounter some kind of configuration
-hierarchies that are combined in arbitrary ways. Users of the systems are typically not aware of the complexities in this
-area, since they simply know the possible locations, formats and the overriding policies. Framework providers on the other
-side must face the complexities and it would be very useful if Tamaya can support here by providing prebuilt functionality
-that helps implementing these aspects. All this leads to the feature set of combining property sources. Hereby the following
-strategies are useful:
-
-* aggregating configurations, hereby later configurations added
- ** override any existing entries from earlier configurations
- ** combine conflicting entries from earlier configurations, e.g. into a comma-separated structure.
- ** may throw a ConfigException ig entries are conflicting
- ** may only add entries not yet defined by former providers, preventing entries that are already present to be overwrite
- ** any custom aggregation strategy, which may be a mix of above
-* intersecting configurations
-* subtracting configurations
-* filtering configurations
-
-These common functionality is provided by +ConfigurationBuilder+ instances. Additionally to the base strategies above a
-+MetaInfo+ instance can be passed optionally as well to define the meta information for the newly created configuration.
-Let's assume we have two configurations with the following data:
-
-[source,properties]
-.Configuration 1
---------------------------------------------
-a=a
-b=b
-c=c
-g=g
-h=h
-i=i
---------------------------------------------
-
-[source,properties]
-.Configuration 2
---------------------------------------------
-a=A
-b=B
-c=C
-d=D
-e=E
-f=F
---------------------------------------------
-
-Looking in detail you see that the entries +a,b,c+ are present in both configurations, whereas +d,e,f+ are only present in Configuration 1,
-and +g,h,i+ only in Configuration 2.
-
-[source,java]
-.Example Combining Configurations
---------------------------------------------
-Configuration cfg1 = ...
-Configuration cfg2 = ...
-
-// aggregate, hereby values from Configuration 2 override values from Configuration 1
-Configuration unionOverriding = ConfigurationBuilder.of().aggregate(cfg1, cfg2).build();
-System.out.println("unionOverriding: " + unionOverriding);
-
-// ignore duplicates, values present in Configuration 1 are not overriden by Configuration 2
-Configuration unionIgnoringDuplicates = ConfigurationBuilder.of()
- .withAggregationPolicy(AggregationPolicy.IGNORE_DUPLICATES).aggregate(cfg1, cfg2).build();
-System.out.println("unionIgnoringDuplicates: " + unionIgnoringDuplicates);
-
-// this variant combines/maps duplicate values into a new value
-Configuration unionCombined = ConfigurationBuilder.of().withAggregationPolicy(AggregationPolicy.COMBINE)
- .aggregate(cfg1, cfg2);
-System.out.println("unionCombined: " + unionCombined);
-
-// This variant throws an exception since there are key/value paris in both providers, but with different values
-try{
- ConfigurationBuilder.of().withAggregationPolicy(AggregationPolicy.EXCEPTION).aggregate(provider1, provider2)
- .build();
-}
-catch(ConfigException e){
- // expected!
-}
---------------------------------------------
-
-The example above produces the following outpout:
-
-[source,listing]
-.Example Combining Configurations
---------------------------------------------
-AggregatedConfiguration{
- (name = dynamicAggregationTests)
- a = "[a][A]"
- b = "[b][B]"
- c = "[c][C]"
- d = "[D]"
- e = "[E]"
- f = "[F]"
- g = "[g]"
- h = "[h]"
- i = "[i]"
-}
-unionOverriding: AggregatedConfigurations{
- (name = <noname>)
- a = "A"
- b = "B"
- c = "C"
- d = "D"
- e = "E"
- f = "F"
- g = "g"
- h = "h"
- i = "i"
-}
-unionIgnoringDuplicates: AggregatedConfigurations{
- (name = <noname>)
- a = "a"
- b = "b"
- c = "c"
- d = "D"
- e = "E"
- f = "F"
- g = "g"
- h = "h"
- i = "i"
-}
-unionCombined: AggregatedConfigurations{
- (name = <noname>)
- a = "a,A"
- b = "b,B"
- c = "c,C"
- d = "D"
- e = "E"
- f = "F"
- g = "g"
- h = "h"
- i = "i"
-}
---------------------------------------------
-
-No +AggregationPolicy+ is also a functional interface that can be implemented:
-
-[source,java]
-.AggregationPolicy Interface
---------------------------------------------
-@FunctionalInterface
-public interface AggregationPolicy {
- String aggregate(String key, String value1, String value2);
-}
---------------------------------------------
-
-So we can also define our own aggregation strategy using a Lambda expression:
-
-[source,java]
-.Use a Custom AggregationPolicy
---------------------------------------------
-Configuration cfg1 = ...;
-Configuration cfg2 = ...;
-Configuration config = ConfigurationBuilder.of("dynamicAggregationTests")
- .withAggregationPolicy((k, v1, v2) -> (v1 != null ? v1 : "") + '[' + v2 + "]")
- .aggregate(cfg1, cfg2).build();
-System.out.println(config);
---------------------------------------------
-
-Additionally we also create here an instance of +MetaInfo+. The output of this code snippet is as follows:
-
-[source,listing]
-.Listing of dynamic aggregation policy
---------------------------------------------
-AggregatedConfiguration{
- (name = dynamicAggregationTests)
- a = "[a][A]"
- b = "[b][B]"
- c = "[c][C]"
- d = "[D]"
- e = "[E]"
- f = "[F]"
- g = "[g]"
- h = "[h]"
- i = "[i]"
-}
---------------------------------------------
-
-Summarizing the +ConfigurationBuilder+ allows to combine providers in various forms:
-
-[source,listing]
-.Methods provided on PropertySources
---------------------------------------------
-public final class ConfigurationBuilder {
-
- private ConfigurationBuilder() {}
-
- public static ConfigurationBuilder of();
- public static ConfigurationBuilder of(PropertySource config);
- public static ConfigurationBuilder of(MetaInfo metaInfo);
- public static ConfigurationBuilder of(String name);
-
- public ConfigurationBuilder withMetaInfo(MetaInfo metaInfo);
- public ConfigurationBuilder withAggregationPolicy(AggregationPolicy aggregationPolicy);
-
- public ConfigurationBuilder addArgs(String... args);
- public ConfigurationBuilder addPaths(List<String> paths);
- public ConfigurationBuilder addUrls(URL... urls);
- public ConfigurationBuilder addUrls(List<URL> urls);
- public ConfigurationBuilder addMap(Map<String, String> map);
-
- public ConfigurationBuilder empty();
- public ConfigurationBuilder empty(MetaInfo metaInfo);
- public ConfigurationBuilder emptyMutable(MetaInfo metaInfo);
- public ConfigurationBuilder addEnvironmentProperties();
- public ConfigurationBuilder addSystemProperties();
- public ConfigurationBuilder aggregate(Configuration... configs){
- public ConfigurationBuilder aggregate(List<Configuration> configs) {
- public ConfigurationBuilder mutable(Configuration config) {
- public ConfigurationBuilder intersected(Configuration... providers) {
- public ConfigurationBuilder subtracted(Configuration target, Configuration... providers) {
- public ConfigurationBuilder filtered(Predicate<String> filter, Configuration config) {
- public ConfigurationBuilder contextual(Supplier<Configuration> mapSupplier,
- Supplier<String> isolationKeySupplier) {
- public ConfigurationBuilder delegating(Configuration mainMap, Map<String, String> parentMap) {
- public ConfigurationBuilder replacing(Configuration mainMap, Map<String, String> replacementMap);
-
- public Configuration build();
- public Configuration buildFrozen();
-}
---------------------------------------------
-
-
-
-=== Environment
-
-The environment basically is also a kind of property/value provider similar to +System
-.getenv()+ in the JDK. Nevertheless it provides additional functionality:
-
-[source,java]
-.Interface Environment
---------------------------------------------
-public interface Environment {
-
- Optional<String> get(String key);
- boolean containsKey(String key);
- Set<String> keySet();
- Map<String,String> toMap();
-
- public static Environment current();
- public static Environment root();
---------------------------------------------
-
-* Basically an environment can contain any properties. The root environment
- hereby must contain at least
- ** all JDK's environment properties.
- ** additional root properties are allowed as well.
-* the root environment is always directly accessible by calling +Environment.root()+
-* the current environment can be accessed by calling +Environment.current()+.
-
-Summarizing the Environment can be seen as a runtime context. This also implies, that this context changes
-depending on the current runtime context. Developers implementing an environment mechanism should be aware that
-an environment can be accessed very frequently, so evaluation and access of an +Environment+ must be fast. For
-further details we recommend the SPI details section of the core implementation.
-
-
-== SPI
-
-[[API PropertySourceBuilder]]
-==== Building Property Sources
-
-In [[PropertSource]] we have outlines that the essence of a property key store for configuration can be modelled by
-the +PropertySource+ interface. Similarly to the +ConfigurationBuilder+ you can also combine several +PropertySource+
-instances to assemble more complex configuration scenarios. Typically assembling is done within a +ConfigProvider+,
-which is responsible for providing correct Configuration corresponding to the current environment.
-
-Summarizing you can
-* aggregate providers, hereby later providers added
- ** override any existing entries from earlier providers
- ** combine conflicting entries from earlier providers, e.g. into a comma-separated structure.
- ** may throw a ConfigException ig entries are conflicting
- ** may only add entries not yet defined by former providers, preventing entries that are already present to be overwritten
- ** any custom aggregation strategy, which may be a mix of above
-* intersecting providers
-* subtracting providers
-* filtering providers
-
-The following code snippet gives a couple of examples:
-
-[source,java]
-.Example Combining PropertySources
---------------------------------------------
-PropertySource provider1 = ...
-PropertySource provider2 = ...
-
-// aggregate, hereby values from provider 2 override values from provider 1
-PropertySource unionOverriding = PropertySourceBuilder.of()
- .aggregate(provider1, provider2).build(); // OVERRIDE policy is default
-System.out.println("unionOverriding: " + unionOverriding);
-
-// ignore duplicates, values present in provider 1 are not overriden by provider 2
-PropertySource unionIgnoringDuplicates = PropertySources
- .aggregate(AggregationPolicy.IGNORE_DUPLICATES(), provider1, provider2).build();
-System.out.println("unionIgnoringDuplicates: " + unionIgnoringDuplicates);
-
-// this variant combines/maps duplicate values into a new value
-PropertySource unionCombined = PropertySourceBuilder.of().withAggregationPolicy(AggregationPolicy.COMBINE))
- .aggregate(provider1, provider2).build();
-System.out.println("unionCombined: " + unionCombined);
-
-// This variant throws an exception since there are key/value paris in both providers, but with different values
-try{
- PropertySourceBuilder.of().withAggregationPolicy(AggregationPolicy.EXCEPTION).aggregate(provider1, provider2);
-}
-catch(ConfigException e){
- // expected!
-}
---------------------------------------------
-
-