Java EE Producer for application configuration

Since the introduction of Java CDI container my focus is on building applications without the usage of any Spring libraries. It’s not that I do not like Spring, but since CDI I don’t really need a heavy weight DI container in my application anymore. When looking at EE 7 even more of the stuff that is provided by Spring comes out of the box.

One thing that I do miss in the EE specification is the handling of properties. There are always properties that are environment specific. As a craftsman you do not want any environment specific in the artifact you deliver. How to do this. On the Internet this discussion started some while ago.  Juliano Viana started this discussion in 2010 with  the post Application configuration in Java EE 6 using CDI – a simple example. This provided a simple but elegant solution for the problem. It was far from complete and usable but still the trend had been set. Later Adam Bien and Markus Eisele produced blogs about it.

All of  the implementations that are provided are either incomplete or are lacking somethings that the other does not have. When handling with properties there are a few requirements that an external property must have:

  • Default values.
    When the property is not available use a reasonable default.
  • Flag as mandatory.
    When the property is not configured, throw an error.
  • custom name.
    The name of the field has no relation to the name as it is mentioned in the configuration file.

The other posts addressed those issues but not all of them in one solution. Key to the solution is the EE7 Producer, see Injecting Objects by Using Producer Methods – The Java EE 6 Tutorial for details.

The usage of the property injection will look like this:

 @Inject @NamedProperty(key = "system.host", defaultValue="localhost") private String host;

So first create an annotation for the property:

@Retention(RUNTIME)
@Target({FIELD, METHOD})
@Qualifier 
public @interface NamedProperty { 
/** 
 * key or name of the property * 
 * @return a valid key or "" 
 */ 
 
 @Nonbinding String key() default ""; 
 /** * Is it a mandatory property. When key is not found in the configuration it 
  * throws a {{@link java.lang.IllegalArgumentException}} 
  * 
  * @return true if mandatory 
  */ 
  
 @Nonbinding boolean mandatory() default false; 
 /** 
  * Default value if not provided 
  * 
  * @return default value or "" 
  */ 
 @Nonbinding String defaultValue() default ""; 
}

As you can see this is fairly straight forward. Next step is to create a Producer that handles the annotation. To get a Producer simply type in a class, this certain producer produces an element of type String other types need their specific return type.

@Produces @NamedProperty public String getString(final InjectionPoint point)

With this you have a producer for the annotation @NamedProperty. The InjectionPoint is required to get the information of the annotation. The annotation created has certain properties that we want to process. The access to the annotation is done by means of the InjecttionPoint. The following snippet shows how to do this:

point.getAnnotated().getAnnotation(NamedProperty.class).mandatory();

The following is the FOOBARred implementation, this gist contains a full implementation.

/** 
 * Factory for providing configuration from a properties file 
 */ 
 
@Startup 
@Singleton 
public class ConfigurationFactory { 
  private static final String APPLICATION_PROPERTIES = "application.properties"; 
  private Properties configData; 
  
  @PostConstruct 
  public void fetchConfiguration() {
    try { 
      configData = loadPropertiesFromFile((String) applicationConfiguration); 
      return; 
    } catch (IOException e) {
      LOGGER.error("Error loading properties. " + e.getMessage()); 
    }    
  }   

  private Properties loadPropertiesFromFile(final String applicationConfiguration) { 

    Properties properties = new Properties(); 
    
    try (InputStream in = new  FileInputStream(applicationConfiguration)) {
      if (in != null) { 
        properties.load(in); 
      }  
    } catch (FileNotFoundException | IOException e) { 
      //-- 
    } 
    
    return properties; 
  }
  
  /** 
   * Get a String property, named by the annotation 
   * {{@link nl.elucidator.homeautomation.configuration.NamedProperty}} 
   * 
   * @param point 
   * @return String 
   */ 
   @Produces 
   @NamedProperty 
   public String getString(final InjectionPoint point) { 
     String key = getKey(point); 
     return configData.getProperty(key);
   } 
   
   @Produces 
   @NamedProperty
   public int getInt(final InjectionPoint point) {
     String key = getKey(point); 
     return configData.getProperty(key);
   } 
   
   private String getKey(final InjectionPoint point) { 
     return point.getAnnotated().
           getAnnotation(NamedProperty.class)
           .key();       
   }
 }
 
 

The actual implementation I finally used also included a named @Resource. When the JNDI name applicationConfiguration exists this value is used to read the configuration file when not available the file application.properties is taken from  the classpath.