blob: 6ccd2d0e40c2341871911285e1206cbd977ffcba [file] [log] [blame]
= DeltaSpike Configuration Mechanism
:Notice: 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.
== Introduction
The goal of the DeltaSpike configuration mechanism is to make it
obsolete to touch released binaries for changing the configuration of
your project. All values which are needed in your code (but should not
be hardcoded as static final constants) can be maintained via
DeltaSpike's own configuration mechanism in a very flexible and powerful
way.
=== Benefits for Production
Once a binary like a WAR file or an EAR got created and tested, it must
_not_ get changed anymore. The exact same binary which got created by
the release manager will get moved to the Test system, then further
propagated to the Staging environment and finally (if all people are
happy with it) will get moved to the Production system. And all this
without any changes on the binary itself!
The Apache DeltaSpike configuration system makes this possible by
providing a default configuration inside the binary and allowing to
amend this configuration (e.g. database credentials, some URLs from
remote REST or SOAP endpoints, etc) from outside like environment
settings, JNDI or the current <<projectstage.adoc#,ProjectStage>>.
=== Drop-In Configuration
This mechanism also allows for dynamic configuration in case of a JAR
drop-in. By adding some JAR to the classpath, all its contained
configuration will get picked up and considered in the property value
evaluation. You could also use this mechanism to switch implementations
of some SPI (Service Provider Interface) in your own code.
=== CDI-Extension Configuration
In some cases low-level configs are needed, for example during the bootstrapping
process of the CDI container.
The good news: our DeltaSpike configuration mechanism does not rely on
any other EE mechanism to be booted. Which means it can perfectly get
used to even configure those parts itself. Since the mechanism does not
rely on CDI it can for example be used to configure CDI-Extensions.
Currently this is, for example, used to configure the value of the current <<projectstage.adoc#,ProjectStage>>, configured values which can be
used in the expressions for `@Exclude`, 'Deactivatable', etc. DeltaSpike
needs such a low-level approach for several features internally, but
users can utilize it for their own needs as well. This is done by using
the `ConfigResolver` which resolves and caches ConfigSources per
application.
== Accessing configured values using ConfigResolver
The `ConfigResolver` is the central point to access configuration in DeltaSpike. There are several different APIs
and ways to access the individual configured values, each suitable for a different purpose:
* `ConfigResolver` methods for easy programmatic access to values
* `TypedResolver` API for typed configuration values and precise control over resolution
* `@ConfigProperty` for injection of configured values into beans
* interface based configuration
All four mechanisms are described in the following sections.
=== ConfigResolver methods
ConfigResolver offers several methods for easy access to String values of configured properties.
==== getPropertyValue()
The method `ConfigResolver#getPropertyValue(String key)` returns the value configured for a given key
as `String`, or `null` if no value has been found.
`ConfigResolver#getAllPropertyValues(String key)` has a similar contract
but it returns a list which might be empty if there are no configured
values for the given key.
This is a code excerpt about how to do a simple lookup in the deltaspike
configuration:
[source,java]
-------------------------------------------------------------------------------
String dbUserName = ConfigResolver.getPropertyValue("databaseconfig.username");
-------------------------------------------------------------------------------
==== getProjectStageAwarePropertyValue()
The method
`ConfigResolver#getProjectStageAwarePropertyValue(String key)` utilizes
the <<projectstage.adoc#,DeltaSpike ProjectStage>> mechanism to allow
configured values to depend on the current `ProjectStage` of the running system.
This is done by first looking up the ProjectStage (this internally
happens with the DeltaSpike ConfigResolver as well) and then go down the
following lookup chain until we found a configured value.
* key + '.' + projectStage , e.g. "databaseconfig.username.Production"
* key alone , e.g. "databaseconfig.username"
==== getPropertyAwarePropertyValue()
The method
`ConfigResolver#getPropertyAwarePropertyValue(String key, String property)`
first looks up the configured value of the given property and uses this
value to determine the final lookup path. All those lookups take the
<<projectstage.adoc#,DeltaSpike ProjectStage>> mechanism into account.
[source,java]
--------------------------------------------------------------------------------------------------------
String dbUserName = ConfigResolver.getPropertyAwarePropertyValue("databaseconfig.username", "dbvendor");
--------------------------------------------------------------------------------------------------------
===== Property value resolution sequence
The following lookup sequence will be performed until a value is found:
First, the value of the _parameter_ property is resolved:
* propertyValue = property + '.' + projectStage, e.g. "dbvendor.Production"
* if nothing found: propertyValue = property, e.g. "dbvendor"
Let's assume we found the value 'mysql' for our dbvendor. In this case
the following lookup chain is used until a value got found:
* key + '.' + propertyValue + '.' + projectstage, e.g. "databaseconfig.username.mysql.Production"
* key + '.' + propertyValue, e.g. "databaseconfig.username.mysql"
* key + '.' + projectStage, e.g. "databaseconfig.username.Production"
* key, e.g. "databaseconfig.username"
==== Handling of Default Values
There is a 2nd variant of all those methods where it is possible to
provide a default value which gets returned instead of `null` or if the
final result is an empty String.
.Performance Hint
TIP: The only `ConfigResolver` operation which is cached is the determination
of the `ConfigSources`. The various getPropertyValue operations are not
cached in the ConfigResolver but might be cached in the ConfigSources.
This makes the overall calculation a bit slower, but allows for values
to change dynamically if someone likes to for example implement a
`JmxConfigSource` (not yet part of DeltaSpike, but easily implementable).
You can also use the <<configuration.adoc#DynamicReloading,TypedResolver>> with the `cacheFor(TimeUnit, long)` setting to cache the resolved values on the caller side.
=== Variable Replacement in Configured Values
Since version 1.6.1, DeltaSpike also supports using 'variables' inside configured values.
You can e.g. define a single configuration key for your server and use it in other configuration values
-----------------------------------------------------------------
document.server.url=http://localhost:8081
myapp.document.lists=${document.server.url}/docapp/list
myapp.document.admin=${document.server.url}/docadmin/app
-----------------------------------------------------------------
A variable name starts with `${` and ends with `}`.
Variable support is enabled by default.
If you like to use the `ConfigResolver` without variable support you need to use the methods with the `evaluateVariables` parameter set to `false`.
=== TypedResolver API
Very often the configured values represent more than just strings -- number types and booleans are commonly used as
configuration types. ConfigResolver provides a builder-style API to access configuration values as specific types.
The API is accessed by a call to `ConfigResolver.resolve(propertyKey)`.
The simplest usage of the API is resolution of a String property, equivalent to a call to
`ConfigResolver.getPropertyValue(propertyKey)`.
.Simple example of TypedResolver
[source,java]
-----------------------------------------------------------------
String userName = ConfigResolver.resolve("user.name").getValue();
-----------------------------------------------------------------
The call to `ConfigResolver.resolve(..)` returns a builder which has methods to refine the resolution, including the
following:
* `as(Class<N> clazz)` -- defines the return type of the property
* `parameterizedBy(String propertyName)` -- sets a parameter for the resolution, similarly as in
<<_getpropertyawarepropertyvalue, ConfigResolver.getPropertyAwarePropertyValue>>
* `withCurrentProjectStage(boolean with)` -- indicates whether the current ProjectStage should be taken into account
for the resolution
* `strictly(boolean strictly)` -- indicates, whether the <<_property_value_resolution_sequence, property value
resolution sequence>> should be taken into account. When set to true, the sequence is not followed.
* `withDefault(T value)` -- sets the default value, used in case the resolution returns `null`
* `getValue()` -- terminates the builder and returns the resolved value with the appropriate type
.A more complete example of TypedResolver
[source,java]
-----------------------------------------------------------------
Integer dbPort = ConfigResolver
.resolve("db.port")
.as(Integer.class)
.withProjectStage(true)
.parameterizedBy("db.vendor")
.withDefault(3306)
.getValue();
-----------------------------------------------------------------
==== Supported types
The types supported out of the box include: String, Integer, Long, Float, Double, Boolean, Class.
Custom types can be supported by providing an implementation of the `ConfigResolver.Converter` interface.
[source,java]
---------------------------------------------------------------------------------------------------------
Date deadline = ConfigResolver.resolve("deadline").as(Date.class, new CustomDateConverter()).getValue());
---------------------------------------------------------------------------------------------------------
[source,java]
------------------------------------------------------------------------------------------
public class CustomDateConverter implements ConfigResolver.Converter<Date> {
@Override
public Date convert(String value)
{
String[] parts = value.split("-");
return new GregorianCalendar(Integer.valueOf(parts[0]), Integer.valueOf(parts[1]),
Integer.valueOf(parts[2])).getTime();
}
}
------------------------------------------------------------------------------------------
==== Dynamic Reloading
The TypedResolver can also be used to efficiently cache configured values.
That way you can pick up configuration which might get changed during runtime on the fly.
E.g. if you have a ConfigSource which picks up the values from a database table.
Instead of resolving the configured value at the beginning you simply invoke `.getValue()` on your TypedResolver each time you need the value.
.Working with dynamically changing values
[source,java]
-----------------------------------------------------------------
private ConfigResolver.TypedResolver<String> urlConfig
= ConfigResolver.resolve("myapp.some.remote.rest.url")
.logChanges(true)
.cacheFor(TimeUnit.MINUTES, 5);
...
connecTo( urlConfig.getValue() );
-----------------------------------------------------------------
The sample above will log any value changes in the configuration (`logChanges(true)`) and internally cache the configured value for 5 minutes (`cacheFor(TimeUnit.MINUTES, 5)`).
Only after that time the configured value will get evaluate again.
TIP: Note that the 'cache' is only held within the very TypedResolver instance.
If you use different `TypedResolver` instances (e.g. in different classes) then you might get different cache timeouts.
==== List Values handling
As of DeltaSpike-1.8.0, the `Typed Resolver` is also able to handle list values.
Resolve a list of e.g. retry interval values via:
[source,java]
----
List<Integer> retryIntervals
= ConfigResolver.resolve("myapp.retry.intervalls")
.as(Integer.class)
.asList()
.getValue();
----
The values get configured as comma (`','`) separated value String.
A Comma inside a value can be escaped with a backslash (`\,`), backslashes should be escaped with double-backslash (`\\`).
Trailing and leading whitespaces get trimmed for each value.
=== Injection of configured values into beans using @ConfigProperty
DeltaSpike provides a way to inject configured values into your code via the qualifier `@ConfigProperty`.
The supported types are the same as the <<_supported_types,supported types of the TypedResolver>>.
[source,java]
------------------------------------------------------
@ApplicationScoped
public class SomeRandomService
{
@Inject
@ConfigProperty(name = "endpoint.poll.interval")
private Integer pollInterval;
@Inject
@ConfigProperty(name = "endpoint.poll.servername")
private String pollUrl;
...
}
------------------------------------------------------
==== Custom ConfigProperty types
Custom types can be injected using `@ConfigProperty` by providing a custom producer.
DeltaSpike provides a base implementation for custom producers in the class `BaseConfigPropertyProducer`
which offers the following methods:
* `getStringPropertyValue` -- looks for the property name in `@ConfigProperty` annotation on the injection point.
If not found, it looks for it in other annotations on the injection point.
* `getPropertyValue` -- a shortcut to <<_configresolver, ConfigResolver#getProjectStageAwarePropertyValue>>
* `getAnnotation` -- extracts any annotation type from the injection point, useful when a custom annotation
is used instead of `@ConfigProperty`
The following example uses `getStringPropertyValue` and a custom `@Location` annotation annotated `@ConfigProperty`.
In such case, the `@Location` annotation is bound to a single fixed property name and acts as a type-safe replacement
for `@ConfigProperty(name = "locationId")`.
[source,java]
--------------------------------------------------------------------
@ApplicationScoped
public class CustomConfigPropertyProducer extends BaseConfigPropertyProducer {
@Produces
@Dependent
@Location
public LocationId produceLocationId(InjectionPoint injectionPoint)
{
String configuredValue = getStringPropertyValue(injectionPoint);
return LocationId.valueOf(configuredValue.trim().toUpperCase());
}
}
--------------------------------------------------------------------
[source,java]
-----------------------------------------------------------------
@Target({ FIELD, METHOD })
@Retention(RUNTIME)
@ConfigProperty(name = "locationId", defaultValue = "LOCATION_X")
@Qualifier
public @interface Location {}
-----------------------------------------------------------------
The `@ConfigProperty` annotation doesn't need to be used at all. Instead, a custom annotation can be provided and
obtained in the producer using `getAnnotation` and `getPropertyValue`:
[source,java]
------------------------------------------------------------------------------------------------------
@ApplicationScoped
public class NumberConfigPropertyProducer extends BaseConfigPropertyProducer
{
@Produces
@Dependent
@NumberConfig(name = "unused")
public Float produceNumberProperty(InjectionPoint injectionPoint) throws ParseException
{
// resolve the annotation
NumberConfig metaData = getAnnotation(injectionPoint, NumberConfig.class);
// get the configured value from the underlying configuration system
String configuredValue = getPropertyValue(metaData.name(), metaData.defaultValue());
// format according to the given pattern
DecimalFormat df = new DecimalFormat(metaData.pattern(), new DecimalFormatSymbols(Locale.US));
return df.parse(configuredValue).floatValue();
}
}
------------------------------------------------------------------------------------------------------
[source,java]
-------------------------------------------------------------------
@Qualifier
public @interface NumberConfig
{
@Nonbinding
String name();
@Nonbinding
String defaultValue() default ConfigProperty.NULL;
@Nonbinding
String pattern() default "#0.00";
}
-------------------------------------------------------------------
== Interface based configuration
The interfaces decorated with `@Configuration` are converted during CDI startup
to Beans matching the interface type. Concretely this interface:
[source]
----
@Configuration
public interface MyConfig {
}
----
Will use accessible using:
[source]
----
@Inject
private MyConfig config;
----
To define a configuration entry in this mode you define an interface method
and decorate it with `@ConfigProperty` exactly as a normal injection:
[source]
----
@Configuration
public interface MyConfig {
@ConfigProperty(name = "my.config")
String url();
}
----
TIP: this mode also supports primitives like `int`, `boolean`, ... as returned types.
The methods are no parameter and not returning void methods.
If all your keys use the same prefix you can configure it on `@Configuration`:
[source]
----
@Configuration(prefix = "client.")
public interface MyConfig {
@ConfigProperty(name = "url")
String url();
@ConfigProperty(name = "timeout", defaultValue = "30000")
long timeout();
}
----
Finally, you can also access the caching feature of the `TypedResolver` through `@Configuration`:
[source]
----
@Configuration(cacheFor = 30, cacheUnit = TimeUnit.SECONDS)
public interface MyConfig {
@ConfigProperty(name = "url")
String url();
@ConfigProperty(name = "timeout", defaultValue = "30000")
long timeout();
}
----
== Providing configuration using ConfigSources
A `ConfigSource` is exactly what its name says: a source for configured
values. The `ConfigResolver` uses all configured implementations of
`ConfigSource` to lookup the property in question.
Each 'ConfigSource' has a specified 'ordinal' which can be configured
using the key `deltaspike_ordinal`. This ordinal get's used to determine
the importance of the values taken from the very ConfigSource. A higher
ordinal means that the values taken from this ConfigSource will override
values from less important ConfigSources. This is the trick which allows
to amend configuration from outside a binary - given those outside
ConfigSources have a higher `deltaspike_ordinal` than the ones who
pickup the values from within the release binaries.
=== ConfigSources Provided by Default
By default there are implementations for the following configuration sources
(listed in the lookup order):
* System properties (deltaspike_ordinal = 400)
* Environment properties (deltaspike_ordinal = 300)
* JNDI values (deltaspike_ordinal = 200, the base name is "java:comp/env/deltaspike/")
* All apache-deltaspike.properties files on the classpath (deltaspike_ordinal = 100, default filename is "META-INF/apache-deltaspike.properties")
* A property file in user.home `~/.deltaspike/apache-deltaspike.properties` (if exists, deltaspike_ordinal = 100)
*It is possible to change this order and to add custom configuration sources.*
.Important Tips Especially for Custom Implementations
TIP: - The config-source with the highest ordinal gets used first. - If a custom
implementation should be invoked _before_ the default implementations,
use an ordinal-value > 400. - If a custom implementation should be
invoked _after_ the default implementations, use an ordinal-value < 100.
- The `ConfigResolver` performs no caching. If your custom ConfigSource
operation is expensive, then you might think about introducing some
caching.
=== Reordering of the Default Order of ConfigSources
To change the lookup order, you have to configure the ordinal in the
corresponding configuration source (e.g. to change the configuration ordinal of the
configuration source for system properties, you have to set the system property
with the ordinal key 'deltaspike_ordinal' and the new value).
Example with `/META-INF/apache-deltaspike.properties`: If the properties
file/s should be used *before* the other implementations, you have to
configure an ordinal > 400. That means, you have to add for example
`deltaspike_ordinal=401`.
Each single property file is treated as own `ConfigSource` and thus can
have different `deltaspike_ordinal` values!
NOTE: In case of *property files* which are supported by default
(`/META-INF/apache-deltaspike.properties`) every file is handled as
independent config-source, but all of them have ordinal 400 by default
(and can be reordered in a fine-grained manner).
=== Custom ConfigSources
ConfigSources are picked up using the `java.util.ServiceLoader'
mechanism.
To add a custom config-source, you have to implement the interface
`ConfigSource` and register your implementation in a file
`/META-INF/services/org.apache.deltaspike.core.spi.config.ConfigSource`
by writing the fully qualified class name of the custom implementation/s
into it.
If you need dynamic ConfigSources you can also register a
`ConfigSourceProvider` in a similar way. This is useful if you like to
dynamically pick up multiple ConfigSources of the same kind. For example, if you
like to pick up all `myproject.properties` files from all the JARs in
your classpath.
Please note that a single `ConfigSource` should be either registered
directly or via a `ConfigSourceProvider`, but never both ways.
TIP: Have a look at the abstract base-implementation of `ConfigSource`
DeltaSpike is using internally, if a custom implementation should load
the ordinal value from the config-source like the default
implementations provided by DeltaSpike do.
Since 1.8.0 you can also decorate a CDI `ConfigSource` with `@Source` and it will
be added to DeltaSpike configuration *once the CDI container is started* (it means
you can't use this source in an `Extension`).
==== PropertyFileConfig
For registering all your own property files of a certain name in your
classpath to get picked up as ConfigSources you can also provide a
class which implements the `PropertyFileConfig` interface.
If `getPropertyFileName()` returns an URL, e.g. `file:///var/opt/myapp/my.properties"` then this will be used to pick up the configured values.
In other words: with using `file://` you can specify a file on the file system.
The method `isOptional` indicates whether your custom property file is mandatory.
If a mandatory property file is not found during deployment, DeltaSpike throws
an `IllegalStateException` and stops the deployment.
[source,java]
---------------------------------------------------------------------
public class MyCustomPropertyFileConfig implements PropertyFileConfig
{
@Override
public String getPropertyFileName()
{
return "myconfig.properties";
}
@Override
public boolean isOptional()
{
return false;
}
}
---------------------------------------------------------------------
_Note: If you are using WildFly with EAR packaging and with
ear-subdeployments-isolated=true, then your EAR should have a deployment
dependency to the module that contains the property file._
[source,xml]
---------------------------------------------------------------------------------------------------
<jboss-deployment-structure>
<ear-subdeployments-isolated>true</ear-subdeployments-isolated>
<deployment>
<dependencies>
<!-- This module contains the custom PropertyFileConfig and the property file -->
<module name="deployment.yourproject.ear.yoursubmodule.jar" meta-inf="import" />
</dependencies>
</deployment>
</jboss-deployment-structure>
---------------------------------------------------------------------------------------------------
== Filtering configured values
It is possible to perform filtering on all configured values on their way between the ConfigSource and user code.
This might be useful for example for decryption of values from an encrypted ConfigSource or to hide passwords from a
log.
DeltaSpike doesn't provide any filters by default but custom filters can be provided by implementing the
`ConfigFilter` interface. This is then enabled either using the ServiceLoader mechanism or by calling
`ConfigResolver.addConfigFilter(ConfigFilter)`. Provided ConfigFilters are then enabled for the whole application.
Once some filters are provided, all operations of ConfigResolver return filtered values.
.A custom ConfigFilter
[source,java]
-------------------------------------------------------------
public class DecryptingConfigFilter implements ConfigFilter
{
private DefaultCipherService ciperSvc = new DefaultCipherService();
private String masterSalt = "someApplicationMasterSalt";
@Override
public String filterValue(String key, String value)
{
if (key.contains("password"))
{
try
{
return cipherSvc.decrypt(value, masterSalt);
}
catch (Exception e)
{
return value;
}
}
return value;
}
@Override
public String filterValueForLog(String key, String value)
{
if (key.contains("password"))
{
return "***************";
}
return value;
}
}
-------------------------------------------------------------
For more information about the `CipherService` please see the <<encryption#, Encryption>> section.
Since 1.8.0 you can also decorate a CDI `ConfigFilter` with `@Filter` and it will
be added to DeltaSpike configuration *once the CDI container is started* (Note that this means
you can't use this ConfigFilter in a CDI `Extension`).