Inject Java Properties in Java EE Using CDI

The aim of this post is to show you how, using CDI, you can inject Java properties entries (those in *.properties files) directly into your Java class. This is somewhat similar to the Spring’s @Value annotation I needed in my plain Java EE project.

The whole code can be found on GitHub so feel free to clone it, use it or make any changes you like.

Context

Ok, so basically in a Java EE environment we already have a way to define some properties that are deployment-specific – it’s the <env-entry> element in the descriptor.

So, if you use EJB you have ejb-jar.xml where you can define the <env-entry> element to define key, type and a default value (for more information take a look at David Blevins’ resource.)

You can then refer to these values using @Resource annotation in your EJBs.

If you can, you can also define property using web.xml and its <env-entry> element.

Nevertheless, we want something simpler – with Java EE 6 you don’t have to have web.xml or ejb-jar.xml. Moreover, we want to inject resources not only in the EJBs but in any managed Java class.

We want some CDI magic.

Overall description

We want to use code something like this in our classes:

@Inject
@ConfigurationValue
String myProp;

this dependency should inject a property with key myProp into our annotated class field.

The @Inject annotation is the official javax.inject.Inject one and the @ConfigurationValue is our own custom one we’ll use to distinct classes fields that should have values injected from the properties.

Qualifier

The @ConfigurationValue is pretty straightforward. It is a CDI Qualifier, so it helps us to define a type of injection to apply to it:

package com.piotrnowicki.cdiconfig;

import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;

/**
 * Defines method / variable that should be injected with value read from some arbitrary resource (e.g. from
 * <code>properties</code> file.)
 * 
 * @author Piotr Nowicki
 * 
 */
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface ConfigurationValue {
    /**
     * Key that will be searched when injecting the value.
     */
    @Nonbinding
    String value() default "";

    /**
     * Defines if value for the given key must be defined.
     */
    @Nonbinding
    boolean required() default true;
}

Note the @Nonbinding annotations on the attributes. By default, attributes of CDI Qualifier are used to further filter the injected candidate. In this case, we want just to set additional information to the producer method and not to use the binding functionality of the attributes.

Ok, so we have qualifier and a client that uses the value. But who will actually provide us with the appropriate value?

Producer Methods

Now we need to define a producer who will produce the objects we want to inject. In other words, it should look in the properties files and grab the appropriate entry for us.

package com.piotrnowicki.cdiconfig;

import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Inject;

/**
* Produces {@link ConfigurationValue} annotated fields. It's responsible for supporting conversion between
* types (mainly from String to any other type required by the user.)
*
* <p>
* These producers should not be interested where the fields are read from. It's the {@link PropertyResolver}
* who is responsible for the configuration loading.
* </p>
*
* @see PropertyResolver
*
* @author Piotr Nowicki
*
*/
public class ConfigurationValueProducer {

    @Inject
    PropertyResolver resolver;

    /**
    * Main producer method - tries to find a property value using following keys:
    *
    * <ol>
    * <li><code>key</code> property of the {@link ConfigurationValue} annotation (if defined but no key is
    * found - returns null),</li>
    * <li>fully qualified field class name, e.g. <code>eu.awaketech.MyBean.myField</code> (if value is null,
    * go along with the last resort),</li>
    * <li>field name, e.g. <code>myField</code> for the example above (if the value is null, no can do -
    * return null)</li>
    * </ol>
    *
    * @param ip
    * @return value of the injected property or null if no value could be found.
    */
    @Produces
    @ConfigurationValue
    public String getStringConfigValue(InjectionPoint ip) {

        String fqn = ip.getMember().getDeclaringClass().getName() + "." + ip.getMember().getName();

        // Trying with explicit key defined on the field
        String key = ip.getAnnotated().getAnnotation(ConfigurationValue.class).value();
        boolean isKeyDefined = !key.trim().isEmpty();

        boolean valueRequired = ip.getAnnotated().getAnnotation(ConfigurationValue.class).required();

        if (isKeyDefined) {
            return resolver.getValue(key);
        }

        // Falling back to fully-qualified field name resolving.
        key = fqn;
        String value = resolver.getValue(fqn);

        // No luck... so perhaps just the field name?
        if (value == null) {
            key = ip.getMember().getName();
            value = resolver.getValue(key);
        }

        // No can do - no value found but you've said it's required.
        if (value == null && valueRequired) {
            throw new IllegalStateException("No value defined for field: " + fqn
                    + " but field was marked as required.");
        }

        return value;
    }

