March 11, 2015

Inject external properties using CDI, Java and WildFly

Often you need to be able to configure different runtime properties based on environment.  Using the configuration below, you can have a different property file on each server (dev, production, test), each defining their own values.

How it works

  1. The location of your property file is found by looking at a system property configured in the WildFly configuration file. This allows you to change the location of the property file, without having to recompile/redeploy the application.
  2. The property file is loaded and the values are stored in a HashMap inside a @Singleton session bean
  3. The properties are then injected into your CDI beans, making them accessible to your application code. This is achieved by creating a CDI Qualifier and producer method.

How its done

1. Create and populate a properties file inside the WildFly configuration folder
$ echo 'docs.dir=/var/documents' >> .standalone/configuration/application.properties

 2. Add a system property to the WildFly configuration file.
$ ./bin/jboss-cli.sh --connect
[standalone@localhost:9990 /] /system-property=application.properties:add(value=${jboss.server.config.dir}/application.properties)

This will add the following to your server configuration file (standalone.xml or domain.xml):
<system-properties>
    <property name="application.properties" value="${jboss.server.config.dir}/application.properties"/>
</system-properties>

3. Create the singleton session bean that loads and stores the application wide properties
package com.ritchie.chris.properties;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;

@Singleton
public class PropertyFileResolver {
     
    private Map<String, String> properties = new HashMap<>();
     
    @PostConstruct
    private void init() throws IOException {
         
        //matches the property name as defined in the system-properties element in WildFly
        String propertyFile = System.getProperty("application.properties");
        File file = new File(propertyFile);
        Properties properties = new Properties();
         
        try {
            properties.load(new FileInputStream(file));
        } catch (IOException e) {
            System.out.println("Unable to load properties file" + e);
        }
         
        HashMap hashMap = new HashMap<>(properties);
        this.properties.putAll(hashMap);
    }
 
    public String getProperty(String key) {
        return properties.get(key);
    }
}

4. Create the CDI Qualifier. We will use this annotation on the Java variables we wish to inject into.
package com.ritchie.chris.properties;

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

import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR })
public @interface ApplicationProperty {

    // no default meaning a value is mandatory
    @Nonbinding
    String name();
}

5. Create the producer method; this generates the object to be injected
package com.ritchie.chris.properties;

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

public class ApplicaitonPropertyProducer {

    @Inject
    private PropertyFileResolver fileResolver;

    @Produces
    @ApplicationProperty(name = "")
    public String getPropertyAsString(InjectionPoint injectionPoint) {
        
        String propertyName = injectionPoint.getAnnotated().getAnnotation(ApplicationProperty.class).name();
        String value = fileResolver.getProperty(propertyName);
        
        if (value == null || propertyName.trim().length() == 0) {
            throw new IllegalArgumentException("No property found with name " + value);
        }
        return value;
    }
    
    @Produces
    @ApplicationProperty(name="")
    public Integer getPropertyAsInteger(InjectionPoint injectionPoint) {
        
        String value = getPropertyAsString(injectionPoint);
        return value == null ? null : Integer.valueOf(value);
    }
}

6. Lastly inject the property into one of your CDI beans
package com.ritchie.chris.properties;

import javax.ejb.Stateless;
import javax.inject.Inject;

@Stateless
public class MySimpleEJB {

    @Inject
    @ApplicationProperty(name = "docs.dir")
    private String myProperty;
    
    public String getProperty() {
        return myProperty;
    }
}

Source code can be found on GitHub



2 comments :

  1. Hi Chris, interesting post. I've also started a similar project that works independently of the application server where the configuration file is optional. Available on github goo.gl/sV1G8y

    ReplyDelete
  2. This is just PERFECT!!! Just what I needed! I just plugged it in and it worked!!! Thanks a bunch!

    ReplyDelete