Using Java Config-Builder to assemble your Application Configuration

May 20th, 2014 by

There’s a variety of configuration frameworks to use in our Java applications. Java Config Builder is one of them and it offers some nice features that I would like to demonstrate in the following short examples as are:

Loading values from different sources like property-files, environment variables, command-line-arguments or system properties, specifying default values, mapping arbitrary types or collections, merging configurations and using the Java Bean Validation standard aka JSR-303.

Run configuration in Eclipse IDE

Run configuration in Eclipse IDE

 

Project Setup

First of all we need to add the config-builder library to our project. Maven or Gradle – it’s your choice (SBT is fine, too ;)

Maven

There is just one dependency that we need to add to our pom.xml:

<dependency>
	<groupId>com.tngtech.java</groupId>
	<artifactId>config-builder</artifactId>
	<version>1.2</version>
</dependency>

Gradle

Preferring Gradle, we might want to add the following dependency to our build.gradle

'com.tngtech.java:config-builder:1.2'

Configuration Sources / Features

First a short explanation of basic configuration types:

Property File

We’re able to load multiple property files and property file locations using the following annotations:

  • @PropertiesFiles( {“config1″,”confi2″}): Loads a file config1.properties and config2.properties from the class path
  • @PropertyLocations(directories = {“/some/path”}): Loads property files from the designated directory
  • @PropertySuffixes(): Filter property files by a given suffix

Sample property file for the following example named config.properties and put in src/main/resources:

app.name=Sample Application using Config Builder

System Properties

In addition we may reference system properties using the following annotation:

  • @SystemPropertyValue(“systemproperty”): Loads the system property named systemproperty

Environment Variables

Also we’re able to reference environment variables like this:

  • @EnvironmentVariableValue(“JAVA_HOME”): Loads the environment variable named JAVA_HOME

Command Line Arguments

Finally we’re able to read in command line arguments and specify their format as we’re used to do using tools like Apache CLI or something else:

  • @CommandLineValue(shortOpt = “s”, longOpt = “silent”, hasArg = false): Assigns a command line input. The following formats are allowed: -s, –silent and this input has no further parameters, therefore we set hasArg to false.
  • To make this work, we need to pass the args passed in from our main method to the config builder like this: new ConfigBuilder(..).withCommandLineArgs(args)

Default Values

If we want to define fallback or default values we’re able to do so using the following annotation:

  • @DefaultValue(“value”): Specifies the string value as the default value for the annotated field.
  • This does not sound very interesting at first but combined with type transformers we may assign more complex default values representated as a string.

Type Transformers

Type Transformers allow us to transform the string value read from one of the configuration sources describe above into something more complex to use in our application.

  • @TypeTransformers(MyTransformer.class): Specifies the type transformer to use.
  • The type transformer class simply needs to extends TypeTransformer<I,O> where I is the input type e.g. String and O is the transformed return type e.g. List<Foo>
  • The following transformer taken from the example below transforms a string into a list of user objects:

Our User class:

package com.hascode.tutorial;
public class User {
	private String name;
 
	public User(final String name) {
		this.name = name;
	}
 
	// getter, setter ommitted
}

The transformer using Java 8 streams and lambda expressions. We’re assuming that a string containing one or multiple user names, separated by a comma are passed in.

class StringToUserTransformer extends TypeTransformer<String, List<User>> {
	@Override
	public List<User> transform(final String input) {
		return Stream.of(input.split(",")).map(s -> new User(s))
				.collect(Collectors.toList());
	}
}

*Update*

As Andreas Würl kindly remarked, the transformation as written in the type transformer above is not necessary as the framework performs transitive transform steps here so that the following type converter is sufficient to produce the same result:

public static class StringToUserTransformer extends
TypeTransformer<String, User> {
	@Override
	public User transform(final String input) {
		return new User(input);
	}
}

Our Custom Configuration

Now let’s put everything together in one configuration class assembling configuration fragments from different sources:

  • We’re loading a properties file named config.properties
  • From this property file we’re loading the value for the key app.name into a string variable named appName
  • We’re loading the environment variable JAVA_HOME into a string variable named javaHomeDir
  • We’re loading a system property named target-os into a string variable named targetOs
  • We’re loading a command line argument (cli) with a default-value of interactive into the string variable named mode. We may specify the cli arg using the notation -m or –mode
  • We’re loading a cli argument without an argument into a boolean property. We may specify the option with -s or with –silent
  • We’re loading a list of users from the command line using a custom type transformer that transforms the input string into a generic list of users
  • We’re loading another list of user but in addition we’re specifying a default value here
  • The type transformer splits the command line argument using a comma as separator token and returns an arraylist of user objects