    /**
    * Produces {@link Double} type of property from {@link String} type.
    *
    * <p>
    * Will throw {@link NumberFormatException} if the value cannot be parsed into a {@link Double}
    * </p>
    *
    * @param ip
    * @return value of the injected property or null if no value could be found.
    *
    * @see ConfigurationValueProducer#getStringConfigValue(InjectionPoint)
    */
    @Produces
    @ConfigurationValue
    public Double getDoubleConfigValue(InjectionPoint ip) {
        String value = getStringConfigValue(ip);

        return (value != null) ? Double.valueOf(value) : null;
    }

    /**
    * Produces {@link Integer} type of property from {@link String} type.
    *
    * <p>
    * Will throw {@link NumberFormatException} if the value cannot be parsed into an {@link Integer}
    * </p>
    *
    * @param ip
    * @return value of the injected property or null if no value could be found.
    *
    * @see ConfigurationValueProducer#getStringConfigValue(InjectionPoint)
    */
    @Produces
    @ConfigurationValue
    public Integer getIntegerConfigValue(InjectionPoint ip) {
        String value = getStringConfigValue(ip);

        return (value != null) ? Integer.valueOf(value) : null;
    }
}

The code and comments should be enough to understand what it’s doing.

Notice the PropertyResolver that is injected in the above code. This class is a CDI singleton bean that is responsible for actually accessing the properties files.

After its initialization, it allows you to fetch a property with given key (you can think of this class as a Map<String, Object> wrapper.) Its actual implementation can be found below:

Property Files Resolver

package com.piotrnowicki.cdiconfig;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.inject.Singleton;

/**
* <p>
* Reads all valid property files within the classpath and prepare them to be fetched.
* </p>
*
* <p>
* This class <strong>can</strong> be accessed concurrently by multiple clients. The inner representation of
* properties <strong>should not</strong> be leaked out; if this is absolutely required, use unmodifiable
* collection.
* </p>
*
* <p>
* This resolver <strong>doesn't pay attention</strong> to multiple properties defined with the same name in
* different files. It's impossible to determine which one will take precedence, so the responsibility for
* name-clash is a deployer concern.
* </p>
*
* @author Piotr Nowicki
*
*/
@Singleton
public class PropertyResolver {

    // TODO: Change it to some hierarchical structure if required.
    Map<String, Object> properties = new HashMap<>();

    /**
    * Initializes the properties by reading and uniforming them.
    *
    * This method is called by the container only. It's not supposed to be invoked by the client directly.
    *
    * @throws IOException
    * in case of any property file access problem
    * @throws URISyntaxException
    */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @PostConstruct
    private void init() throws IOException {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        List<File> propertyFiles = getPropertyFiles(cl);

        for (File file : propertyFiles) {
            Properties p = new Properties();
            p.load(new FileInputStream(file));

            // TODO: If required - notify if added key was already present in the map
            properties.putAll(new HashMap<String, Object>((Map) p));
        }
    }

    /**
    * Gets flat-file properties files accessible from the root of the given classloader.
    *
    * @param cl
    * classpath to be used when scanning for files.
    *
    * @return found property files.
    *
    * @throws IOException
    * if there was a problem while accessing resources using the <code>cl</code>.
    */
    List<File> getPropertyFiles(ClassLoader cl) throws IOException {
        List<File> result = new ArrayList<>();

        Enumeration<URL> resources = cl.getResources("");

        while (resources.hasMoreElements()) {
            File resource = getFileFromURL(resources.nextElement());

            File[] files = resource.listFiles(new PropertyFileFilter());
            result.addAll(Arrays.asList(files));
        }

        return result;
    }

