| = 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 `ConfigSource`s 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/") |
| * Properties file values (apache-deltaspike.properties) (deltaspike_ordinal = 100, default filename is "META-INF/apache-deltaspike.properties") |
| |
| *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`). |