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



March 10, 2015

Obtaining a reference to a CDI managed bean

Most of the time you can inject your CDI beans using the @Inject annotation. There may be occasions when you need to access your CDI beans inside a class where you can not use @Inject, you have a couple of options.

To programmatically access a CDI managed bean, first you need to get hold of the BeanManager (analogous to the ApplicationContext in Spring), by using CDI.current() or by doing a JNDI lookup.

Using CDI.current()
BeanManager bm = CDI.current().getBeanManager();

Using JNDI:
BeanManager bm = null;
try {
    InitialContext context = new InitialContext();
    bm = (BeanManager) context.lookup("java:comp/BeanManager");
} catch (Exception e) {
    e.printStackTrace();
}

Now you have the BeanManager you can access your CDI beans by doing either a type-based lookup or a name-based lookup.

Type based:
Bean<CrudService> bean = (Bean<CrudService>) bm.getBeans(CrudService.class).iterator().next();
CreationalContext<CrudService> ctx = bm.createCreationalContext(bean);
CrudService crudService = (CrudService) bm.getReference(bean, CrudService.class, ctx);

Name-based
Bean bean = bm.getBeans("crudService").iterator().next();
CreationalContext ctx = bm.createCreationalContext(bean);
CrudService crudService = bm.getReference(bean, bean.getClass(), ctx);

When using name-based lookup, your name has to match the value you pass into your @Named annotation. If do not pass a value then it is the name of your class, in camel case. Example:
@Named("crudService")
public class CrudService {}

And a full code example using CDI.current() and type-based lookup:
BeanManager bm = CDI.current().getBeanManager();
Bean<CrudService> bean = (Bean<CrudService>) bm.getBeans(CrudService.class).iterator().next();
CreationalContext<CrudService> ctx = bm.createCreationalContext(bean);
CrudService crudService = (CrudService) bm.getReference(bean, CrudService.class, ctx);