    /**
    * Converts URL resource to a File. Makes sure that invalid URL characters (e.g. whitespaces) won't
    * prevent us from accessing the valid file location.
    *
    * @param url
    * URL to be transformed
    *
    * @return File pointing to the given <code>url</code>.
    */
    File getFileFromURL(URL url) {
        File result;

        try {
            result = new File(url.toURI());
        } catch (URISyntaxException e) {
            result = new File(url.getPath());
        }

        return result;
    }

    /**
    * Returns property held under specified <code>key</code>. If the value is supposed to be of any other
    * type than {@link String}, it's up to the client to do appropriate casting.
    *
    * @param key
    * @return value for specified <code>key</code> or null if not defined.
    */
    public String getValue(String key) {
        Object value = properties.get(key);

        return (value != null) ? String.valueOf(value) : null;
    }
}

It scans all *.properties in your classpath root, reads them and combines together. The properties file can have any name.

Property Files Filter

Now the last part that is quite useful in our case is the FileFilter that allows us to list only *.properties files within the root of the classpath. I’ve come up with the following implementation:

package com.piotrnowicki.cdiconfig;

import java.io.File;
import java.io.FileFilter;

/**
* Selects only files that are within <code>WEB-INF</code> directory and have <code>properties</code>
* extension.
*
* @author Piotr Nowicki
*/
public class PropertyFileFilter implements FileFilter {

    @Override
    public boolean accept(File pathname) {

        // TODO: Is this even possible in real-life?
        if (pathname == null) {
            return false;
        }

        // We're not investigating sub-directories
        boolean isDirectory = pathname.isDirectory();

        /*
        * FIXME: Change to something more appropriate - user can have a dir named "classes" on the regular
        * path - should it also be scanned for 'properties'?
        */
        boolean isWebInfResource = pathname.getAbsolutePath().contains("classes/");

        if (isDirectory || !isWebInfResource) {
            return false;
        }

        String extension = getExtension(pathname.getName());

        if (extension.equals("properties")) {
            return true;
        } else {
            return false;
        }
    }

    /**
    * <p>
    * Returns filename extension. Returns empty String if no extension is defined. E.g.:
    * <ul>
    * <li><code>myFile.dat</code>, returns <code>dat</code></li>
    * <li><code>myFile.with.dots.properties</code>, returns <code>properties</code></li>
    * </ul>
    * </p>
    *
    * <p>
    * This method never returns null and is null-argument safe.
    * </p>
    *
    * @param filename
    * @return extension of the <code>filename</code> without the trailing dot.
    */
    protected String getExtension(String filename) {
        if (filename == null) {
            return "";
        }

        int lastDotIdx = filename.lastIndexOf(".");

        if (lastDotIdx == -1) {
            return "";
        }

        return filename.substring(lastDotIdx + 1);
    }
}

This class has nothing to do with the CDI itself – it’s just a helper class for listing only interesting to us files.

Unit and Integration Tests

I decided to go with TestNG instead of JUnit because I very like the Data Provider feature of the TestNG. For those of you who are not familiar with this feature, take a look at the exemplary test:

package com.piotrnowicki.cdiconfig;

import static org.fest.assertions.Assertions.assertThat;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.piotrnowicki.cdiconfig.PropertyResolver;

public class PropertyResolverTest extends Arquillian {

    @Inject
    PropertyResolver cut;

    @Deployment
    public static Archive<?> createDeployment() throws IOException {
        JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(PropertyResolver.class);

        return archive;
    }

    @Test(dataProvider="getFileFromURLProvider")
    public void getFileFromURL(URL url, File expected) {
        File actual = cut.getFileFromURL(url);

        assertThat(actual).isEqualTo(expected);
    }

