blob: c3b3c6449966aee3bb3d3976a35f334c82ed5fa2 [file] [log] [blame]
: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