Wednesday, October 24, 2012

Injecting Configuration Values with CDI in a Java EE environment

Commonly while developing a web application there is need to configure an application differently per deployment. This configuration is generally handled by an external data source such as a properties or XML file. In this post I will examine a novel way of retrieving these configuration values using CDI injection.

My first thought about injecting Configuration values led me to inject a Configuration object and using this object I could access the configuration values. This is kind of clunky as I never really wanted the configuration object just the values. My code would end up looking like this:
@Inject Configuration conf;

private void useConfig(){
    System.out.println(conf.getValue("myConfig"));
}
But why can't I just inject the value directly. What I would like to do is this
@Inject @Config("myConfig") String myConfig;
Well, I'm not quite able to do that either but I can get pretty close to this. Ultimately, I was able to come up with a configuration library that allows me to inject configuration like this
@Inject @Config @ConfigName("myConfig") String myConfig;
So how did I do it? There's a few pieces to this puzzle. There is a CDI producer, a CDI qualifier, a plain old annotation and a data access object to retrieve the configuration values. Let's start with the qualifer.

Configuration Qualifier

A qualifier in CDI is a way to limit what will be injected. We will use this qualifier on String objects to allow our producer, which we'll come to later, to provide the values. A CDI Qualifier is actually just a regular annotation that is annotated with @Qualifier. Below is the qualifier we will use.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER})
public @interface Config {
}

Configuration Name Annotation

The configuration name allows us to inject the value by the name that we refer to them in the configuration data source. This annotation is quite simple and will contain a value, which will be the configuration name. If you already know about qualifiers, you may be asking why we have this annotation and why not include the name in the qualifier. The problem is that if you included the name in the qualifier you'd have to have a separate producer for every configuration value. This is because when CDI does it's look up for objects and producers that match the injection, the value of the qualifier is actually used.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER})
public @interface ConfigName {
   public String value();
}

Configuration Data Access Object

We will need some way of accessing our configuration values, whether they're stored in a properties file, and XML file, a database, or somewhere else, we just need a common way to access them. To this end, we'll just describe an interface and you can provide an implementation.
public interface ConfigurationDao{
    String readValue(String name);
}
Now that all of that is out of the way, let's see the producer.

Configuration Producer

So, to start, we need a class that will produce the configuration values, lets call it ConfigurationProducer.
public class ConfigurationProducer {
Next, we'll need access to the configuration data, so let's inject that.
   @Inject ConfigurationDao dao;
And now the actual producer method. A producer method, as defined by the weld reference,
... is a method that acts as a source of bean instances.
More information on Producer Methods can be found in the Weld Reference. This needs to be annotated as a Producer and qualified with Config, the qualifier we made earlier.
   @Produces @Config public String getConfigurationValue(...){
Next, we need more details about where we're injecting, maybe information about the point of injection. Well, it just so happens that part of the CDI specification is that Producers can have access to the InjectionPoint.
   @Produces @Config public String getConfigurationValue(InjectionPoint p){
You can see the Weld Reference for more information on the Injection Point. With the injection point, we can actually access the ConfigName annotation.
       String valueName = null;
       for(Annotation annot:p.getAnnotated().getAnnotations()){
           if(annot instanceof ConfigName){
               log.debug("Found a config name annotation");
               ConfigName configNameAnnot = (ConfigName)annot;
               valueName = configNameAnnot.value();
           }
       }
 
And now that we've determined the Configuration Name all we have to do is look it up and return the value.
       return dao.readValue(valueName);
And that's it. Now, in any class (that supports injection) you can simply code
@Inject @Config @ConfigName("super.awesome.config") String configValue;
and the producer will handle the rest. On top of this, you have further options with the producer. You can add another annotation for default values. If this annotation is found and there is nothing in the configuration data source this value would be used. You can also have it provide types other than String, simply add another method to your producer that provides, lets say, an Integer instead of a String. There are many further options that you could tackle, you could even include multiple data sources with some simple tweaks.

Finally, i just want to point out that the code on this page is not production ready. The DAO should likely be able to throw an Exception if there is an error accessing the data source, and it might be wise for the Producer to throw an exception if the ConfigName annotation is not found, but, for simplicity's sake these things are not here.