2019-11-17

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 High Level Design Documentation

The Tamaya API

The API provides the artifacts as described in the 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.

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.

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.

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:

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:

  1. it is a simple literal value.

  2. it is an object containing none to many child property values as named fields.

  3. 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.

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:

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:

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:

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).

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:

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().

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:

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:

Applying a ConfigurationOperator using a lambda expression:
Configuration secured = Configuration.current()
                           .with((config) ->
                                 config.get("foo")!=null?;
                                 FooFilter.apply(config):
                                 config);

ConfigException

The class ConfigException models the base runtime exception used by the configuration system.

SPI

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):

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:

PropertyValue val = PropertyValue.createValue("key","value");

You can also add additional metadata:

val.addMetaEntry("figured", "true");

PropertyValues are type safe value objects. To render a value immutable just call the corresponding method:

//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:

//immutable instance
PropertyValue val = ...;

// mutable instance
PropertyValue newVal = val.mutable();

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:

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.

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:

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:

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.

Interface PropertySourceProvider

Instances of this type can be used to register multiple instances of PropertySource.

@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 .

PropertySourceProviders are by default registered using the Java ServiceLoader or the mechanism provided by the current active ServiceContext.

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:

@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.

The Configuration Context

A Configuration is created based on a ConfigurationContext, which ia accessible calling configuration.getContext():

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.

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:

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:

Accessing a ConfigurationContextBuilder
ConfigurationBuilder emptyConfigBuilder = Configuration.createConfigurationBuilder();

Using the builder we then can change the configuration as needed:

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.

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:

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

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).

Interface ConfigurationBuilder

Overview

The Tamaya builder module provides a generic (one time) builder for creating Configuration instances, e.g. as follows:

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:

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:

builder.addPropertyFilters(new MyConfigFilter());

…​or PropertySourceProvider instances:

builder.addPropertySourceProvider(new MyPropertySourceProvider());

…​and of course converters and other artifacts.

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:

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:

  1. 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.

  2. 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).

  3. 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.

  4. the register methods allow to explcitly register (and optionally override) a service or services registered.

  5. 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:

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:

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:

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:

java ... -Duse.my.system.answer=yes

We can then access the value from the configuration:

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:

  1. Define a PropertySource as follows:

  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:

META-INF/services/org.apache.tamaya.spi.PropertySource

…​containing the following line:

com.mypackage.MyPropertySource

API Implementation

The Tamaya configuration API is implemented by the tamaya-core module. Refer to the Core documentation for further details.

Furthermore 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