Skip to content

Property Resolution

Michael Irwin edited this page Jan 24, 2018 · 6 revisions

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

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.

Overriding Properties During Test Execution

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.

Overriding Properties in Java EE and Web Applications

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.

Resolution Order

Property values are resolved by the built-in resolvers in the following order.

  1. System properties set using java.lang.System.setProperty or by using -Dname=value arguments when starting the JRE.
  2. If the System property pinject.properties is set, the referenced properties files are located using the URL(s) specified.
  3. 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.
  4. If running in a Java EE or Servlet container, all beans.properties files located by searching the namespace rooted by the URL specified by the java:comp/env/beans.properties.root JNDI environment setting. The search is conducted in the same manner as when searching the classpath.
  5. 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.
  6. 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.

Using Unified EL Expressions

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!

Referencing Another Property Value

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.

Referencing an Environment Variable

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.