2017-11-25

Tamaya Injection (Extension Module)

Tamaya Injection is an extension module. Refer to the extensions documentation for further details.

What functionality this module provides ?

Tamaya Injection provides functionality for injecting configured values into beans, or creating configuration template instances.

Inversion of Control (aka IoC/the Hollywood Principle) has proven to be very useful and effective in avoiding boilerplate code. In Java there are different frameworks available that all provide IoC mechanisms. Unfortunately IoC is not a built-in language feature. So for a portable solution that works also in Java SE Tamaya itself has to provide the according injection services. This module adds this functionality to Tamaya.

Compatibility

The module is based on Java 7, so it can be used with Java 7 and beyond.

Installation

Basically Tamaya’s injection API is deployed as API artifact:

<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-injection-api</artifactId>
  <version>{tamaya_version}</version>
</dependency>

To use injection with Java SE you must add the corresponding dependency to your module:

<dependency>
  <groupId>org.apache.tamaya.ext</groupId>
  <artifactId>tamaya-injection</artifactId>
  <version>{tamaya_version}</version>
</dependency>

Similarly there are other injection implementations available, targetig platforms such as

Core Concepts

Basically you annotate fields or methods in your beans with @Config to enable configuration injection. Tamaya additionally defines further annotations that allo you to define additional aspects such as default values, custom converters etc. The following example illustrates the basic functionality: code snippet:

Annotated Example Class
package foo.bar;

public class ConfiguredClass {

    // resolved by default, using property name, class and package name: foo.bar.ConfiguredClass.testProperty
    private String testProperty;

