blob: 3cec143fe0d9a81fabd0cf093b6e36ffac50472e [file] [log] [blame]
:jbake-type: page
:jbake-status: published
= Apache Tamaya - Extension: Metamodel (Configuration of Tamaya)
toc::[]
[[Model]]
== Tamaya Metamodel (Configuration of Tamaya) (Extension Module)
Tamaya _metamodel_ is an extension module. Refer to the link:../extensions.html[extensions documentation] for further details.
=== What functionality this module provides ?
The Tamaya _metamodel_ module provides support for configuring the Tamaya system itself. It
allows, like a logging configuration, to configure how your configuration framework should
work, where to find configuration and how it is combined using overrides, filters etc.
By default it uses an XML based configuration format as illustrated below:
[source, xml]
.Extract from `tamaya-config.xml`
-----------------------------------------------
<configuration>
<!-- Context is evaluated first. -->
<context>
<context-entry name="stage">${properties:system:STAGE?default=DEV}</context-entry>
<context-entry name="configdir">${properties:system:configdir?default=.}</context-entry>
<context-entry name="app">${properties:system.APP?default=NONE}</context-entry>
<context-entry name="context">${java:org.apache.tamaya.context.Context#id()}</context-entry>
<context-entry name="company">Trivadis</context-entry>
<context-entry name="default-formats">yaml,json</context-entry>
<context-entry name="default-refresh-period">5 SECOND</context-entry>
</context>
<!-- combinationPolicy type="" / -->
<!-- Configuration definition. -->
<property-sources>
<source enabled="${stage=TEST || stage=PTA || stage=PROD}"
type="env-properties">
<filter type="PropertyMapping">
<param name="mapTarget">ENV.</param>
</filter>
<filter type="AccessMask">
<param name="roles">admin,power-user</param>
<param name="policy">mask</param>
<param name="mask">*****</param>
<param name="matchExpression">SEC_</param>
</filter>
</source>
<source type="sys-properties" >
<filter type="ImmutablePropertySource" />
</source>
<source type="file" refreshable="true">
<name>config.json</name>
<param name="location">config.json</param>
</source>
...
</property-sources>
</configuration>
-----------------------------------------------
The module basically provides an XML representation to the +ConfigurationContextBuilder+ API.
It creates a corresponding +ConfigurationContext+ and registers the corresponding +Configuration+
as the system's _default_ configuration (accessible from `ConfigurationProvider.getConfiguration()`.
=== Compatibility
The module is based on Java 8.
=== Installation
To use _metamodel_ features you only must add the corresponding dependency to your module:
[source, xml]
-----------------------------------------------
<dependency>
<groupId>org.apache.tamaya.ext</groupId>
<artifactId>tamaya-model</artifactId>
<version>{tamaya_version}</version>
</dependency>
-----------------------------------------------
=== Creating a Configuration using Meta-Configuration
The basic feature of this module is the capability of creating a +Configuration+ completely
based on a meta-configuration file. For this the +MetaConfiguration+ main singleton
provides different methods:
[source, java)
-----------------------------------------------
public final class MetaConfiguration {
public static void configure();
public static void configure(URL metaConfig);
public static ConfigurationContextBuilder createContextBuilder(URL metaConfig);
public static Configuration createConfiguration(URL metaConfig);
-----------------------------------------------
* If you have supplied your meta-configuration at `META-INF/tamaya-config.xml` you simply
call +MetaConfiguration.configure();+. This will read the meta-configuration and
configure Tamaya's _default_ configuration. Alternatively you can choose your own
metaconfiguration location by passing an alternate `URL` ro read from.
* With +MetaConfiguration.createContextBuilder()+ you can stop a step earlier: a new
instance of +ConfigurationContextBuilder+ is created and configured with all the
entries found in your meta-configuration. Also here you can optionally pass your
custom location for the meta-configuration resouce.
* Finally +MetaConfiguration.createConfiguration(URL)+ allows you to create an
arbitrary +Configuration+ instance using a meta-configuration file. The `Configuration`
instance is completely independent and not registered as _default_ configuration, so
it's lifecycle and usage is completely under your control.
=== MetaContext
When thinking what are the various input parameters for determining a correct configuration, there
might be different things relevant in different scenarios, especially for developers in different
companies. A good example of such an input parameter is the current `STAGE`. All these kinf od inputs
can be summarized in some sort of meta-configuration, commonly known as a _context_. So
the metamodel extension ships with a +MetaContext+ class that allows to define a common meta-context,
that can be accessed by components as needed to determine the correct settings to be applied:
[source, java)
-----------------------------------------------
public final class MetaContext {
...
public static MetaContext getInstance(String contextName);
/**
* Access the default context. Contexts are managed as weak references in this class. If no
* such context exists, a new instance is created.
* @return the context instance, never null.
*/
public static MetaContext getDefaultInstance();
/**
* Access a context by name. Contexts are managed as weak references in this class. If no
* such valid context exists, a new instance is created, using the given {@code validSupplier}.
* @param contextName the context name, not null.
* @return the context instance, never null.
*/
public static MetaContext getInstance(String contextName, Supplier<Boolean> validSupplier);
/**
* Access the thread-based context. If no such context
* exists a new one will be created.
* @param reinit if true, clear's the thread's context.
* @return the corresponding context, never null.
*/
public static MetaContext getThreadInstance(boolean reinit);
/**
* Access the current context, which actually is the current context, combined with the thread based
* context (overriding).
* @return the corresponding context, never null.
*/
public MetaContext getCurrentInstance();
/**
* Access the current context, which actually is the current context, combined with the thread based
* context (overriding).
* @param reinit if true, clear's the thread's context.
* @return the corresponding context, never null.
*/
public MetaContext getCurrentInstance(boolean reinit);
/**
* Method to evaluate if a context is valid. This basically depends on the
* {@code validSupplier}, if any is set. If no supplier is present the context is valid.
*
* @return true, if this context is valid.
*/
public boolean isValid();
/**
* Combine this context with the other contexts given, hereby only contexts are included
* which are {@code valid}, see {@link #isValid()}.
* @param contexts the context to merge with this context.
* @return the newly created Context.
*/
public MetaContext combineWith(MetaContext... contexts);
/**
* Access the given context property.
* @param key the key, not null
* @return the value, or null.
*/
public String getProperty(String key);
/**
* Access the given context property.
* @param key the key, not the default value.
* @param defaultValue the default value to be returned, if no value is defined, or the
* stored value's TTL has been reached.
* @return the value, default value or null.
*/
public String getProperty(String key, String defaultValue);
/**
* Sets the given context property.
* @param key the key, not null.
* @param value the value, not null.
* @return the porevious value, or null.
*/
public String setProperty(String key, String value);
/**
* Sets the given context property.
* @param key the key, not null.
* @param value the value, not null.
* @param ttl the time to live. Zero or less than zero means, no timeout.
* @param unit the target time unit.
* @return the porevious value, or null.
*/
public String setProperty(String key, String value, int ttl, TimeUnit unit);
/**
* Sets the given property unless there is already a value defined.
* @param key the key, not null.
* @param value the value, not null.
*/
public void setPropertyIfAbsent(String key, String value);
/**
* Sets the given property unless there is already a value defined.
* @param key the key, not null.
* @param value the value, not null.
* @param ttl the time to live. Zero or less than zero means, no timeout.
* @param unit the target time unit.
*/
public void setPropertyIfAbsent(String key, String value, long ttl, TimeUnit unit);
/**
* Adds all properties given, overriding any existing properties.
* @param properties the properties, not null.
*/
public void setProperties(Map<String,String> properties);
/**
* Adds all properties given, overriding any existing properties.
* @param properties the properties, not null.
* @param ttl the time to live. Zero or less than zero means, no timeout.
* @param unit the target time unit.
*/
public void setProperties(Map<String,String> properties, long ttl, TimeUnit unit);
/**
* Checks if all the given properties are present.
* @param keys the keys to check, not null.
* @return true, if all the given keys are existing.
*/
public boolean checkProperties(String... keys);
/**
* Access all the current context properties.
* @return the properties, never null.
*/
public Map<String,String> getProperties();
}
-----------------------------------------------
As you see, a +MetaContext+ has the following aspects:
* there are multiple context's possible, identified by their name.
* Accessing an instance that does not yet exist, will create a new one.
* there is one shared _default_ instance.
* they store ordinary `String,String` key, value pairs.
* they can be _combined_ into a overriging hierarchy
* accessing the _default_ MetaContext returns the global instance combined with
a threaded override instance. Passing `reinit` will clear the thread instance's
data.
==== Configuring MetaContexts
`MetaContext` instances can be configured in the _meta-configuration_ in the first
`meta-context` section as illustrated below:
[source, xml]
-----------------------------------------------
<!-- Configuring the default context -->
<context>
<context-entry name="stage">${properties:system:STAGE?default=DEV}</context-entry>
<context-entry name="configdir">${properties:system:configdir?default=.}</context-entry>
<context-entry name="app">${properties:system.APP?default=NONE}</context-entry>
<context-entry name="context">${java:org.apache.tamaya.context.Context#id()}</context-entry>
<context-entry name="company">Trivadis</context-entry>
<context-entry name="default-formats">yaml,json</context-entry>
<context-entry name="default-refresh-period">5 SECOND</context-entry>
</context>
<!-- Configuring a context named 'APP' -->
<context name="APP">
<context-entry name="application">someAppName</context-entry>
</context>
-----------------------------------------------
As shown above multiple contexts can be configured. Keys and values are of type `String`.
===== Using Expressions
As shown before, it is possible to add simple expressions, enclosed in `${}`. Hereby the
contents must be formatted as `evaluator:expression`, which then internally must be interpreted by
the +org.apache.tamaya.metamodel.internal.SimpleResolver+, which effectively reads and
applied context entries.
Currently the following placeholders for context entries are provided:
* properties - mapping to system properties (`properties:sys:KEY`) or
environment properties (`properties:env:KEY`) or other MetaContext
entries initialized already (`properties:ctx[:CTXNAME]:KEY`)
* java - mapping to a static method or field, returning a `String` value.
=== General Extensions
Working with meta-models requires additional aspects to be generalized to separate
concerns and reuse some of the common functionality. These concepts are shown in the following
subsections.
=== Enabled
Things can be dynamically enabled or disabled, e.g. based on context. This can be
modelled by the +Enabled+ interface:
[source, java]
-----------------------------------------------
public interface Enabled {
/**
* Returns the enabled property.
* @return the enabled value.
*/
boolean isEnabled();
/**
* Enables/disables this property source.
* @param enabled the enabled value.
*/
void setEnabled(boolean enabled);
}
-----------------------------------------------
+Enabled+ can be used as a mixin-logic, e.g. for decorating property sources,
property source providers, filters and converters. The decorator can also, if not
set explicitly, evaluate the _enabled_ property based on the current runtime
context.
=== Refreshable
Similar to _Enabled_ things can also be refreshable.
[source, java]
-----------------------------------------------
public interface Refreshable {
/**
* Refreshes the given instance.
*/
void refresh();
}
-----------------------------------------------
This can be used to define a common API for refreshing artifctas. Similar to
_Enabled_ this can be applied as a decorator/mix-in interface to property
sources and property source providers. This property also is supported in the
XML metaconfiguration, e.g.
[source, xml]
-----------------------------------------------
<property-sources>
<source type="file" refreshable="true">
<name>config.json</name>
<param name="location">config.json</param>
</source>
</property-sources>
-----------------------------------------------
=== The MetaConfiguration XML Structure
In general the `tamaya-config.xml` file does never apply an XML schema or
similar. Nevertheless there is a common DSL structure, which can be extended
as well (see next chapter).
[source, xml]
-----------------------------------------------
<configuration>
<!-- PART ONE: Contexts initialization. -->
<context>
<context-entry name="stage">${properties:system:STAGE?default=DEV}</context-entry>
<context-entry name="configdir">${properties:system:configdir?default=.}</context-entry>
...
</context>
<context name="APP">
<context-entry name="application">someAppName</context-entry>
</context>
<!-- PART TWO: Global settings of ConfigurationContext. -->
<!-- combinationPolicy type="" / -->
<!-- PART THREE: Configuration definition. -->
<property-sources>
<source enabled="${stage=TEST || stage=PTA || stage=PROD}"
type="env-properties">
<filter type="PropertyMapping">
<param name="mapTarget">ENV.</param>
</filter>
<filter type="AccessMask">
<param name="roles">admin,power-user</param>
<param name="policy">mask</param>
<param name="mask">*****</param>
<param name="matchExpression">SEC_</param>
</filter>
</source>
<source type="sys-properties" >
<filter type="ImmutablePropertySource" />
</source>
<source type="file" refreshable="true">
<name>config.json</name>
<param name="location">config.json</param>
</source>
<source type="file" refreshable="true">
<name>config.xml</name>
<param name="location">config.xml</param>
<param name="formats">xml-properties</param>
</source>
<source-provider type="resource">
<name>classpath:application-config.yml</name>
<param name="location">/META-INF/application-config.yml</param>
</source-provider>
<source type="ch.mypack.MyClassSource" />
<!--<include enabled="${stage==TEST}">TEST-config.xml</include>-->
<source-provider type="resource" enabled="${configdir != null}">
<name>config-dir</name>
<param name="location">/${configdir}/**/*.json</param>
</source-provider>
<source type="url" refreshable="true">
<name>remote</name>
<param name="location">https://www.confdrive.com/cfg/customerId=1234</param>
<param name="formats">json</param>
<filter type="CachedPropertySource">
<param name="ttl">30 SECOND</param>
</filter>
</source>
</property-sources>
<property-filters>
<filter type="UsageTrackerFilter"/>
<filter type="AccessControl">
<param name="roles">admin,power-user</param>
<param name="policy">hide</param>
<param name="expression">*.secret</param>
</filter>
<filter type="Cache">
<param name="ttl">30000</param>
<param name="expression">cached.*</param>
</filter>
</property-filters>
<property-converters>
<!--<converter type="AllInOneConverter"/>-->
<default-converters/>
</property-converters>
</configuration>
-----------------------------------------------
The different parts in fact are not hardcoded, but implemented
as independent components, where each of them gets access to the
XML DOM tree to read the configuration aspects of interest.
Instances related must implement the ++ interface and register it to
the `ServiceContext`. Reading order is mapped using `@Priority`
annotations.
For further details refer to the SPI section in this document.
== Model SPI
=== Extending the XML DSL
The XML DSL can be extended in various ways:
* Basically adding a new feature maps to adding a new section to the
meta-config XML. This can be easily done, by implementing +MetaConfigurationReader+
and do whatever is appropriate for your use case.
* For adding new expression capabilities for `MetaContext`entries +SimpleResolver+ must
be implemented.
* For allowing customized parameterization of artifacts, e.g. property sources,
property source providers, converters and filters etc. you may implement +ItemFactory+
instances.
=== MetaConfigurationReader
XML metaconfiguration is effectively processed by instances of
type +org.apache.tamaya.metamodel.spi.MetaConfigurationReader+:
[source,java]
-----------------------------------------------------------
public interface MetaConfigurationReader {
/**
* Reads meta-configuration from the given document and configures the current
* context builder. The priority of readers is determined by the priorization policy
* implemented by the {@link org.apache.tamaya.spi.ServiceContext},
* @param document the meta-configuration document
* @param contextBuilder the context builder to use.
*/
void read(Document document, ConfigurationContextBuilder contextBuilder);
}
-----------------------------------------------------------
Hereby we also see that an instance of `ConfigurationContextBuilder` is passed.
Remember, we mentioned earlier that meta-configuration basically is a XML
API to the building a configuration using a +ConfigurationContextBuilder+. So
all you can do with the meta-config XML can also be done programmatically using
the Java API.
This module provides instances of this class for reading of meta-context,
property-sources, property source providers, converters, filters and more.
Look into the +org.apache.tamaya.metamodel.internal+ package for further details.
New instances implementing this interface must be registered into the current
+ServiceContext+, by default the +ServiceLoader+ is used.
=== ItemFactory
Instances of +ItemFactory+ allow to configure artifacts using XML data:
[source, java]
-----------------------------------------------------------
public interface ItemFactory<T> {
/**
* Get the factory name.
* @return the factory name, not null.
*/
String getName();
/**
* Create a new instance.
* @param parameters the parameters for configuring the instance.
* @return the new instance, not null.
*/
T create(Map<String,String> parameters);
/**
* Get the target type created by this factory. This can be used to
* assign the factory to an acording item base type, e.g. a PropertySource,
* PropertySourceProvider, PropertyFilter etc.
* @return the target type, not null.
*/
Class<? extends T> getType();
}
-----------------------------------------------------------
The factory's name hereby is used as a short cut, e.g. have a look at the following
XML snippet defining a `PropertySource` to be added:
[source, xml]
-----------------------------------------------------------
<source type="file" refreshable="true">
<name>config.json</name>
<param name="location">config.json</param>
</source>
-----------------------------------------------------------
In the above snippet _file_ equals to the factory name, which provides the user
a simple to use short name, instead of adding the fully qualified classname
(which is always possible).
The _location_ paramter with its value is passed as `Map` to the `create` method.
=== ItemFactoryManager
This singleton class manages the _ItemFactory_ instances found, hereby allowing
accessing and registering instances. This singleton is actually used by the
component parsers (type `MetaConfigurationReader`).
[source, java]
-----------------------------------------------------------
public final class ItemFactoryManager {
...
public static ItemFactoryManager getInstance();
public <T> List<ItemFactory<T>> getFactories(Class<T> type);
public <T> ItemFactory<T> getFactory(Class<T> type, String id);
public <T> void registerItemFactory(ItemFactory<T> factory);
}
-----------------------------------------------------------
=== Extended Implementations
The package +org.apache.tamaya.metamodel.ext+ contains a few useful
implementations that also can be used in your meta-configuration and
show how mixin-functionality can be added without touching property source
implementations.
As of now the package contains
* +EnabledPropertySource+: a decorator for a `PropertySource`
adding the capability to _enable/disable_ the property source.
* +EnabledPropertySourceProvider+ a decorator for a `PropertySourceProvider`
adding the capability to _enable/disable_ the property source provider.
* +RefreshablePropertySource+: a decorator for a `PropertySource`
adding the capability to _refresh_ the property source.
* +EnabledPropertySourceProvider+ a decorator for a `PropertySourceProvider`
adding the capability to _refresh_ the property source provider.
Not yet implemented but planned are implementations to add the following
functionality:
* _caching_ of entries for a given time.
* _immutability_ of entries, so a configuration data (or parts of it) will
never change later.