String dbUserName = ConfigResolver.getPropertyValue("databaseconfig.username");
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.
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.
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.
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, 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.
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 offers several methods for easy access to String values of configured properties.
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:
String dbUserName = ConfigResolver.getPropertyValue("databaseconfig.username");
The method
ConfigResolver#getProjectStageAwarePropertyValue(String key)
utilizes
the 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"
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
DeltaSpike ProjectStage mechanism into account.
String dbUserName = ConfigResolver.getPropertyAwarePropertyValue("databaseconfig.username", "dbvendor");
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"
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
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 TypedResolver with the cacheFor(TimeUnit, long) setting to cache the resolved values on the caller side.
|
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
.
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)
.
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
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 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
Integer dbPort = ConfigResolver
.resolve("db.port")
.as(Integer.class)
.withProjectStage(true)
.parameterizedBy("db.vendor")
.withDefault(3306)
.getValue();
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.
Date deadline = ConfigResolver.resolve("deadline").as(Date.class, new CustomDateConverter()).getValue());
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();
}
}
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.
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.
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.
|
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:
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.
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 of the TypedResolver.
@ApplicationScoped
public class SomeRandomService
{
@Inject
@ConfigProperty(name = "endpoint.poll.interval")
private Integer pollInterval;
@Inject
@ConfigProperty(name = "endpoint.poll.servername")
private String pollUrl;
...
}
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#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")
.
@ApplicationScoped
public class CustomConfigPropertyProducer extends BaseConfigPropertyProducer {
@Produces
@Dependent
@Location
public LocationId produceLocationId(InjectionPoint injectionPoint)
{
String configuredValue = getStringPropertyValue(injectionPoint);
return LocationId.valueOf(configuredValue.trim().toUpperCase());
}
}
@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
:
@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();
}
}
@Qualifier
public @interface NumberConfig
{
@Nonbinding
String name();
@Nonbinding
String defaultValue() default ConfigProperty.NULL;
@Nonbinding
String pattern() default "#0.00";
}
The interfaces decorated with @Configuration
are converted during CDI startup
to Beans matching the interface type. Concretely this interface:
@Configuration
public interface MyConfig {
}
Will use accessible using:
@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:
@Configuration
public interface MyConfig {
@ConfigProperty(name = "my.config")
String url();
}
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
:
@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
:
@Configuration(cacheFor = 30, cacheUnit = TimeUnit.SECONDS)
public interface MyConfig {
@ConfigProperty(name = "url")
String url();
@ConfigProperty(name = "timeout", defaultValue = "30000")
long timeout();
}
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.
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
- 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.
|
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!
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).
|
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.
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
).
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.
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.
<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>
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.
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 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
).