    // Trying to resolve mutiple keys, with a default value, if none could be resolved
    @Config({"a.b.c.key1","a.b.legacyKey",area1.key2"}, defaultValue="The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.")
    String value1;

    // Typical case
    @Config("a.b.c.key2")
    private int value2;

    // resolved by default as foo.bar.ConfiguredClass.accessUrl
    // Using a (default) String -> URL converter
    @Config(defaultValue="http://127.0.0.1:8080/res/api/v1/info.json")
    private URL accessUrl;

    // Config injection disabled for this property
    @NoConfig
    private Integer int1;

    // Overriding the String -> BigDecimal converter with a custom implementation.
    @Config("BD")
    @WithPropertyConverter(MyBigDecimalRoundingAdapter.class)
    private BigDecimal bigNumber;

    ...
}

When configuring data or configuration classes it is also possible to auto-inject the fields identified. For activating this feature a class must be annotated with @ConfigAutoInject:

. An autoinjected bean class
--------------------------------------------
package a.b;

@ConfigAutoInject public final class Tenant { private int id; private String name; private String description; @NoConfig // prevents auto injection for this field private String id2;

  public int getId(){
    return id;
  }
  public String getName(){
    return name;
  }
  public String getDescription(){
    return description;
  }
}
These examples do not show all possibilities provided. Configuring instance of these
class using Tamaya is very simple: Just pass the instance to Tamaya to let
Tamaya inject the configuration (or throw a +ConfigException+, if this is not possible):

[source,java]
.Configuring the +ConfiguredClass+ Instance

ConfiguredClass classInstance = new ConfiguredClass(); ConfigurationInjector.configure(configuredClass);

Tenant tenant = new Tenant(); ConfigurationInjector.configure(tenant);

NOTE: Configuration injection works similarly, when used with other integration modules, e.g. when Tamaya is used
with CDI, Spring or within an OSGI container. For further details refer also to the corresponding integration module's
documentation.


==== The ConfigurationInjector

The +ConfigurationInjector+ interface provides methods that allow any kind of instances to be configured
by passing the instances to +T ConfigurationInjector.getInstance().configure(T);+. The classes passed
hereby must not be annotated with +@Config+ for being configurable.

==== Accessing Supplier instances

In many cases you want to create a supplier that simply creates instances that are correctly configured as defined
by the current context. This can be done using +Suppliers+:

[source, java]

Supplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier( new Supplier<Tenant>(){ public Tenant get(){ return new Tenant(); } });

With Java 8 it's even more simple:

[source, java]

Supplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier( Tenant::new);

Hereby this annotation can be used in multiple ways and combined with other annotations such as
+@WithLoadPolicy+, +@WithConfigOperator+, +@WithPropertyConverter+.


==== Minimal Example

To illustrate the mechanism below the most simple variant of a configured class is given:

[source,java]
.Most simple configured class

pubic class ConfiguredItem{ @Config private String aValue; }

When this class is configured, e.g. by passing it to +ConfigurationInjector.getInstance().configure(Object)+,
the following is happening:

* The current valid +Configuration+ is evaluated by calling +Configuration cfg = ConfigurationProvider.getConfiguration();+
* The current property value (String) is evaluated by calling +cfg.get("aValue");+ for each possible key (mutliple
  keys are possible).
* if not successful, an error is thrown (+ConfigException+)
* On success, since no type conversion is involved, the value is injected.


=== The Annotations in detail

==== Using `@Config`

This is the main annotation targeting a field in a class for configuration injection.

===== Evaluating of _configuration keys_

By default Tamaya tries to determine configuration for each property of an instance
passed, using the following resolution policy:

* Given a class +a.b.MyClass+ and a field +myField+ it would try to look up the
  following keys:

[source, listing]

a.b.MyClass.myField a.b.MyClass.my-field MyClass.myField MyClass.my-field myField my-field

This behaviour can be adapted, e.g. by using the `@ConfigDefaultSections` annotation on the
declaring type:

@ConfigDefaultSections("a.b.c", "deprecated") pubic class MyClass{ @Config private String myField; }

This will result in a modified lookup chain as illustrated below:

[source, listing]

a.b.c.myField a.b.c.my-field deprecated.myField deprecated.my-field

This helps to reduce redundancy when referring to you configuration keys. Additionally
it is also possible to define absolute key entries, e.g.

@ConfigDefaultSections("a.b.c") pubic class MyClass{ @Config("myField" /* relative */, "[absolute.key]") private String myField; }

This will result in a lookup chain as illustrated below:

[source, listing]

a.b.c.myField absolute.key # default sections are ignored

===== Using defaults

In the next example we explicitly define the _default_ property value:
[source,java]

pubic class ConfiguredItem{

  @Config(value={"aValue", "a.b.value","a.b.deprecated.value"}, defaultValue="${env:java.version}")
  private String aValue;
}
==== Automatically inject all items using `@ConfigAutoInject`

Using `@ConfigAutoInject` allows you to automatically select all properties found for
configuration injection:

[source,java]

@ConfigAutoInject pubic class ConfiguredItem{

private transient int sum;
  private String a;
  private String b;
  Private String c;
}
Adding the `@NoConfig` annotation prevents a field or method to be auto-injected from
configuration. This is especially useful, if a type is annotated as @ConfigAutoInject with auto-confiuration
turned on as follows:

[source,java]

@NoConfig private transient int sum;

In this case the fields +a,b,c+ are configured, whereas the field +sum+ is ignored regarding
configuration.


==== Adding custom operators using `@WithConfigOperator`

The @WithConfigOperator annotation allows you define a class of type +ConfigOperator+, to being applied
to the final +Configuration+, BEFORE the value is injected. This can be used for various use cases, e.g.
filtering or validating the visible properties for a certain use case.

[source,java]

@WithConfigOperator(MyConfigView.class) pubic class ConfiguredItem{

@Config
private String a;

}

==== Adding custom property converters using `@WithPropertyConverter`

The @WithPropertyConverter annotation allows you to define a class of type +PropertyConverter+, to be applied
on a property configured to convert the String value to the expected injected type. This can be used for
various use cases, e.g. adding custom formats, config models, decryption.

[source,java]

pubic class ConfiguredItem{

@WithPropertyConverter(MyPropertyConverter.class)
@Config
private String a;

}

==== Inject a `DynamicValue`

Within this example we evaluate a dynamic value. This mechanism allows you to listen for configuration changes and to
commit new values exactly, when convenient for you.

[source,java]

pubic class ConfiguredItem{

  @Config(value={"aValue", "a.b.value","a.b.deprecated.value"}, defaultValue="${env:java.version}")
  private DynamicValue aValue;
}
The +DynamicValue+ provides you the following functionality:

[source,java]

public interface DynamicValue<T> {

T get();
T getNewValue();
T evaluateValue();
T commitAndGet();
void commit();
void discard();
boolean updateValue();
void setUpdatePolicy(UpdatePolicy updatePolicy);
UpdatePolicy getUpdatePolicy();
void addListener(PropertyChangeListener l);
void removeListener(PropertyChangeListener l);
boolean isPresent();
T orElse(T other);
// Enabled with Java 8
// T orElseGet(ConfiguredItemSupplier<? extends T> other);
// <X extends Throwable> T orElseThrow(ConfiguredItemSupplier<? extends X> exceptionSupplier) throws X;

}

public enum UpdatePolicy{ IMMEDIATE, EXPLCIT, NEVER, LOG_AND_DISCARD }

//Summarizing +DynamicValue+ looks somehow similar to the new +Optional+ class added with Java 8. It provides
//a wrapper class around a configured instance. Additionally this class provides functionality that gives
//active control, to manage a configured value based on a ++LoadingPolicy+:
//
//* +IMMEDEATE+ means that when the configuration system detects a change on the underlying value, the new value
//  is automatically applied without any further notice.
//* +EXPLICIT+ means that a new configuration value is signalled by setting the +newValue+ property. if +getNewValue()+
//  returns a non null value, the new value can be applied by calling +commit()+. You can always access the newest value,
//  hereby implicitly applying it, by accessing it via +commitAndGet()+. Also it is possible ti ignore a change by calling
//  +discard()+.
//* +NEVER+ means the configured value is evaluated once and never updated. All changes are silently discarded.
//* +LOG_AND_DISCARD+ similar to +NEVER+, but changes are logged before they are discarded.

Summarizing a +DynamicValue+ allows you

* to reload actively updates of configured values.
* update implicitly or explicitly all changes on the value.
* add listeners that observe changes of a certain value.

Dynamic values also allow on-the-fly reevaluation of the value by calling +evaluateValue()+. Hereby the value of the
instance is not changed.


===== The Loading policy enum

The +LoadPolicy+ enum defines different configuration loading behaviour
to be applied:

[source,java]

@Deprecated public enum LoadPolicy { / * The configuration keys is evaluated once, when the owning component is loaded/configured, but never updated later. / INITIAL, / * The configuration keys is evaluated exactly once on its first access/use lazily, but never updated later. * @see DynamicValue#get() * @see DynamicValue#commitAndGet() */ LAZY, /* * The configuration value is evaluated every time it is accessed. */ ALWAYS }

This enum type currently is used only internally, so avoid using it as of
now in your code is recommended.


=== Configuration Events

Similar to CDI Tamaya publishes Configuration events, when instances were configured. It depends on the effective
event backend in use, if and how events are published:

* when you have the CDI extension active events are published using the default CDI event mechanism.
* in all other scenarios events are delegated to the +tamaya-events+ module, if available,
* if no event delegation is available no events are published.

The event published is very simple:

[source,java]

public interface ConfiguredType { Class getType(); String getName(); Collection<ConfiguredField> getConfiguredFields(); Collection<ConfiguredMethod> getConfiguredMethods(); void configure(Object instance, Configuration config); }

public interface ConfiguredField { Class<?> getType(); Collection<String> getConfiguredKeys(); String getName(); String getSignature(); Field getAnnotatedField(); void configure(Object instance, Configuration config); }

public interface ConfiguredMethod { Collection<String> getConfiguredKeys(); Class<?>[] getParameterTypes(); Method getAnnotatedMethod(); String getName(); String getSignature(); void configure(Object instance, Configuration config); }