    @DataProvider(name = "getFileFromURLProvider")
    Object[][] getFileFromURLProvider() throws MalformedURLException {
        List<Object[]> data = new ArrayList<>();

        data.add(new Object[] { new URL("file:///testMyFile"), new File("/testMyFile") });
        data.add(new Object[] { new URL("file:///myDir/file with whitespaces"), new File("/myDir/file with whitespaces") });
        data.add(new Object[] { new URL("file:///file%20with%20whitespaces"), new File("/file with whitespaces") });

        return data.toArray(new Object[0][0]);
    }

    @Test(dataProvider = "keyProvider")
    public void getValue(String key, String expected) {
        String actual = cut.getValue(key);

        assertThat(actual).isEqualTo(expected);
    }

    @DataProvider(name = "keyProvider")
    Object[][] dataProvider() {
        List<Object[]> data = new ArrayList<>();

        data.add(new Object[] { "myProp", "myVal" });
        data.add(new Object[] { "myProp2", "myVal2" });
        data.add(new Object[] { "myProp3", null });

        return data.toArray(new Object[0][0]);
    }
}

You can define your Data Provider method and use it to generate test data (and expected result in my case) for your test methods.

It allows me to cleanly test method behaviour for different arguments. At the same time, TestNG will save information about each of the test methods invocation – so for Data Provider with 3 different data sets, you’ll get information about 3 executions of a test method in the report (each with different arguments.) It’s quite useful to retain this kind of information.

Second thing that I use very often are fluent assertions. I don’t care if its Hamcrest or FEST. I just love the fluent API and the way you can easily read the test assertions and know exactly what it does.

Last thing about this snippet is that if you want to use Arquillian to conduct your integration tests, you should extend the org.jboss.arquillian.testng.Arquillian class. It’s an equivalent of the @RunWith(Arquillian.class) in the JUnit’s world.

It’s worth mentioning, as it took me a few minutes to dig this information.

Nice thing about using Arquillian in your tests is that you can easily simulate a client code of your component just as done here.

Side notes

Below you can find few remarks about problems I’ve stumbled upon when writing this example.

slf4j Hell

I’ve got some weird SLF4J errors when executing the Arquillian test:

java.lang.NoSuchMethodError: 
    org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Throwable;)V

If you’ve ever used SLF4J with log4j and got this error – you probably know it’s because of the messed up versions of slf4j on your classpath. The point is, I’ve inspected the pom.xml file and there was no conflicting versions (well, there was but between versions 1.6.1 and 1.6.4, however the error I’ve posted above is related with differences between versions 1.5.x and 1.6.x.)

It seemed that my Eclipse project had a Glassfish 3.1.2 facet set and apparently it added slf4j 1.5.x libraries on the classpath. This resulted in those nasty exceptions.

Removing the facet solved the problem.

Java EE API in Maven

Arquillian User Guide is very right about using org.jboss.spec:jboss-javaee-6.0 artifact in your pom.xml instead of the javax:javaee-api.

Don’t think twice – if you plan to use Arquillian – just use the coordinates they posted in the user guide instead of Java EE API with empty method bodies.

Accessing Files in Java EE

I’ve seen discussions about allowance of accessing filesystem from within the Java EE applications. I do believe that this restriction is talking about accessing plain filesystem rather than fetching the resources from the classpath, so I don’t believe it breaks the Java EE specification.

Test it

This code is mainly for exemplary purposes, but if instead of checking the sources and fiddling with how it’s implemented, you just want to see it running, you can define the following Maven dependency:

    <dependency>
        <groupId>com.piotrnowicki</groupId>
        <artifactId>cdiconfig</artifactId>
        <version>0.1</version>
    </dependency>

And set my website as a repository for artifacts:

<repository>
    <id>piotrnowicki.com</id>
    <url>http://maven.piotrnowicki.com</url>
</repository>

Either way, remember to make your web application CDI managed by putting beans.xml in your WEB-INF directory!