package com.hascode.tutorial;
 
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
import com.tngtech.configbuilder.annotation.propertyloaderconfiguration.PropertiesFiles;
import com.tngtech.configbuilder.annotation.typetransformer.TypeTransformer;
import com.tngtech.configbuilder.annotation.typetransformer.TypeTransformers;
import com.tngtech.configbuilder.annotation.valueextractor.CommandLineValue;
import com.tngtech.configbuilder.annotation.valueextractor.DefaultValue;
import com.tngtech.configbuilder.annotation.valueextractor.EnvironmentVariableValue;
import com.tngtech.configbuilder.annotation.valueextractor.PropertyValue;
import com.tngtech.configbuilder.annotation.valueextractor.SystemPropertyValue;
 
@PropertiesFiles("config")
public class Config {
	@PropertyValue("app.name")
	private String appName;
 
	@EnvironmentVariableValue("JAVA_HOME")
	private String javaHomeDir;
 
	@SystemPropertyValue("target-os")
	private String targetOs;
 
	@DefaultValue("interactive")
	@CommandLineValue(shortOpt = "m", longOpt = "mode", hasArg = true)
	private String mode;
 
	@CommandLineValue(shortOpt = "s", longOpt = "silent", hasArg = false)
	private boolean silent;
 
	@TypeTransformers(StringToUserTransformer.class)
	@CommandLineValue(shortOpt = "u", longOpt = "users", hasArg = true)
	private List<User> usersAllowed = new ArrayList<>();
 
	@DefaultValue("burt,bart,allan")
	@TypeTransformers(StringToUserTransformer.class)
	@CommandLineValue(shortOpt = "f", longOpt = "forbid", hasArg = true)
	private List<User> usersForbidden = new ArrayList<>();
 
	public List<User> getUsersForbidden() {
		return usersForbidden;
	}
 
	public void setUsersForbidden(List<User> usersForbidden) {
		this.usersForbidden = usersForbidden;
	}
 
	public static class StringToUserTransformer extends
			TypeTransformer<String, List<User>> {
		@Override
		public List<User> transform(final String input) {
			return Stream.of(input.split(",")).map(s -> new User(s))
					.collect(Collectors.toList());
		}
	}
 
	// getter, setter ommitted..
}

Running the Application

This sample application initializes our configuration and prints it to the screen.

package com.hascode.tutorial;
 
import com.tngtech.configbuilder.ConfigBuilder;
 
public class Main {
	public static void main(final String[] args) {
		System.setProperty("target-os", "Linux x86_64 GNU/Linux");
 
		Config cnf = new ConfigBuilder<Config>(Config.class)
				.withCommandLineArgs(args).build();
		System.out.println("Starting application " + cnf.getAppName());
		System.out.println("\tJava-Home-Dir\t:" + cnf.getJavaHomeDir());
		System.out.println("\tTarget-OS\t:" + cnf.getTargetOs());
		System.out.println("\tSelected Mode\t:" + cnf.getMode());
		System.out.println("\tTarget-OS\t:" + cnf.isSilent());
		System.out.println("\tAllowed users\t:");
		cnf.getUsersAllowed().forEach(
				u -> System.out.println("\t\t\t" + u.getName()));
		System.out.println("\tForbidden users\t:");
		cnf.getUsersForbidden().forEach(
				u -> System.out.println("\t\t\t" + u.getName()));
	}
 
}

Running with Maven

We’re able to run our Main class with some command line args set running the following command:

mvn clean compile exec:java -Dexec.mainClass=com.hascode.tutorial.Main -DcommandLineArgs="--mode=automatic" "-s" "--users=susan,lisa,tim"

Running from Eclipse IDE

In Eclipse just create a new run configuration and add the following program arguments:

–mode=automatic -s –users=susan,lisa,tim

Run configuration in Eclipse IDE

Run configuration in Eclipse IDE

Output

On my system, running the program produces the following output:

Starting application Sample Application using Config Builder
        Java-Home-Dir   :/usr/lib/jvm/jdk1.8.0_05
        Target-OS       :Linux x86_64 GNU/Linux
        Selected Mode   :automatic
        Target-OS       :true
        Allowed users   :
                        susan
                        lisa
                        tim
        Forbidden users :
                        burt
                        bart
                        allan

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/config-builder-tutorial.git

Resources

Alternative: Netflix Archaius

I have written another tutorial about a possible alternative that has a strong focus on working with dynamic properties here: “Dynamic Configuration Management with Netflix Archaius and Apache ZooKeeper, Property-Files, JMX

    Article Updates

    • 2014-06-18: Added remark from Andreas Würl regarding transitive transform steps in a type converter
    • 2016-04-13: Added link to my Netflix Archaius tutorial

    Tags: , , , , , , ,

    Search
    Categories