http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/cd513bf3/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 new file mode 100644 index 0000000..831a71f --- /dev/null +++ b/docs/src/main/asciidoc/design/2_CoreConcepts.adoc @@ -0,0 +1,849 @@ +// 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! +} +-------------------------------------------- + +
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/cd513bf3/docs/src/main/asciidoc/design/3_Extensions.adoc ---------------------------------------------------------------------- diff --git a/docs/src/main/asciidoc/design/3_Extensions.adoc b/docs/src/main/asciidoc/design/3_Extensions.adoc new file mode 100644 index 0000000..db949ac --- /dev/null +++ b/docs/src/main/asciidoc/design/3_Extensions.adoc @@ -0,0 +1,841 @@ +// 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 +* _PropertyProvider:_ is the the SPI for a source that provides configuration data. A +PropertyProvider+ + 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 +PropertyProvider+ 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. +* _PropertyProviders_ allows to aggregate different property providers. Hereby property providers are + seen as sets, which can be combined to new providers using set styled operations (aggregate, intersect, subtract). + This allows to model and create composite container providers, to build up more complex configuration models + step by step. +* _MetaInfo_ is provided by each +Configuration, PropertyProvider+ 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: + +* _Bootstrap_ 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 +ServiceProvider+ using +java.util.ServiceLoader+. +* All the singleton used explicitly (+PropertyAdapters,PropertyProviders+ 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 +Bootstrap+ setup (by default ServiceLoader based). +* Also the singleton used implicitly by +Configuration, Environment, Stage+ 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 +Bootstrap+ 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, ConfigOperator, ConfigQuery, PropertyAdapter, Stage, + Environment, PropertyProvider, MetaInfo+ +* +org.apache.tamaya.spi+ contains the SPI interfaces to be implemented by implementations and the +Bootstrap+ 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. + +[APIPropertyProviders] +=== Property Providers +==== 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 +vava.util.Map<String, +String>+ and +java.util.Properties+ basically model these quite well out of the box. +So it makes 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 things to be considered: + +* 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 +PropertyProvider+ interface models exact these aspects and looks as illustrated below: + +[source,java] +.Interface PropertyProvider +-------------------------------------------- +public interface PropertyProvider{ + + 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, containsKey, keySet+ 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+. +* +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. +* +hasSameProperties+ allows to perform a comparison with another provider. +* +toMap+ allows to extract thing to a +Map+. + +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 +PropertyMap+ 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 +-------------------------------------------- +PropertyProvider 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 providers optionally may be mutable. This can be checked by calling +boolean isMutable()+. If a provider +is mutable a +ConfigChangeSet+ can be passed. This change set can then be applied by the provider. On creation +of the +ConfigChangeSetBuilder+ a provider can pass version information, so _optimistic locking_ can be implemented +easily: + +[source,java] +.Creating and applying a +ConfigChangeSet+ to a provider +-------------------------------------------- +PropertyProvider prov = ...; +ConfigChangeSet changeSet = ConfigChangeSetBuilder.of(provider) // creating a default version + .remove("key1ToBeRemoved", +key2ToBeRemoved") + .put("key2", "key2Value") + .put("key3", 12345) + .put("key4", 123.45) + .build(); +provider.apply(changeSet); +-------------------------------------------- + +[[API CombineProviders]] +==== Combining Property Providers + +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 providers. Hereby the following +strategies are useful: + +* aggregating 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 ConfigExcepotion ig entries are conflicting + ** may only add entries not yet defined by former providers, preventing entries that are already present to be overwritte + ** any custom aggregation strategy, which may be a mix of above +* intersecting providers +* subtracting providers +* filtering providers + +These common functionality is provided by the +PropertyProviders+ singleton. Additionally to the base strategies above a +MetaInfo+ +instance can be passed optionally as well to define the meta information for the newly created provider instances. +Let's assume we have two property providers with the following data: + +[source,properties] +.Provider 1 +-------------------------------------------- +a=a +b=b +c=c +g=g +h=h +i=i +-------------------------------------------- + +[source,properties] +.Provider 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 providers, whereas +d,e,f+ are only present in provider 1, +and +g,h,i+ only in provider 2. + +[source,java] +.Example Combining PropertyProviders +-------------------------------------------- +PropertyProvider provider1 = ... +PropertyProvider provider2 = ... + +// aggregate, hereby values from provider 2 override values from provider 1 +PropertyProvider unionOverriding = PropertyProviders.aggregate(AggregationPolicy.OVERRIDE(), provider1, provider2); +System.out.println("unionOverriding: " + unionOverriding); + +// ignore duplicates, values present in provider 1 are not overriden by provider 2 +PropertyProvider unionIgnoringDuplicates = PropertyProviders.aggregate(AggregationPolicy.IGNORE_DUPLICATES(), provider1, provider2); +System.out.println("unionIgnoringDuplicates: " + unionIgnoringDuplicates); + +// this variant combines/maps duplicate values into a new value +PropertyProvider unionCombined = PropertyProviders.aggregate(AggregationPolicy.COMBINE(), provider1, provider2); +System.out.println("unionCombined: " + unionCombined); + +// This variant throws an exception since there are key/value paris in both providers, but with different values +try{ + PropertyProviders.aggregate(AggregationPolicy.EXCEPTION(), provider1, provider2); +} +catch(ConfigException e){ + // expected! +} +-------------------------------------------- + +The example above produces the following outpout: + +[source,listing] +.Example Combining PropertyProviders +-------------------------------------------- +AggregatedPropertyProvider{ + (name = dynamicAggregationTests) + a = "[a][A]" + b = "[b][B]" + c = "[c][C]" + d = "[D]" + e = "[E]" + f = "[F]" + g = "[g]" + h = "[h]" + i = "[i]" +} +unionOverriding: AggregatedPropertyProvider{ + (name = <noname>) + a = "A" + b = "B" + c = "C" + d = "D" + e = "E" + f = "F" + g = "g" + h = "h" + i = "i" +} +unionIgnoringDuplicates: AggregatedPropertyProvider{ + (name = <noname>) + a = "a" + b = "b" + c = "c" + d = "D" + e = "E" + f = "F" + g = "g" + h = "h" + i = "i" +} +unionCombined: AggregatedPropertyProvider{ + (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 an 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 +-------------------------------------------- +PropertyProvider provider1 = ...; +PropertyProvider provider2 = ...; +PropertyProvider props = PropertyProviders.aggregate( + (k, v1, v2) -> (v1 != null ? v1 : "") + '[' + v2 + "]", + MetaInfo.of("dynamicAggregationTests"), + props1, props2); +System.out.println(props); +-------------------------------------------- + +Additionally we also pass here an instance of +MetaInfo+. The output of this code snippet is as follows: + +[source,listing] +.Listing of dynamic aggregation policy +-------------------------------------------- +AggregatedPropertyProvider{ + (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 +PropertyProviders+ singleton allows to combine providers in various forms: + +[source,listing] +.Methods provided on PropertyProviders +-------------------------------------------- +public final class PropertyProviders { + + private PropertyProviders() {} + + public static PropertyProvider fromArgs(String... args) { + public static PropertyProvider fromArgs(MetaInfo metaInfo, String... args) { + public static PropertyProvider fromPaths(AggregationPolicy aggregationPolicy, String... paths) { + public static PropertyProvider fromPaths(String... paths) { + public static PropertyProvider fromPaths(List<String> paths) { + public static PropertyProvider fromPaths(AggregationPolicy aggregationPolicy, List<String> paths) { + public static PropertyProvider fromPaths(MetaInfo metaInfo, List<String> paths) { + public static PropertyProvider fromPaths(AggregationPolicy aggregationPolicy, MetaInfo metaInfo, List<String> paths) { + public static PropertyProvider fromUris(URI... uris) { + public static PropertyProvider fromUris(AggregationPolicy aggregationPolicy, URI... uris) { + public static PropertyProvider fromUris(List<URI> uris) { + public static PropertyProvider fromUris(AggregationPolicy aggregationPolicy, List<URI> uris) { + public static PropertyProvider fromUris(MetaInfo metaInfo, URI... uris) { + public static PropertyProvider fromUris(AggregationPolicy aggregationPolicy, MetaInfo metaInfo, URI... uris) { + public static PropertyProvider fromUris(MetaInfo metaInfo, List<URI> uris) { + public static PropertyProvider fromUris(AggregationPolicy aggregationPolicy, MetaInfo metaInfo, List<URI> uris) { + public static PropertyProvider fromMap(Map<String, String> map) { + public static PropertyProvider fromMap(MetaInfo metaInfo, Map<String, String> map) { + public static PropertyProvider empty() { + public static PropertyProvider emptyMutable() { + public static PropertyProvider empty(MetaInfo metaInfo) { + public static PropertyProvider emptyMutable(MetaInfo metaInfo) { + public static PropertyProvider fromEnvironmentProperties() { + public static PropertyProvider fromSystemProperties() { + public static PropertyProvider freezed(PropertyProvider provider) { + public static PropertyProvider aggregate(AggregationPolicy mapping, MetaInfo metaInfo, PropertyProvider... providers){ + public static PropertyProvider aggregate(PropertyProvider... providers) { + public static PropertyProvider aggregate(List<PropertyProvider> providers) { + public static PropertyProvider aggregate(AggregationPolicy mapping, PropertyProvider... propertyMaps) { + public static PropertyProvider aggregate(AggregationPolicy mapping, List<PropertyProvider> providers) { + public static PropertyProvider mutable(PropertyProvider provider) { + public static PropertyProvider intersected(AggregationPolicy aggregationPolicy, PropertyProvider... providers) { + public static PropertyProvider intersected(PropertyProvider... providers) { + public static PropertyProvider subtracted(PropertyProvider target, PropertyProvider... providers) { + public static PropertyProvider filtered(Predicate<String> filter, PropertyProvider provider) { + public static PropertyProvider contextual(Supplier<PropertyProvider> mapSupplier, + Supplier<String> isolationKeySupplier) { + public static PropertyProvider delegating(PropertyProvider mainMap, Map<String, String> parentMap) { + public static PropertyProvider replacing(PropertyProvider mainMap, Map<String, String> replacementMap) { +} +-------------------------------------------- + + +[[API Configuration]] +=== Configuration +==== Basic Model + +Configuration inherits all basic features from +PropertyProvider+, but additionally adds functionality for +type safety and extension mechanisms: + +[source,java] +.Interface Configuration +-------------------------------------------- +public interface Configuration extends PropertyProvider{ + + default OptionalBoolean 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(ConfigOperator operator); + default <T> T query(ConfigQuery<T> query); + + // versioning + default String getVersion(){return "N/A";} + void addPropertyChangeListener(PropertyChangeListener l); + void removePropertyChangeListener(PropertyChangeListener l); + + // 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 addGlobalPropertyChangeListener(PropertyChangeListener listener); + public static void removeGlobalPropertyChangeListener(PropertyChangeListener 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. + ** +addPropertyChangeListener, removePropertyChangeListener+ 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 +PropertyProvider+ 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] +.ConfigOperator and ConfigQuery +-------------------------------------------- +@FunctionalInterface +public interface ConfigOperator{ + Configuration operate(Configuration config); +} + +@FunctionalInterface +public interface ConfigQuery<T>{ + T query(Configuration config); +} +-------------------------------------------- + +Both interfaces can be applied on a +Configuration+ instance: + +[source,java] +.Applying Config operators and queries +-------------------------------------------- +Configuration secured = Configuration.of().apply(ConfigSecurity::secure); +ConfigSecurity securityContext = Configuration.of().query(ConfigSecurity::targetSecurityContext); +-------------------------------------------- + +NOTE: +ConfigSecurity+ is an arbitrary class. + +=== 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)+. + +=== Environment + +The environment basically is also a kind of property/value provider similar to +System.getProperties()+ and +System +.getenv()+ in the JDK. Nevertheless it provides additional functionality: + +[source,java] +.Interface Environment +-------------------------------------------- +public interface Environments { + + String getEnvironmentType(); + String getEnvironmentId(); + Environment getParentEnvironment(); + + Optional<String> get(String key); + boolean containsKey(String key); + Set<String> keySet(); + Map<String,String> toMap(); + + public static Environment current(){ + public static Environment getRootEnvironment(){ + public static List<String> getEnvironmentTypeOrder(){ + public static List<String> getEnvironmentHierarchy(){ + public static Optional<Environment> getInstance(String environmentType, String contextId){ + public static Set<String> getEnvironmentContexts(String environmentType){ + public static boolean isEnvironmentActive(String environmentType){ +-------------------------------------------- + +* environments are hierarchical. Hereby all environments inherit from the root environment. The root environment + hereby must contain + ** all JDK's system properties, with same keys, values + ** all JDK's environment properties, prefixed with +env:+. + ** additional root properties are allowed as well. +* the root environment is always directly accessible by calling +Environment.getRootEnvironment()+ +* the current environment can be accessed by calling +Environment.of()+. +* each environment also defines a +Stage+ (implementing +StageSupplier+). Hereby, if not set explicitly the +Stage+ is inherited from the root + environment. Consequently the root environment must provide a +Stage+, which by default is +Stage.development()+. + +Additionally each environment instance is uniquely identified by the environment type (accessible from ++getEnvironmentType()+ and the environment id (accessible from +getEnvironmentId()+). So it is possible to access +an +Environment+ by calling +of(String environmentType, String environmentId)+. Implementations may restrict access +to environments depending on the current runtime environment (runtime context) active. The API does +not require further aspects. + +The call to +getEnvironmentIds(String)+ returns all context ids of the known +Environment+ instances +of a given type. E.g. assuming there is an environment type +war+ calling +Environment.getEnvironmentIds("war")+ +may return +"/web/app1", "/web/app2"+ (assuming the war context ids equal the web applications root contexts). + +All environments are basically ordered. The ordering can be accessed by calling +getEnvironmentTypeOrder()+. Hereby +not every environment type in a hierarchy must necessarily present. This is reflected by +getEnvironmentHierarchy()+ +which returns the environment type ids in order, but only containing the types of the environments +currently present and accessible in the hierarchy. As an example an environment type order in an advanced +use case could be something like +"root","ear","war","saas","user"+, whereas the concrete environment type hierarchy +may be +"root","war","saas"+, because the application was not included +in an additional ear archive and no user is currently active (anonymous). The call to +isEnvironmentActive(String)+ +allows to determine if an environment of the given type is currently active. +Finally the environment hierarchy is of course similarly reflected by the relationship (+getParentEnvironment()+). +The following code should illustrate some of these concepts: + +[source,java] +.Interface Environment +-------------------------------------------- +List<String> envHierarchy = Environment.getEnvironmentHierarchy(); + // -> "root","war","saas" +Environment env = Environment.of(); +System.out.println(env.getEnvironmentContext()); // saas +System.out.println(env.getEnvironmentId()); // mysolution_pro +env = env.getParentEnvironment(); +System.out.println(env.getEnvironmentContext()); // war +System.out.println(env.getEnvironmentId()); // pro +env = env.getParentEnvironment(); +System.out.println(env.getEnvironmentContext()); // root +System.out.println(env.getEnvironmentId()); // system +env = env.getParentEnvironment(); +// env is null now! +-------------------------------------------- + + http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/cd513bf3/docs/src/main/asciidoc/design/4_ImplementationCore.adoc ---------------------------------------------------------------------- diff --git a/docs/src/main/asciidoc/design/4_ImplementationCore.adoc b/docs/src/main/asciidoc/design/4_ImplementationCore.adoc new file mode 100644 index 0000000..2233002 --- /dev/null +++ b/docs/src/main/asciidoc/design/4_ImplementationCore.adoc @@ -0,0 +1,47 @@ +// 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 Implementation Concepts +Tamaya comes with an implementation module that implements the Tamaya API. The API itself does only provide the API for configuration usage. +Especially it does not define how the current runtime +Environment+ is mapped to a +Configuration+. This the gaop that should be solved +by the core library. + +The high level packages give you a good overview of the functionality provided: + +* +org.apache.tamaya.core+ is the root package of the Tamaya core implementation. +* +org.apache.tamaya.core.config+ provides + + + +The SPI contains the following core concepts/artifacts: + +* _Bootstrap_ 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 +ServiceProvider+ using +java.util.ServiceLoader+. +* All the singleton used explicitly (+PropertyAdapters,PropertyProviders+ 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 +Bootstrap+ setup (by default ServiceLoader based). +* Also the singleton used implicitly by +Configuration, Environment, Stage+ 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 +Bootstrap+ 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, ConfigOperator, ConfigQuery, PropertyAdapter, Stage, + Environment, PropertyProvider, MetaInfo+ +* +org.apache.tamaya.spi+ contains the SPI interfaces to be implemented by implementations and the +Bootstrap+ mechanism. ++ +org.apache.tamaya.annot+ contains the annotations defined. http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/cd513bf3/docs/src/main/asciidoc/design/Design.adoc ---------------------------------------------------------------------- diff --git a/docs/src/main/asciidoc/design/Design.adoc b/docs/src/main/asciidoc/design/Design.adoc new file mode 100644 index 0000000..4865ca1 --- /dev/null +++ b/docs/src/main/asciidoc/design/Design.adoc @@ -0,0 +1,117 @@ +Apache Tamaya -- Design Documentation +===================================== +:name: Tamaya +:rootpackage: org.apache.tamaya +:title: Apache Tamaya +:revnumber: 0.1-SNAPSHOT +:revremark: Incubator +:revdate: November 2014 +:longversion: {revnumber} ({revremark}) {revdate} +:authorinitials: ATR +:author: Anatole Tresch, Anatole Tresch +:email: <atsti...@gmail.com> +:source-highlighter: coderay +:website: http://tamaya.incubator.apache.org/ +:iconsdir: {imagesdir}/icons +:toc: +:toc-placement: manual +:icons: +:encoding: UTF-8 +:numbered: + +''' + +<<< + +-> add image : : https://raw.githubusercontent.com/JavaConfig/config-api/master/src/main/asciidoc/images/javaconfig.jpg[] + +toc::[] + +<<< +:numbered!: +----------------------------------------------------------- +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. +----------------------------------------------------------- + +:numbered: + +<<< + +== Introduction +This document describes the {name} API for Configuration. The technical objective is to provide a +unified configuration model in Java, targeting Java ME, SE as well as the EE platform. +The API will provide support for key/value based application configuration. It will provide +as well higher level APIs that are based on the low level ke</value pairs. Finally it will +provide extension points for adding additional features and additional modules for extension +or adaption. + +=== Working Group +This work is being conducted as part of a community lead joint effort under the Apache Software Foundation. This +specification is the result of the collaborative work of the members of the {name} Users Group and the community at +large. Currently the project is lead by Anatole Tresch (atsticks at gmail.dot com). + +=== Goals +Configuration is a key feature in all kind of programming languages. Basically configuration is the parametrization of +well defined aspects of a software product without having to recompile/rebuild the code. + +==== Targets +{name} targets to support all general configuration aspects, e.g. + +* spplication configuration +** plugins +** modules +** components +* Configuration of Java EE related aspects for Java enterprise application portability and dynamic provisioning, such as +** Configuration of CDI (interceptors, decorators and alternatives) +** Configuration of Bean Validation, JSF, web applications etc. +* Configuration of instances within Java SE, e.g. by passing instances to a method that injects configured values, or by providing + accessors to evaluate current configuration vlues. This can be used explicitly or transparently by client code. + +Additionally the solution should support + +* multiple configuration locations, including remote locations +* multiple configuration formats, including custom formats +* multiple configuration loading mechanisms, including custom mechanisms. By default reading the classpath, files und URIs are supported by default. +* type conversion +* configuration of collections + + +=== Required Java version +The API is based on Java SE 8.0 language features. + +=== How this document is organized +There are five main section in this document: + +* Use cases. +* Requirements. +* Specification. +* Implementation Recommendations. +* An appendix. + +<<< +include::src/main/asciidoc/design/0_UseCases.adoc[] + +<<< +include::src/main/asciidoc/design/1_Requirements.adoc[] + +<<< +include::src/main/asciidoc/design/2_CoreConcepts.adoc[] + +:numbered!: +== APPENDIX +