| :jbake-type: page |
| :jbake-status: published |
| |
| [[CoreDesign]] |
| == Apache Tamaya: API |
| |
| Though Tamaya is a very powerful and flexible solution there are basically only a few simple core concepts required. |
| Everything else uses or extends these basic mechanisms. As a starting point we recommend you read the corresponding |
| link:../highleveldesign.html[High Level Design Documentation] |
| |
| [[API]] |
| == The Tamaya API |
| The API provides the artifacts as described in the link:../highleveldesign.html[High Level Design Documentation], which are: |
| |
| * The package +org.apache.tamaya+ defines a simple but complete SE *API* for accessing _Configuration_: |
| ** +Configuration+ hereby models _configuration_, the main interface of Tamaya. +Configuration+ provides |
| *** access to literal key/value pairs. |
| *** functional extension points (+with, query+) using a unary +ConfigOperator+ or |
| a function +ConfigurationQuery<T>+. |
| ** +Configuration+ also provides with +current()+ and +current(ClassLoader)+ static access point for |
| obtaining the current configuration. |
| ** +ConfigurationSnapshot+ declares an immutable configuration instance, which supports consistent property access. |
| ** +ConfigException+ defines a runtime exception for usage by the configuration system. |
| ** +TypeLiteral+ provides a possibility to type safely define the target type to be returned in case non-String types |
| are accessed. It is especially useful when accessing collections from Tamaya. |
| ** +PropertyConverter+, which defines conversion of configuration values (one or multiple Strings) into any |
| required target type. |
| |
| * The package +org.apache.tamaya.spi+ provides interfaces used for extending and/or |
| adapting Tamaya's core functionality, as well as artifacts for creating |
| +Configuration+ instances programmatically: |
| ** _PropertySource:_ is the the interface to be implemented for adding configuration entries. A +PropertySource+ hereby |
| *** is minimalistic and can be implemented in any way. E.g. there is no distiction that |
| the configuration data provided is managed locally, remotely. There is even no |
| requirement that the configuration data is always fully available. Summarizing a |
| +PropertySource+ |
| *** provides property access for property values. A single property value hereby is modelled as +PropertyValue+, |
| which by default contains a single literal value. Nevertheles Tamaya also supports more complex scenarios |
| by allowing property values to have children either as array/list type or as named fields. These children |
| themselves are recursively also modelled by property values. This makes it possible to map also complex |
| structures such as JSON, XML or YAML data. Finally a property value also may contain additional metadata entries. |
| *** can _optionally_ provide access to a +Map<String,PropertyValue>+, providing all its properties at once. |
| *** defines the default ordinal to be used for establishing the order of significance among all |
| auto-discovered property sources. |
| ** _PropertySourceProvider:_ allows to automatically register multiple property sources, e.g. all config files found in |
| a file system folder.. |
| ** +ConfigurationProviderSpi+ defines the interface to be implemented by the delegating bean that is implementing the |
| +ConfigurationProvider+ singleton. |
| ** +PropertyFilter+ allows filtering of property values prior getting returned to the caller. Filters by default are |
| registered as global filters. The final value of a configuration entry is the final value after all registered |
| filters have been applied. |
| ** A +ConfigurationContext+ is the container of all components that make up a configuration (+PropertySource, |
| PropertyFilter, PropertyConverter, MetadataProvider+) required to implement a +Configuration+. Also the ordering |
| of the property sources, filters and converters is defined by the context. |
| By default a +ConfigurationContext+ is automatically created on first use for each classloader collecting and |
| adding all registered artifacts. Based on a +ConfigurationContext+ a +Configuration+ can be created. |
| Summarizing a +ConfigurationContext+ contains the ordered property sources, property filters, converters and combination |
| policy used. Once a +ConfigurationContext+ is instanciated a corresponding +Configuration+ instance can be |
| created easily using +ConfigurationBuilder+. |
| ** Whereas the Tamaya's API provides access to automatic configuration resources, it also allows to create |
| _programmatically_ +Configuration+ instances. This can be achieved using a |
| +ConfigurationContextBuilder+. This builder can be obtained calling +Configuration.createConfigurationBuilder();+. |
| ** Finally +ServiceContext+ and +ServiceContextManager+ provide an abstraction to the underlying runtime environment, |
| allowing different component loading and lifecycle strategies to be used. This is very useful since component (service) |
| loading in Java SE, Java EE, OSGI and other runtime environments may be differ significantly. In most cases even |
| extension programmers will not have to deal with these internals. |
| |
| |
| |
| [[APIKeyValues]] |
| === Key/Value Pairs and More |
| |
| 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] |
| -------------------------------------------- |
| Properties props = new Properties(); |
| props.readProperties(...); |
| String val = props.getProperty("a.b.c"); |
| val = props.getProperty("a.b.c.1"); |
| ... |
| -------------------------------------------- |
| |
| But we also have more complex configuration mechanism today. For example YAML is widely used format, which also |
| supports lists and maps. Basically these can be mapped also as key/value pairs. Unfortunately this is not very |
| useful in some cases: |
| |
| * a configuration client want to access a list of server configurations |
| * a configuration client wants to map structures with named fields. |
| * both of the above. |
| |
| As an example think of a server, which wants to extract a list of all members in the current cluster. Hereby: |
| |
| * A owner is modelled by a +MembeConfigr+ class, with the fields +serverId, serverName, serverURL+. |
| * The current members should be accessed as list, as follows: |
| |
| [source,java] |
| ---- |
| List<MemberConfig> members = configuration.get("cluster.members", new TypeLiteral<List<MemberConfig>>(); |
| ---- |
| |
| This coud be modelled as property as well, but it is more convenient to let Tamaya have a more flexible model |
| to represent a configuration internally. This is why a +PropertyValue+ can have one of three flavors: |
| |
| . it is a simple literal value. |
| . it is an object containing none to many child property values as named fields. |
| . it is an array containing none to many child properties as unnamed array instances. |
| |
| Summarizing Tamaya is capable of fully represent the structures described by widely used configuration file formats. |
| |
| |
| ==== Why Using Strings Only |
| |
| There are good reason to keep of non String-values as core storage representation of configuration. Mostly |
| there are several 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 Configuration]] |
| |
| === Configuration |
| |
| +Configuration+ is the main artifact provided by Tamaya. It allows reading of single property values or all known |
| properties, but also supports type safe access: |
| |
| [source,java] |
| .Interface Configuration |
| -------------------------------------------- |
| public interface Configuration{ |
| String get(String key); |
| String getOrDefault(String key, String value); |
| <T> T get(String key, Class<T> type); |
| <T> T getOrDefault(String key, Class<T> type, T defaultValue); |
| <T> T get(String key, TypeLiteral<T> type); |
| <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue); |
| <T> Optional<T> getOptional(String key, TypeLiteral<T> type); |
| <T> Optional<T> getOptional(String key, Class<T> type); |
| Map<String,String> getProperties(); |
| |
| // extension points |
| Configuration map(UnaryOperator<Configuration> operator); |
| <T> T adapt(Function<Configuration,T> adapter); |
| |
| // Snapshots for consistent config access |
| ConfigurationSnapshot getSnapshot(Iterable<String> keys); |
| ConfigurationSnapshot getSnapshot(String... keys); |
| |
| ConfigurationContext getContext(); |
| ConfigurationBuilder toBuilder(); |
| static Configuration current(); |
| static Configuration current(ClassLoader classsLoader); |
| static void setCurrent(Configuration config); |
| static void setCurrent(Configuration config, ClassLoader classLoader); |
| static ConfigurationBuilder createConfigurationBuilder(); |
| |
| // the deault EMPTY configuration |
| static Configuration EMPTY{} |
| } |
| -------------------------------------------- |
| |
| Hereby |
| |
| * +<T> T get(String, Class<T>)+ provides type safe accessors for all basic wrapper types of the JDK. |
| * +map, adapt+ provide the extension points for adding additional functionality. |
| * +getProperties()+ provides access to all key/values, whereas entries from non scannable property sources may not |
| be included. |
| * +getOrDefault+ allows to pass default values as needed, returned if the requested value evaluated to +null+. |
| * +getConfigurationContext()+ allows access to the underlying components of a +Configuration+ instance. |
| * the +static+ methods allow access for obtaining or changing +Configuration+. |
| * +getSnapshot+ allows to create a configuration snapshot, which guarantees consistent and immutable access to |
| a +Configuration+. It can optionally be constraint to a set of keys, by default a full snapshot is created. |
| |
| Accessor methods also support multi-key access, where an +Iterable+ of keys can be provided. This basically |
| is equivalent to multiple calls to this method, where the result of the first successful call (returning |
| configuration data) will end the evaluation chain. |
| |
| The class +TypeLiteral+ is basically similar to the same class provided with CDI: |
| |
| [source,java] |
| -------------------------------------------- |
| public class TypeLiteral<T> implements Serializable { |
| |
| [...] |
| |
| protected TypeLiteral(Type type) { |
| this.type = type; |
| } |
| |
| protected TypeLiteral() { } |
| |
| public static <L> TypeLiteral<L> of(Type type){...} |
| public static <L> TypeLiteral<L> of(Class<L> type){...} |
| |
| public final Type getType() {...} |
| public final Class<T> getRawType() {...} |
| |
| public static Type getGenericInterfaceTypeParameter(Class<?> clazz, Class<?> interfaceType){...} |
| public static Type getTypeParameter(Class<?> clazz, Class<?> interfaceType){...} |
| |
| [...] |
| } |
| -------------------------------------------- |
| |
| |
| Instances of +Configuration+ can be accessed using the +Configuration.current()+ or +Configuration.current(ClassLoader)+ |
| singleton: |
| |
| [source,java] |
| .Accessing Configuration |
| -------------------------------------------- |
| Configuration config = Configuration.current(); |
| -------------------------------------------- |
| |
| Hereby the singleton is backed up by an instance of +ConfigurationProviderSpi+, which is managed by the |
| +ServiceContextManager+ (see later). |
| |
| |
| [[PropertyConverter]] |
| ==== Property Type Conversion |
| |
| As illustrated in the previous section, +Configuration+ also allows access of typed values. Internally |
| all properties are strictly modelled as Strings. As a consequence non String values must be derived by converting the |
| String values into the required target type. This is achieved with the help of +PropertyConverters+: |
| |
| [source,java] |
| -------------------------------------------- |
| public interface PropertyConverter<T>{ |
| T convert(String value, ConversionContext context); |
| } |
| -------------------------------------------- |
| |
| The +ConversionContext+ contains additional meta-information about the key accessed, including the key'a name and |
| additional metadata. This can be very useful, e.g. when the implementation of a +PropertyConverter+ requires additional |
| metadata for determining the correct conversion to be applied. |
| |
| +PropertyConverter+ instances can be implemented and registered by default using the Java +ServiceLoader+. The ordering |
| of the registered converters, by default, is based on the annotated +@Priority+ values (priority +0+ is assumed if the |
| annotation is missing). The first non-null result of a converter is returned as the final configuration value. |
| |
| Access to converters is provided by the current +ConfigurationContext+, which is accessible calling +Configuration.getConfigurationContext()+. |
| |
| |
| [[ExtensionPoints]] |
| === 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 (e.g. Java Time API and JSR 354) as well: |
| |
| * +map(UnaryOperator<Configuration> operator)+ allows to pass arbitrary unary functions that take and return instances of |
| +Configuration+. Operators can be used to cover use cases such as filtering, configuration views, security |
| interception and more. |
| * +adapt(Function<Configuration,T)+ allows to apply a function returning any kind of result based on a |
| +Configuration+ instance. Queries are used for accessing/deriving any kind of data based on of a +Configuration+ |
| instance, e.g. accessing a +Set<String>+ of root keys present. |
| |
| Both interfaces hereby are functional interfaces. Because of backward compatibility with Java 7 we did not use |
| +UnaryOperator+ and +Function+ from the +java.util.function+ package. Nevertheless usage is similar, so you can |
| use Lambdas and method references in Java 8: |
| |
| [source,java] |
| .Applying an Adapter using a method reference |
| -------------------------------------------- |
| ConfigSecurity securityContext = Configuration.current().adapt(ConfigSecurity::targetSecurityContext); |
| -------------------------------------------- |
| |
| NOTE: +ConfigSecurity+ is an arbitrary class only for demonstration purposes. |
| |
| |
| Operator calls basically look similar: |
| |
| [source,java] |
| .Applying a +ConfigurationOperator+ using a lambda expression: |
| -------------------------------------------- |
| Configuration secured = Configuration.current() |
| .with((config) -> |
| config.get("foo")!=null?; |
| FooFilter.apply(config): |
| config); |
| -------------------------------------------- |
| |
| |
| [[ConfigException]] |
| === ConfigException |
| |
| The class +ConfigException+ models the base *runtime* exception used by the configuration system. |
| |
| |
| [[SPI]] |
| == SPI |
| |
| [[PropertyValue]] |
| === PropertyValue |
| |
| On the API properties are represented as Strings only, whereas in the SPI value are represented as +ProeprtyValue+, |
| which contain |
| |
| * the property's _key_ (String) |
| * the property's _value_ (String) |
| * the property's _source_ (String, typically equals to the property source's name) |
| * any additional meta-data represented as _Map<String,String>_ |
| * named or unnamed child objects, arrays or text filters.s |
| |
| This helps to kepp all value relevant data together in one place and also allows to choose any kind of |
| representation for meta-data entries. The +PropertyValue+ itself is a final and _serializable_ data container, |
| which also has a powerful builder API (e.g. for using within filters): |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| public final class PropertyValue implements Serializable{ |
| [...] |
| |
| public static ObjectValue createObject(){ |
| public static ListValue createList(){ |
| public static PropertyValue createValue(String key, String value){ |
| public static ListValue createList(String key){ |
| public static ObjectValue createObject(String key) |
| |
| public final boolean isImmutable(); |
| public PropertyValue immutable(); |
| public PropertyValue mutable(); |
| public final ValueType getValueType(); |
| |
| public String getKey(); |
| public String getQualifiedKey(); |
| public String getSource(); |
| public String getValue(); |
| public PropertyValue setValue(String value); |
| public Map<String, String> getMetaEntries(); |
| public String getMetaEntry(String key); |
| |
| public final PropertyValue getParent(); |
| public final int getVersion(); |
| public final boolean isRoot(); |
| public final boolean isLeaf(); |
| public static Map<String,PropertyValue> map(Map<String, String> config, String source); |
| public static Map<String,PropertyValue> map(Map<String, String> config, String source, |
| Map<String,String> metaData); |
| ---------------------------------------------------------------- |
| |
| When writing your own datasource you can easily create your own +PropertyValues+: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| PropertyValue val = PropertyValue.createValue("key","value"); |
| ---------------------------------------------------------------- |
| |
| You can also add additional metadata: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| val.addMetaEntry("figured", "true"); |
| ---------------------------------------------------------------- |
| |
| +PropertyValues+ are type safe value objects. To render a value |
| immutable just call the corresponding method: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| //mutable instance |
| PropertyValue val = ...; |
| |
| // immutable instance |
| PropertyValue newVal = val.immutable(); |
| ---------------------------------------------------------------- |
| |
| Changing an immutable value will result in a +IllegalStateException+. The process also works the other way |
| round, so an immutable instance can be rendered into a mutable as follows: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| //immutable instance |
| PropertyValue val = ...; |
| |
| // mutable instance |
| PropertyValue newVal = val.mutable(); |
| ---------------------------------------------------------------- |
| |
| [[ObjectValue]] |
| ==== ObjectValue and ListValue |
| |
| In many cases using +PropertyValues+ is sufficient. Nevertheless when handling more complex configuration sources, such |
| as file sources mapping hierarchical structures helps a lot. This is what +ObjectValue+ and +listValue+ are for: |
| |
| * +ObjectValue+ defines a +PropertyValue+ that has an arbitrary number of child values, identified by a unique text key. |
| * +ListValue+ defines a +PropertyValue+ that has an arbitrary number of child values, organized as a list of |
| values. |
| |
| When rendered to +Map<String,String>+ generated keys look very familiar: |
| |
| [source,properties] |
| ---------------------------------------------------------------- |
| a.a=valA |
| a.b=valB |
| a.list[0]=val0 |
| a.list[1]=val1 |
| a.c=valC |
| ---------------------------------------------------------------- |
| |
| Hereby |
| |
| * +a+ can be mapped as an +ObjectValue+, with +a,b,c+ being normale +PropertyValue+ instances. |
| * +list+ is also a child of +a+, but of type +ListValue+ containing two +PropertyValue+ instances. |
| |
| |
| [[PropertySource]] |
| === Interface PropertySource |
| |
| We have seen that constraining configuration aspects to simple literal key/value pairs provides us with an easy to |
| understand, generic, flexible, yet extensible 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 drawbacks. Notably implementation |
| of these types is far not trivial and the collection API offers additional functionality not useful when aiming |
| for modelling simple property sources. |
| |
| To render an implementation of a custom +PropertySource+ as convenient as possible only the following methods were |
| identified to be necessary: |
| |
| [source,java] |
| -------------------------------------------- |
| public interface PropertySource{ |
| |
| default int getOrdinal(); |
| String getName(); |
| PropertyValue get(String key); |
| Map<String,PropertyValue> getProperties(); |
| |
| ... |
| } |
| -------------------------------------------- |
| |
| Hereby |
| |
| * +get+ looks similar to the methods on +Map+. It may return +null+ in case no such entry is available. |
| * +getProperties+ allows to extract all property data to a +Map<String,PropertyValue>+. Other methods like +containsKey, |
| keySet+ as well as streaming operations then can be applied on the returned +Map+ instance. |
| * +int getOrdinal()+ defines the ordinal of the +PropertySource+. Property sources are managed in an ordered chain, where |
| property sources with higher ordinals override ones with lower ordinals. If the ordinal of two property sources is |
| the same, the natural ordering of the fully qualified class names of the property source implementations is used. |
| The reason for not using +@Priority+ annotations is that property sources can define dynamically their ordinals, |
| e.g. based on a property contained with the configuration itself. |
| Implementations of this API may provide additional functionality to adapt the default ordinal of auto-discovered |
| property sources. |
| * Finally +getName()+ returns a (unique) name that identifies the +PropertySource+ within its containing +ConfigurationContext+. |
| |
| NOTE: Not in all scenarios a property source is able to provide all values at once, e.g. |
| when looking up keys is very inefficient. In this case |
| +getProperties()+ may not return all key/value pairs that would be available when accessed directly using the |
| +PropertyValue get(String)+ method. |
| |
| 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. |
| |
| +PropertySources+ to be picked up automatically and be added to the _default_ +Configuration, must be registered |
| using the Java +ServiceLoader+ (or the mechanism provided by the current active +ServiceContext+, see later in this |
| document for further details). |
| |
| ==== Consistent Configuration Access |
| |
| For consistent configuration access using Snapshots property sources may provide additional services: |
| |
| [source,java] |
| -------------------------------------------- |
| public interface PropertySource{ |
| |
| ... |
| |
| // Method for consistent configuration access |
| |
| default ChangeSupport getChangeSupport(); |
| default String getVersion(); |
| default void addChangeListener(BiConsumer<Set<String>, PropertySource> l); |
| default void removeChangeListener(BiConsumer<Set<String>, PropertySource> l); |
| } |
| -------------------------------------------- |
| |
| These methods allow to determine Tamaya if all values accessed are consistent. The idea behind is that a |
| +PropertySource+ may change during a configuration evaluation. Providing a version allows the configuration system |
| to detect such a change and restart the evaluation. Additionally registering of change listeners allow actively |
| listening for changes. Since not all implementations may support versioning the may declare their capabilities: |
| |
| * +getChangeSupport()+ declares the versioning capabilities, possible values are +UNSUPPORTED,SUPPORTED,IMMUTABLE+. |
| * +getVersion()+ returns a version. Returning a new String value signals c change in the property source. |
| * +add/removeChangeListener+ allows to add or remove listeners. |
| |
| |
| [[PropertySourceProvider]] |
| === Interface PropertySourceProvider |
| |
| Instances of this type can be used to register multiple instances of +PropertySource+. |
| |
| [source,java] |
| -------------------------------------------- |
| @FunctionalInterface |
| public interface PropertySourceProvider{ |
| Collection<PropertySource> getPropertySources(); |
| } |
| -------------------------------------------- |
| |
| This allows to evaluate the property sources to be read/that are available dynamically. All property sources |
| are read out and added to the current chain of +PropertySource+ instances within the current +ConfigurationContext+, |
| refer also to [[ConfigurationContext]]. |
| |
| +PropertySourceProviders+ are by default registered using the Java +ServiceLoader+ or the mechanism provided by the |
| current active +ServiceContext+. |
| |
| |
| [[PropertyFilter]] |
| === Interface PropertyFilter |
| |
| Also +PropertyFilters+ can be added to a +Configuration+. They are evaluated each time before a configuration value |
| is passed to the user. Filters can be used for multiple purposes, such as |
| |
| * resolving placeholders |
| * masking sensitive entries, such as passwords |
| * constraining visibility based on the current active user |
| * ... |
| |
| For +PropertyFilters+ to be picked up automatically and added to the _default_ +Configuration+ must be,by default, |
| registered using the Java +ServiceLoader+ (or the mechanism provided by the current active +ServiceContext+). |
| Similar to property sources they are managed in an ordered filter chain, based on the |
| class level +@Priority+ annotations (assuming +0+ if none is present). |
| |
| A +PropertyFilter+ is defined as follows: |
| |
| [source,java] |
| -------------------------------------------- |
| @FunctionalInterface |
| public interface PropertyFilter{ |
| PropertyValue filterProperty(PropertyValue value, FilterContext context); |
| } |
| -------------------------------------------- |
| |
| Hereby: |
| |
| * returning +null+ will remove the key from the final result. |
| * non null values are used as the current value of the key. Nevertheless for resolving multi-step dependencies |
| filter evaluation has to be continued as long as filters are still changing some of the values to be returned. |
| To prevent possible endless loops after a defined number of loops evaluation is stopped. |
| * +FilterContext+ provides additional metdata, including the property accessed, which is useful in many use cases. |
| |
| This method is called each time a single entry is accessed, and for each property in a full properties result. |
| |
| |
| [[ConfigurationContext]] |
| ==== The Configuration Context |
| |
| A +Configuration+ is created based on a +ConfigurationContext+, which ia accessible calling |
| +configuration.getContext()+: |
| |
| [source,java] |
| .Accessing the +ConfigurationContext+ of a configuration |
| -------------------------------------------- |
| Configuration config = ...; |
| ConfigurationContext context = config.getContext(); |
| -------------------------------------------- |
| |
| The +ConfigurationContext+ provides access to the internal artifacts that determine the +Configuration+. |
| Similarly the context also defines the ordering and significance of property sources, filters and |
| converters: |
| |
| * +PropertySources+ registered (including the PropertySources provided from +PropertySourceProvider+ instances). |
| * +PropertyFilters+ registered, which filter values before they are returned to the client |
| * +PropertyConverter+ instances that provide conversion functionality for converting String values to any other types. |
| |
| |
| [[Mutability]] |
| ==== Changing a Configuration |
| |
| A +Configuration+ is basically not mutable. Nevertheless when the containing property sources provide different |
| values, e.g. because a configuration file has been updated, also the configuration values may change |
| (dependiing on the significance of the changed property source). |
| |
| Nevertheless it is also possible to create a new +ConfigurationBuilder+ based on an existing configuration |
| and add/remove property sources, filters or converters as needed. |
| |
| A new configuration builder can be easily accessed from an existing configuration as follows: |
| |
| [source,java] |
| .Accessing a +ConfigurationContextBuilder+ |
| -------------------------------------------- |
| Configuration config = ...; |
| ConfigurationBuilder preinitializedConfigBuilder = config.toBuilder(); |
| -------------------------------------------- |
| |
| It is also possible to builkd up a configuration completely from scratch, having full control on the |
| resources included: |
| |
| [source,java] |
| .Accessing a +ConfigurationContextBuilder+ |
| -------------------------------------------- |
| ConfigurationBuilder emptyConfigBuilder = Configuration.createConfigurationBuilder(); |
| -------------------------------------------- |
| |
| Using the builder we then can change the configuration as needed: |
| |
| [source,java] |
| -------------------------------------------- |
| ConfigurationBuilder builder = Configuration.crteateConfigurationBuilder(); |
| builder.addPropertySources(new MyPropertySource()) |
| .addPropertyFilter(new MyFilter()) |
| .setMeta("a.b.c.collectionType", "List") |
| .build(); |
| -------------------------------------------- |
| |
| |
| Let's have a short look at the +ConfigurationBuilder+. Basically such a |
| builder allows to add, remove or reorder property sources, converters and filters or changing any other aspect |
| of a +Configuration+. Finally a new +Configuration+ instance can be built. |
| |
| [source,java] |
| .Chain manipulation using +ConfigurationContextBuilder+ |
| -------------------------------------------- |
| PropertySource propertySource = builder.getPropertySource("sourceId"); |
| |
| // changing the priority of a property source. The ordinal value hereby is not considered. |
| // Instead the position of the property source within the chain is changed. |
| builder.decreasePriority(propertySource); |
| |
| // Alternately a comparator expression can be passed to establish the defined ordering... |
| builder.sortPropertyFilters(MyFilterComparator::compare); |
| -------------------------------------------- |
| |
| Finally if a new +Configuration+ can be built. Optionally the new +Configuration+ can also be installed as |
| the _default_ +Configuration+ instance as illustrated below: |
| |
| [source,java] |
| .Creating and applying a new +Configuration+ |
| -------------------------------------------- |
| // Creates a new matching Configuration instance |
| Configuration config = builder.build(); |
| |
| // Apply the new context to replace the current configuration: |
| Configuration.setCurrent(newConfig); |
| -------------------------------------------- |
| |
| Hereby +Configuration.setCurrent(Configuration)+ can throw an +UnsupportedOperationException+. |
| This can be checked by calling the method +boolean Configuration.isConfigurationSettable()+. |
| |
| == SPI |
| |
| [[ConfigurationProviderSpi]] |
| === Implementing and Managing Configuration |
| |
| One of the most important SPI in Tamaya is the +ConfigurationProviderSpi+ interface, which is backing up the |
| +Configuration+ static accessor methods. Implementing this interface allows |
| |
| * to fully determine the implementation class for +Configuration, ConfigurationBuilder, ConfigurationContext+ |
| * to manage +Configurations+ in the scope and granularity required. |
| * to provide access to the right +Configuration+ based on the current runtime context (e.g. classloader). |
| |
| [[BuilderCore]] |
| == Interface ConfigurationBuilder |
| |
| === Overview |
| |
| The Tamaya builder module provides a generic (one time) builder for creating +Configuration+ instances, |
| e.g. as follows: |
| |
| [source,java] |
| --------------------------------------------------------------- |
| ConfigurationBuilder builder = Configuration.createConfigurationBuilder(); |
| // do something |
| Configuration config = builder.build(); |
| --------------------------------------------------------------- |
| |
| Basically a builder allows to create configuration instances completely independent of the current configuration |
| setup. This gives you full control how and when +Configuration+ is created. |
| |
| |
| === Supported Functionality |
| |
| The builder allows you to add +PropertySource+ instances: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| ConfigurationBuilder builder = Configuration.createConfigurationBuilder(); |
| builder.addPropertySources(sourceOne, sourceTwo, sourceThree |
| Configuration config = builder.build(); |
| ---------------------------------------------------------------- |
| |
| Hereby the ordering of the property sources is not changed, regardless of the ordinals provided |
| by the property sources. This allows alternate ordering policies easily being implemented because |
| creating a configuration based on a configuration context is already implemented and provided by the core |
| API. |
| |
| Similarly you can add +PropertyFilters+: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| builder.addPropertyFilters(new MyConfigFilter()); |
| ---------------------------------------------------------------- |
| |
| ...or +PropertySourceProvider+ instances: |
| |
| [source,java] |
| ---------------------------------------------------------------- |
| builder.addPropertySourceProvider(new MyPropertySourceProvider()); |
| ---------------------------------------------------------------- |
| |
| ...and of course converters and other artifacts. |
| |
| |
| [[ServiceContext]] |
| == The ServiceContext |
| |
| The +ServiceContext+ allows to define how components are loaded in Tamaya. It is the glue layer, which interacts |
| with the underlying runtime system such as Java SE, Java EE, OSGI, VertX etc. |
| The +ServiceContext+ hereby defines access methods to obtain components, whereas itself it is available from the |
| +ServiceContextManager+ singleton: |
| |
| [source,java] |
| .Accessing the +ServiceContext+ |
| -------------------------------------------- |
| // using an explicit classloader (recommended) |
| ClassLoader classloader = ...; |
| ServiceContext serviceContext = ServiceContextManager.getServiceContext(classloader); |
| |
| // using the default classloader |
| ServiceContext serviceContext = ServiceContextManager.getServiceContext(); |
| |
| public interface ServiceContext{ |
| int ordinal(); |
| <T> T getService(Class<T> serviceType); |
| <T> T getService(Class<T> serviceType, Supplier<T> serviceSupplier); |
| <T> T createService(Class<T> serviceType); |
| <T> T createService(Class<T> serviceType, Supplier<T> serviceSupplier); |
| <T> List<T> getServices(Class<T> serviceType); |
| <T> List<T> getServices(Class<T> serviceType, Supplier<List<T>> serviceSupplier); |
| <T> T register(Class<T> type, T instance, boolean force); |
| <T> List<T> register(Class<T> type, List<T> instances, boolean force); |
| |
| Enumeration<URL> getResources(String resource) throws IOException; |
| URL getResource(String resource); |
| |
| } |
| -------------------------------------------- |
| |
| With the +ServiceContext+ a component can be accessed in two different ways: |
| |
| . access as as a single service. Hereby the detected services (if multiple) are sorted by priority and then finally |
| the most significant instance (the one with the highest priority value) is selected and cached. |
| . access all items given a type. This will return (by default) all service instances loadedable from the current |
| runtime context (classloader), ordered by priority (the most significant components added first). |
| . service lookup can be further customized by passing suppliers. The supplier is called if no default services |
| could be auto-detected. The supplied instance(s) are registered and cached for subsequent accesses. |
| . the _register_ methods allow to explcitly register (and optionally override) a service or services |
| registered. |
| . Finally the methods `getResource(s)` allow to load resources from the classpath. This is especially useful |
| when running in an OSGI context, where loading of resources from the classloaders will fail. |
| |
| |
| ## Examples |
| ### Accessing Configuration |
| |
| _Configuration_ is obtained from the `Configuration` interface using static accessors: |
| |
| [source,java] |
| .Accessing +Configuration+ |
| -------------------------------------------- |
| Configuration config = Configuration.current(); |
| Configuration config = Configuration.current(Thread.currentThread().getContextClassLoader()); |
| -------------------------------------------- |
| |
| Many users in a SE context will probably only work with _Configuration_, since it offers all functionality |
| needed for basic configuration with a very lean memory and runtime footprint. In Java 7 access to the keys is |
| very similar to *Map<String,String>*, whereas in Java 8 additionally usage of _Optional_ is supported: |
| |
| [source,java] |
| -------------------------------------------- |
| Configuration config = Configuration.current(); |
| String myValue = config.get("myKey"); // access as (raw) String value. |
| int myLimit = config.get("all.size.limit", int.class); // access a value using type conversion. |
| List<URL> urls = config.get("all.urls", new TypeLiteral<List<URL>>(); // access a value using advanced type conversion. |
| -------------------------------------------- |
| |
| |
| ### Environment and System Properties |
| |
| By default environment and system properties are included into the _Configuration_. So we can access the current |
| _PROMPT_ environment variable as follows: |
| |
| [source,java] |
| -------------------------------------------- |
| String prompt = ConfigurationProvider.getConfiguration().get("PROMPT"); |
| -------------------------------------------- |
| |
| Similary the system properties are directly applied to the _Configuration_. Let's assume, we pass the following system |
| property to our JVM: |
| |
| [source,java] |
| -------------------------------------------- |
| java ... -Duse.my.system.answer=yes |
| -------------------------------------------- |
| |
| We can then access the value from the configuration: |
| |
| [source,java] |
| -------------------------------------------- |
| boolean useMySystem = Configuration.current().get("use.my.system.answer", boolean.class); |
| -------------------------------------------- |
| |
| |
| ### Adding additional configuration entries |
| |
| Adding additional configuration entries is simple: just implement an according `PropertySource` and register |
| it with the Java `ServiceLoader`. Using the _tamaya-spi-support_ extension library you just have to perform a few steps: |
| |
| . Define a PropertySource as follows: |
| |
| [source,java] |
| -------------------------------------------- |
| public class MyPropertySource extends PropertiesResourcePropertySource{ |
| |
| public MyPropertySource(){ |
| super(ClassLoader.getSystemClassLoader().getResource("META-INF/cfg/myconfig.properties"), DEFAULT_ORDINAL); |
| } |
| } |
| -------------------------------------------- |
| |
| Then register +MyPropertySource+ using the +ServiceLoader+ by adding the following file: |
| |
| [source,listing] |
| -------------------------------------------- |
| META-INF/services/org.apache.tamaya.spi.PropertySource |
| -------------------------------------------- |
| |
| ...containing the following line: |
| |
| [source,listing] |
| -------------------------------------------- |
| com.mypackage.MyPropertySource |
| -------------------------------------------- |
| |
| |
| [[APIImpl]] |
| == API Implementation |
| |
| The Tamaya configuration API is implemented by the +tamaya-core+ module. Refer to the link:core.html[Core documentation] for |
| further details. |
| |
| Furhtermore Tamaya also implements or supports |
| |
| * the API as defined by JSR 310 (Config JSR) |
| * the Microprofile API |
| * the Spring Configuration Mechanism |
| * integration with the OSGI `ConfigAdmin` API. |
| * the Apache Camel Configuration SPI |