-
Notifications
You must be signed in to change notification settings - Fork 2
Property Resolution
Property resolution is the process by which property names are resolved into the corresponding (string) property value. The extension includes built-in support for resolving property names using system properties, beans.properties
resources on the classpath, and (if running in a Java EE or Servlet container) beans.properties
resources in locations specified via
a JNDI environment variable. Each beans.properties
resource is a file in the format produced by java.util.Properties
.
- Placing
beans.properties
Resources on the Classpath - Overriding Properties During Test Execution
- Overriding Properties in Java EE and Web Applications
- Resolution Order
- Using Unified EL Expressions
The built-in property resolution takes advantage of the fully-qualified injection point names that are used as the default property names wherever @Property
is applied, allowing properties to be assembled in beans.properties
files at any level of the package hierarchy that makes sense for your needs. The following example illustrates the concept.
Suppose our application has a couple of beans defined as follows:
package org.example.illustrator;
import javax.inject.Inject
import javax.enterprise.context.ApplicationScoped
import org.soulwing.cdi.properties.Property
@ApplicationScoped
public class ApplicationConfig {
@Inject @Property private String emailAddress;
@Inject @Property private int maxConcurrentUsers;
// ...
}
package org.example.illustrator.http;
import java.net.URL;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import org.soulwing.cdi.properties.Property;
@SessionScoped
public class RestClientBean {
@Inject @Property private URL location;
@Inject @Property private String username;
@Inject @Property private String password;
// ...
}
We could simply create a beans.properties
file at the root of the classpath (or as META-INF/beans.properties
) and define our properties with the fully qualified names of our property injection points, like this:
org.example.illustrator.ApplicationConfig.emailAddress=help@org.example
org.example.illustrator.ApplicationConfig.maxConcurrentUsers=100
org.example.illustrator.http.RestClientBean.location=http://internal.example.org/appws
org.example.illustrator.http.RestClientBean.username=illustrator
org.example.illustrator.http.RestClientBean.password=s3kr3t
While this approach is probably fine for an application with a small number of configuration properties, using a flat file and fully qualified names is a bit too unwieldy and repetitive when defining and organizing a large number of configuration properties.
One immediate improvement we could make would be to put beans.properties
files in the corresponding packages on the classpath. If we did this, we would have two files -- one located at org/example/illustrator/beans.properties
containing the properties for the ApplicationConfig
bean:
ApplicationConfig.emailAddress=help@org.example
ApplicationConfig.maxConcurrentUsers=100
The other file would be located at org/example/illustrator/http/beans.properties
and would contain the
properties for the RestClientBean
:
RestClientBean.location=http://internal.example.org/appws
RestClientBean.username=illustrator
RestClientBean.password=s3kr3t
For an application this small, having the configuration files in two separate files might be a bit too sophisticated. But we'd still like to avoid the long property names we saw in the first approach. We could instead put one file at org/example/illustrator/beans.properties
and put all of the properties in it, like this:
ApplicationConfig.emailAddress=help@org.example
ApplicationConfig.maxConcurrentUsers=100
http.RestClientBean.location=http://internal.example.org/appws
http.RestClientBean.username=illustrator
http.RestClientBean.password=s3kr3t
Note how we can just prepend the subpackage name to the property names for the RestClientBean
object.
Properties placed on the classpath in META-INF/beans.properties
override those
specified elsewhere on the classpath. This allows you to easily target
properties whose values you want to override during tests by putting their
fully-qualified names in a META-INF/beans.properties
file with your test
resources. For example, in a Maven project you could put your overrides in
src/test/resources/META-INF/beans.properties
.
For applications deployed in a Java EE or Servlet container, it is often desirable to override property values specified in the deployment artifact (e.g. properties specified in files bundled in the Web Archive (WAR) file) with deployment-environment-specific values. For example, we might use different properties for a production server than we would for a development or pre-production server.
If the JNDI environment variable java:comp/env/beans.properties.location
exists and is of string type, the extension will treat it as a space- and/or comma-delimited list of URLs to properties files that will be used, in the order specified, to resolve property values.
If the JNDI environment variable java:comp/env/beans.properties.root
exists and is of string type, the extension will treat it as a base URL for resolving properties from beans.properties
files placed in a folder structure that corresponds to package names. This mechanism allows you to provide a hierarchy of beans.properties
files based on package names, outside of the runtime classpath.
While any URL scheme supported by the java.net.URL
type can be used to specify the location of properties resources, great care should be taken when using a URL with a scheme other than file:
. Loading configuration properties from a potentially untrusted source may introduce significant security risks.
Property values are resolved by the built-in resolvers in the following order.
- System properties set using
java.lang.System.setProperty
or by using-Dname=value
arguments when starting the JRE. - If the System property
pinject.properties
is set, the referenced properties files are located using the URL(s) specified. - If running in a Java EE or Servlet container, properties defined in properties files located using the URL(s) specified by the
java:comp/env/beans.properties.location
JNDI environment setting. - If running in a Java EE or Servlet container, all
beans.properties
files located by searching the namespace rooted by the URL specified by thejava:comp/env/beans.properties.root
JNDI environment setting. The search is conducted in the same manner as when searching the classpath. - All properties files at the root of the classpath with the name
META-INF/beans.properties
. The order in which these properties files are consulted is arbitrary (due to inherent limitations of the classloader mechanism) so you should not rely on the order in which these files are evaluated when using this mechanism. - All properties files on the classpath named
beans.properties
, located by considering the property name as a package qualified name. The order in which properties files in a given package will be consulted is arbitrary (due to inherent limitations of the classloader mechanism) so you should not rely on the order in which these files are evaluated when using this mechanism.
Resolution stops with the first resolver that provides a value for the named property. If no value is resolved, the value
attribute specified on the @Property
qualifier is used as a last resort default. If no value is resolved and the qualifier does not specify a default, the injection process will stop with an error indicating that the property value could not be resolved.
You can use unified EL expressions to reference other property values and environment variables in the value of any injected property value. An EL expression can appear anywhere we would specify a literal string value as the value for an injected property.
- As the
value
attribute for@Property
- In any
beans.properties
file - In a system property or environment variable
The evaluation of an EL expression must not produce another EL expression. This is enforced to prevent an infinite recursion in resolving property values. If there are valid use cases that cannot be solved without recursive evaluation of EL expressions, please open an issue.
The value for an injected property may use any combination of literal text and value expressions; yes, ${name}, you can do this!
When the same value needs to be injected into multiple property injection points, you cannot specify the same name
attribute of @Property
at each injection point. Instead, you need to use an EL expression.
Suppose we want to inject the same value for a timeout
property into two different beans. We can create a property to specify the timeout value and reference it for each of the injection points.
In the root beans.properties
or META-INF/beans.properties
(or as a system property) we specify the timeout value:
timeout=30000
Then we use an EL expression that calls on a p
-function to inject the value wherever we need it:
package baz;
class Foo {
@Inject @Property("${p:required('timeout')}")
private long timeout;
}
package baz;
class Bar {
@Inject @Property("${p:required('timeout')}")
private long timeout;
}
We can also use an EL expression in a properties file anywhere we would have used a literal expression. Suppose we put a beans.properties
file in package baz
. It could contain:
Foo.timeout=${p:required('timeout')}
Bar.timeout=${p:required('timeout')}
We can also specify use a function that provides a default value when the property isn't specified.
@Inject
@Property("${p:optional('databaseUrl', 'jdbc:hsqldb:mem:demodb')}")
private String databaseUrl;
This is especially useful if the databaseUrl
property will be provided by a system property that might not always be specified.
The p
-functions use the same property resolvers in the same order used when finding the value for a property injection point.
You can use EL expressions that invoke an e
-function to reference the values of system environment variables. This is especially convenient for substituting values that vary depending on the runtime environment.
Suppose we have a different database URL depending on the deployment tier; development, pre-production, production. We can easily inject the right URL through an environment variable.
@Inject
@Property("${e:optional('DATABASE_URL', 'jdbc:hsqldb:mem:demodb')}")
private String databaseUrl;
Now, we can set the DATABASE_URL
environment variable in the shell that will run the JVM for our application, and easily inject the right value. This is especially useful when using container-based infrastructure such as Docker,
in which it is quite easy to specify environment settings for the running container, but more difficult to specify system properties for the JVM or override/overlay properties files.
There is also an e:required
function that will result in an unresolved property value error if the environment variable is not set.