1. Requirements

1.1. Core Configuration Requirements

1.1.1. General

Tamaya must provide a Java SE API for accessing key/value based configuration. Hereby

  • Configuration is modelled by an interface

  • Configuration is organized as key/value pairs, using a subset of functionality present on Map<String,String> as follows:

    • access a value by key (get)

    • check if a value is present (containsKey)

    • get a set of all defined keys (keySet)

    • a configuration must be convertible to a Map, by calling toMap()

    • a configuration must provide access to its meta information.

  • Configuration value access methods must never return null.

  • The API must support undefined values.

  • The API must support passing default values, to be returned if a value is undefined.

  • The API must allow to throw exceptions, when a value is undefined. Customized exceptions hereby should be supported.

  • Properties can be stored in the classpath, on a file or accessible by URL.

  • Properties can be stored minimally in properties, xml-properties or ini-format.

1.1.2. Minimalistic Property Source

For enabling easy integration of custom built configuration sources a minimalistic API/SPI must be defined, that

  • is modelled by an interface

  • is a minimal subset of Configuration necessary to implement a configuration.

  • must be convertible to a "Configuration+.

1.1.3. Extension Points

For supporting more complex scenarios, Configuration

  • must implement the composite pattern, meaning new Configuration instances can be created by combining existing configurations.

  • must be adaptable, by creating a new configuration by applying a UnaryOperator<COnfiguration> to it.

  • must be queryable, by passing a ConfigQuery to an Configuration instance.

1.1.4. Type Safety

