blob: 63d69e028af5c4c163c7936b0d0e6107c3937b01 [file] [log] [blame]
= Apache Tamaya -- Extension: Injection
:name: Tamaya
:rootpackage: org.apache.tamaya.ext.injection
:title: Apache Tamaya Extension: Injection
:revnumber: 0.1.1
:revremark: Incubator
:revdate: March 2015
:longversion: {revnumber} ({revremark}) {revdate}
:authorinitials: ATR
:author: Anatole Tresch
:email: <anatole@apache.org>
:source-highlighter: coderay
:website: http://tamaya.incubator.apache.org/
:toc:
:toc-placement: manual
:encoding: UTF-8
:numbered:
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
'''
<<<
toc::[]
<<<
:numbered!:
<<<
[[Core]]
== Tamaya Injection (Extension Module)
=== Overview
Tamaya Injection is an extension module. Refer to the link:modules.html[extensions documentation] for further details
about modules.
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
To benefit from configuration event support you only must add the corresponding dependency to your module:
[source, xml]
-----------------------------------------------
<dependency>
<groupId>org.apache.tamaya.ext</groupId>
<artifactId>tamaya-injection</artifactId>
<version>{tamayaVersion}</version>
</dependency>
-----------------------------------------------
=== Core Concepts
As an example refer to the following
code snippet:
[source,java]
.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;
@ConfiguredProperty(keys={"a.b.c.key1","a.b.legacyKey",area1.key2"})
@DefaultValue("The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.")
String value1;
// Using a (default) String -> Integer converter
@ConfiguredProperty(keys="a.b.c.key2")
private int value2;
// resolved by default as foo.bar.ConfiguredClass.accessUrl
// Using a (default) String -> URL converter
@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.
@ConfiguredProperty(keys="BD")
@WithPropertyConverter(MyBigDecimalRoundingAdapter.class)
private BigDecimal bigNumber;
...
}
--------------------------------------------
The class does not show all (but most) possibilities provided. Configuring an instance of the
class using Tamaya is very simple. The only thing is to 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);
--------------------------------------------
=== The Annotations in detail
==== 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 +@ConfiguredProperty+ for being configurable. 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
--------------------------------------------
So given the following properties:
[source, properties]
--------------------------------------------
a.b.Tenant.id=1234
Tenant.description=Any kind of tenant.
name=<unnamed>
--------------------------------------------
The following bean can be configured as follows:
[source, java]
--------------------------------------------
package a.b;
@ConfiguredType(autoConfigure=true)
public final class Tenant{
private int id;
private String name;
private String description;
public int getId(){
return id;
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
}
Tenant tenant = ConfigurationInjector.getInstance().configure(new Tenant());
--------------------------------------------
==== Accessing ConfiguredItemSupplier 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]
--------------------------------------------
ConfiguredItemSupplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier(
new ConfiguredItemSupplier<Tenant>(){
public Tenant get(){
return new Tenant();
}
});
--------------------------------------------
With Java 8 it's even more simpler:
[source, java]
--------------------------------------------
ConfiguredItemSupplier<Tenant> configuredTenantSupplier = ConfigurationInjector.getInstance().getConfiguredSupplier(
Tenant::new);
--------------------------------------------
Hereby this annotation can be used in multiple ways and combined with other annotations such as +@DefaultValue+,
+@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{
@ConfiguredProperty
private String aValue;
}
--------------------------------------------
When this class is configured, e.g. by passing it to +Configuration.configure(Object)+,
the following is happening:
* The current valid +Configuration+ is evaluated by calling +Configuration cfg = Configuration.of();+
* The current property value (String) is evaluated by calling +cfg.get("aValue");+
* if not successful, an error is thrown (+ConfigException+)
* On success, since no type conversion is involved, the value is injected.
* The configured bean is registered as a weak change listener in the config system's underlying
configuration, so future config changes can be propagated (controllable by applying the
+@WithLoadPolicy+ annotation).
==== Using @DefaultValue
In the next example we explicitly define the property value:
[source,java]
--------------------------------------------
pubic class ConfiguredItem{
@ConfiguredProperty(keys={"aValue", "a.b.value","a.b.deprecated.value"})
@DefaultValue("${env:java.version}")
private String aValue;
}
--------------------------------------------
==== Inject a DynamicValue Property
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{
@ConfiguredProperty(keys={"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> {
enum UpdatePolicy{
IMMEDIATE,
EXPLCIT,
NEVER,
LOG_AND_DISCARD
}
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);
T orElseGet(ConfiguredItemSupplier<? extends T> other);
<X extends Throwable> T orElseThrow(ConfiguredItemSupplier<? extends X> exceptionSupplier) throws X;
}
--------------------------------------------
Summarizing this class 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.
==== Ommitting Injection using @NoConfig
Adding the @NoConfig annotation prevents a field or method to be selected (mostly auto-selected) for
configuration. This is especially useful, if a type is annotated as @ConfiguredType with auto-confiuration
turned on as follows:
[source,java]
--------------------------------------------
@ConfiguredType(autoConfigure=true)
pubic class ConfiguredItem{
@NoConfig
private transient int sum;
private String a;
private String b;
Private String c;
}
--------------------------------------------
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{
@ConfiguredProperty
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, validation, decryption.
[source,java]
--------------------------------------------
pubic class ConfiguredItem{
@WithPropertyConverter(MyPropertyConverter.class)
@ConfiguredProperty
private String a;
}
--------------------------------------------
==== Defining the loading policy to be applied to configured values using @WithLoadPolicy
The @WithLoadPolicy annotation allows to define the loading behaviour to be applied. The +LoadPolicy+
enum hereby defines the various loading modes.
[source,java]
--------------------------------------------
@WithLoadPolicy(LoadPolicy.NEVER)
pubic class BootTimeStableConfig{
@WithPropertyConverter(MyPropertyConverter.class)
@ConfiguredProperty
private String a;
}
--------------------------------------------