Tuesday Aug 06

Key Concepts

Property file configuration using Injection (CDI)

PDFPrintE-mail
Tuesday, 09 April 2013 12:20
AddThis Social Bookmark Button

Inject configuration using CDI
Inject configuration using CDI

One of my favorite games in recent times is to minimize the number of libraries as much as possible by using the features provided in the JDK.

In this context, Spring is an excellent candidate. In this article, I will illustrate how to inject the configuration parameters using CDI instead of Spring IOC.

Our requirements are :

Injecting configuration using a configuration file using the parameter key. We want to define a default value if the param is not suported by the configuration file and a mandatory parameter to throw an exception if the parameter is not provided.

Configuration injection with CDI

Let's define the annotation we will use

Injected configuration annotation

injected configuration annotation with CDI

package com.ubiteck.cdi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.enterprise.util.Nonbinding;
import javax.inject.Qualifier;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectedConfiguration {
    /**
     * Bundle key
     * @return a valid bundle key or ""
     */
    @Nonbinding String key() default "";
    /**
     * Is it a mandatory property
     * @return true if mandator
     */
    @Nonbinding boolean mandatory() default false;
    /**
     * Default value if not provided
     * @return default value or ""
     */
    @Nonbinding String defaultValue() default "";
}

So we are defining the three attributes defined in our requirements and we use the RUNTIME retention policy to get access to it at runtime.
We can bind our configuration using the following snippet :

java-source

    @Inject 
    @InjectedConfiguration(key="host.name",defaultValue="localhost") 
    String hostName;

In this snippet we are injecting the host name using the key 'host.name'. If the parameter is not defined we will use 'localhost' instead.

Configuration Injection Manager

Now that we have our annotation we must process thos annotations to inject the corresponding configuration. To do this, we will develop a "Producer" which we call "ConfigurationInjectionManager".

java-source

import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
public class ConfigurationInjectionManager {
    static final String INVALID_KEY="Invalid key '{0}'";
    static final String MANDATORY_PARAM_MISSING = "No definition found for a mandatory configuration parameter : '{0}'";
    private final String BUNDLE_FILE_NAME = "configuration";
    private final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_FILE_NAME);
    
    @Produces
    @InjectedConfiguration
    public String injectConfiguration(InjectionPoint ip) throws IllegalStateException {
        InjectedConfiguration param = ip.getAnnotated().getAnnotation(InjectedConfiguration.class);
        if (param.key() == null || param.key().length() == 0) {
            return param.defaultValue();
        }
        String value;
        try {
            value = bundle.getString(param.key());
            if (value == null || value.trim().length() == 0) {
                if (param.mandatory())
                    throw new IllegalStateException(MessageFormat.format(MANDATORY_PARAM_MISSING, new Object[]{param.key()}));
                else
                    return param.defaultValue();
            }
            return value;            
        } catch (MissingResourceException e) {
            if (param.mandatory()) throw new IllegalStateException(MessageFormat.format(MANDATORY_PARAM_MISSING, new Object[]{param.key()}));
            return MessageFormat.format(INVALID_KEY, new Object[]{param.key()});
        }
    }

In this so called "producer" we have a method responding to the @InjectionConfiguration annotation by injecting the corresponding configuration. The method should be marked with the @Produces annotation to be considered as a provider for Injected Configuration. The implementation define the logic and retrieve the configuration using the bundle name defined by the variable BUNDLE_FILE_NAME.

Our implementation is completed but does'it work ? To make sure we have a working implementation we will use the excellllllllent Arquillian framework

Testing injection using Arquillian

With the following snippet we will bundle our solution in an archive and test it using Arquillian.

java-source

package com.ubiteck.cdi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.text.MessageFormat;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(Arquillian.class)
public class ConfigurationInjectionManagerTest {
    @Deployment
    public static Archive<?> createTestArchive() {
        Archive<?> archive = ShrinkWrap
            .create(JavaArchive.class, "configuration-ejb.jar")
            .addClass(InjectedConfiguration.class)
            .addClass(ConfigurationInjectionManager.class)
            .addAsResource("configuration.properties")
            .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
        return archive;
    }
    @Inject
    @InjectedConfiguration(key = "host.name")
    String hostName;
    @Inject
    @InjectedConfiguration(key = "invalidParam")
    String invalidParam;
    @Test
    public void test_property_injection() throws Exception {
        assertNotNull(hostName);
        assertEquals("www.java-tutorial.ch", hostName);
    }
    @Test
    public void test_invalid_key() throws Exception {
        assertNotNull(invalidParam);
        assertEquals(
                MessageFormat.format(ConfigurationInjectionManager.INVALID_KEY,
                new Object[] { "invalidParam" }), invalidParam);
    }
}

In createTestArchive method we are creating the archive to deploy. We are adding the annotation class InjectedConfiguration, the configuration manager named "ConfigurationInjectionManager"  and the configuration file called "configuration.properties". To activate CDI we added a blank beans.xml file as a manifest resource.

In line 26 to 32 we are injecting the params to be tested

test_property_injection method is testing a simple workinf case. The test_invalid_key method make sure your get an invalid message in the variable if the configuration parameter is invalid.

Conclusion

CDI is a very elegant way to inject the configuration but it's as wel a very effective way to replace your configuration for testing purpose or refactoring your configuration using an annotation processor.

If you have any remark or questions feel free to put a comment.If you enjoyed this tutorial and want to promote it don't hesitate to click on

Troubleshooting

WELD-001408 Unsatisfied dependencies for type [String] with qualifiers [@InjectedConfiguration] at injection point [[field] @Inject @InjectedConfiguration com.ubiteck.cdi.ConfigurationInjectionManagerTest.hostName]

If your container is using Weld you can get a WELD-001408 Unsatisfied dependencies for type... error.... It means simply that no producer has been found in the classloader of your container to be inject in the injection point defined in the error. Make sure your producer can be found by the classloader and this error should be fixed.

Tags: spring, configuration, parameters, context, injection, excellent, inject

Java Tutorial on Facebook