Besides Strings Configuration should also support the following types:

  • Primitive types

  • Wrapper types

  • All other types (by using a PropertyAdapter

Hereby type conversion should be done as follows:

  1. Check if for the given target type an explicit adapter is registered, if so, use the registered adapter.

  2. If no adapter is present, check if the target type T has static methods called T of(String), T getInstance(String), T valueOf(String), T from(String). If so use this method to create the non value of T.

  3. Check if the target type has a constructor T(String). If so, try to instantiate an instance using the constructor.

  4. Give up, throw a IllegalArgument exception.

1.2. Configuration Fomats

By default Tamaya support the following configuration formats:

  • .properties

  • .xml properties

  • .ini files

It must be possible to add additional formats by registering them with the current ServiceContext.

1.3. Mutability

  • Configurations can be mutable, mutability can be accessed as a property.

  • Configuration can be changed by collecting the changes into a ConfigCHangeSet and apply this set to the given Configuration instance.

  • Besides the points above, Configuration is immutable.

1.4. Serializability and Immutability of Configuration

  • Configuration is modelled as a service. Therefore serialization may not work. This can be mitigated by adding a freeze feature, where the know key/value pairs are extracted into an immutable and serializable form.

1.5. Configuration Combination Requirements

At least the following composition policies must be supported:

  • override: subsequent entries override existing ones.

  • aggregate-exception: key/values were added, in case of conflicts a ConfigException must be thrown.

  • aggregate-ignore-duplicates: similar to union, whereas duplicates are ignored (leaving the initial value loaded).

  • aggregate-combine: conflicting entries were resolved by adding them both to the target configuration by redefining partial keys.

  • custom: any function determining the key/values to be kept must be possible

When combining configuration it must also be possible to override (file/classpath) configuration by

  • system properties.

  • command line arguments.

1.6. Configuration Injection

As metnioned configuration can be injected by passing a unconfigured instance of an annotated class to the Configuration.configure static method:

Configuring a POJO
MyPojo instance = new MyPojo();
Configuration.configure(instance);

Hereby * It must be possible to define default values to be used, if no valid value is present. * It must be possible to define dynamic expressions, at least for default values. * The values configured can be reinjected, if the underlying configuration changes. This should also be the case for final classes, such as Strings. * Reinjection should be controllable by an loading policy. * It must be possible to evaluate multiple keys, e.g. current keys, and as a backup deprecated keys from former application releases. * It must be possible to evaluate multiple configurations. * The type conversion of the properties injected must be configurable, by defining a PropertyAdapter. * The value evaluated for a property (before type conversion) must be adaptable as well. * It must be possible to observe configuration changes.

The following annotations must be present at least:

  • @ConfiguredProperty defining the key of the property to be evaluated. It takes an optional value, defining the property name. It must be possible to add multiple annotations of this kind to define an order of evaluation of possible keys.

  • @DefaultValue (optional) defines a default String value, to be used, when no other key is present.

  • @WithConfig (optional) defines the name of the configuration to be used. Similar to @ConfiguredProperty multiple configuration can be defined for lookup.

  • @WithConfigOperator allows to adapt the String value evaluated, before it is passed as input to injection or type conversion.

  • @WithPropertyAdapter allows to adapt the conversion to the required target type, hereby overriding any default conversion in place.

  • @WithLoadPolicy allows to define the policy for (re)injection of configured values.

  • @ObservesConfigChange allows to annotate methods that should be called on configuration changes.

  • *@DefaultAreas" allows to define a key prefix key to be used for the configured key, if no absolute key is defined.

1.7. Configuration Templates

For type safe configuration clients should be able to define an interface and let it implement by the configuration system based on Configuration available:

  • Clients define an interface and annotate it as required (similar to above)

  • The interface methods must not take any arguments

  • The configuration system can be called to return such an interface implementation.

  • The configuration system returns a proxy hereby providing type-safe access the values required.

  • Similar to configured types also templates support multiple values and custom adapters.

  • It is possible to listen on configuration changes for templates, so users of the templates may react on configuration changes.

The following snippet illustrates the requirements:

Type Safe Configuration Template Example
public interface MyConfig {

  @ConfiguredProperty("myCurrency")
  @DefaultValue("CHF")
  String getCurrency();

  @ConfiguredProperty("myCurrencyRate")
  Long getCurrencyRate();

  @ConfigChange
  default configChanged(ConfigChange event){
     ...
  }

}

Templates can be accessed by calling the Configuration.current(Class) method:

Accessing a type safe Configuration Template
MyConfig config = Configuration.current(MyConfig.class);

1.8. Server Configuration Requirements

  • Ensure Configuration can be transferred over the network easily.

  • Beside serializability text based formats for serialization in XML and JSON must be defined.

  • A management API must be defined, which allows to inspect the configuration in place, e.g. using JMX or REST services.

Java EE leads to the following requirements:

  • Configuration must be contextual, depending on the current runtime context (e.g. boot level, ear, war, …​).

  • Hereby contextual aspects can even exceed the levels described above, e.g. for SaaS scenarios.

  • Resources can be unloaded, e.g. wars, ears can be restarted.

  • The different contextual levels can also be used for overriding, e.g. application specific configuration may override ear or system configuration.

  • Configuration may be read from different sources (different classloaders, files, databases, remote locations).

  • Configuration may be read in different formats (deployment descriptors, ServiceLoader configuration, alt-DD feature, …​)

  • JSF also knows the concept of stages.

  • Many SPI’s of Java EE require the implementation of some well defined Java interface, so it would be useful if the configuration solution supports easy implementation of such instances.

  • In general it would be useful to model the Environment explicitly.

  • Configuration used as preferences is writable as well. This requires mutability to be modelled in way, without the need of synchronization.

  • JNDI can be used for configuration as well.

Configurations made in the tenant or user layer override the default app configuration etc., so

  • It must be possible to structure Configuration in layers that can override/extend each other.

  • The current environment must be capable of mapping tenant, user and other aspects, so a corresponding configuration (or layer) can be derived.

1.9. Extensions Requirements

It must be possible to easily add additional functionality by implementing external functional interfaces operating on Configuration.

  • UnaryOperator<Configuration> for converting into other version of Configuration.

  • ConfigQuery<T> extending Function<T, Configuration>.

1.10. Non Functional Requirements

THe following non-functional requirements must be met:

  • tbd