All-in-one Java configuration properties at hand

Learn on how to use the “all-in-one” Java configuration properties giving you all the power of the various configuration features with a single line of code.

Managing properties from multiple sources such as files, input streams or URLs, with a precedence applied on them, enriched with profile functionality, obfuscated and taking environment variables, command line arguments or system properties into account as well as being being observable regarding updates:

All this functionality is covered by artifacts such as refcodes-properties with its extensions for obfuscation, console or observer functionality.

Each part of functionality mentioned above has its own coherent implementation combinable with each other. This gives you much power to construct exactly the behavior you need.

In my blog post Dead simple Java application configuration I already outlined the ease of use of the refcodes-properties artifact alongside the observer extension. With the blog post Automatically obfuscate your Java application’s configuration I described on how to secure your properties files using the obfuscation extension.

This blog post now introduces the refcodes-properties-ext-runtime artifact, providing you with “all-in-one” runtime properties harnessing all the configuration properties’s functionality with just one line of code.

“All-in-one” runtime properties

The ApplicationProperties type defines all the methods required to provide the “all-in-one” functionality described above. The ApplicationProperties class implements the actual functionality as seen in the image below and the ApplicationPropertiesSugar class provides the syntactic sugar for ease of use.

Runtime properties onion

The diagram above illustrates the “onion” alike scheme which the ApplicationProperties class implements. The ApplicationProperties manage various therein contained Properties instances of various sub-types, applying the obfuscation mechanism to each “onion” layer, thereby harnessing the functionality provided by the refcodes-properties-ext-obfuscation artifact:

  • Profile: First of all, the managed properties are being applied a profile projection as of the ProfilePropertiesProjection type. In case any “profiles” have been defined in a property called “/runtime/profiles”, then these profiles are applied on the final representation of the properties.

  • Precedence: Any Properties instances wrapped by the PropertiesPrecedenceComposite are applied with a precedence mechanism, defining which properties sources wins over which other properties source in case of colliding properties keys.

  • Args: First in the precedence chain are the command line arguments represented by the ArgsProperties and the ArgsParserProperties types with their properties representation of the command line arguments.

  • System: Second in the precedence chain are the system properties represented by the SystemProperties type and with its properties representation of the system properties.

  • Env: Third in the precedence chain are the environment variables represented by the EnvironmentProperties type with their properties representation of the environment variables.

  • Resources: Last come zero to many explicitly added ResourceProperties such as YamlProperties or TomlPropertiesBuilder with a precedence according to the order they have been added.

See the section on “Further reading” for details on the above mentioned “onion” layers building up the ApplicationProperties.

Getting started

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-properties-ext-runtime</artifactId>
		<groupId>org.refcodes</groupId>
		<version>3.3.5</version>
	</dependency>
	...
</dependencies>

Using the “all-in-one” runtime properties

Using the ApplicationProperties out of the box is quite simple, below we use syntactic sugar to create a ApplicationProperties instance and then print out the properties contained in there right from the start:

1
2
3
4
5
6
7
8
9
10
11
import static org.refcodes.properties.ext.application.ApplicationPropertiesSugar.*;
// ...
public class Foo {
	public Foo() {
		ApplicationProperties theProperties = fromApplicationProperties();
		for ( String eKey : theProperties.keySet() ) {
			LOGGER.info( eKey + " = " + theProperties.get( eKey ) );
		}
	}
	// ...
}

You will notice that even though you did not add any properties yourself, there are lots of properties already accessible. An excerpt may look similar to the listing below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
/eclipse/home = /usr/lib/eclipse
/file/encoding = UTF-8          
/file/encoding/pkg = sun.io     
/file/separator = /             
/gdk/core/device/events = 1     
/gdk/scale = 1                  
/java/class/version = 53.0                                                 
/java/home = /usr/lib/jvm/java-9-jdk                                       
/java/io/tmpdir = /tmp                                                     
/java/library/path = /usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib
/java/runtime/name = Java(TM) SE Runtime Environment                       
/java/runtime/version = 9.0.4+11                                           
/java/version = 9.0.4                                                      
/java/vm/compressedOopsMode = Zero based                                   
/java/vm/info = mixed mode                                                 
/java/vm/name = Java HotSpot(TM) 64-Bit Server VM                          
/java/vm/vendor = Oracle Corporation                                       
/java/vm/version = 9.0.4+11
/xdg/session/type = x11
/xdg/vtnr = 2          
/xmodifiers = @im=ibus
... 

How is this possible? As shown by the illustration above, the ApplicationProperties instance takes several sources of properties into account. By default, Java’s system properties and your operating systems’s environment variables are included.

Those properties are transformed to their path representation to enable unified access to all kinds of properties sources. For example a system property with name java.version will be unified to the path /java/version or an environment variable with name JAVA_HOME will be transformed to the path /java/home.

Regarding the “path” paradigm, please read my blog post The canonical model, an ace upon your sleeve.

Colliding paths are resolved as of their precedence. For details on the precedence in case of colliding property names from different sources, please consult the diagram above.

Obfuscation properties from a file

Now you can enrich your ApplicationProperties with other properties sources. In the example below, we load the properties from a file and want specially marked properties to be encrypted (and decrypted) on a host level:

1
2
3
4
5
6
7
8
9
import static org.refcodes.properties.ext.application.ApplicationPropertiesSugar.*;
// ...
public class Foo {
	public Foo() throws IOException, ParseException {
		ApplicationProperties theProperties = withRuntimeObfuscateMode( SystemContext.HOST );
		theProperties.withFilePath( "application.config" );
	}
	// ...
}

In addition to the system properties and the environment variables being automatically added, we added the properties from a file (see the ConfigLocator on how the properties file is discovered and where it is to be placed).

See the blog post Automatically obfuscate your Java application’s configuration for details on how to use obfuscation.

Adding command line arguments

You may directly add properties from the command line by constructing the ApplicationProperties as follows:

1
2
3
4
5
6
7
8
9
import static org.refcodes.properties.ext.application.ApplicationPropertiesSugar.*;
// ...
public class Foo {
	public static void main( String[] args ) throws IOException, ParseException {
		ApplicationProperties theProperties = withArgs( args );
		// ...
	}
	// ...
}

If you want to apply a a special syntax to your command line arguments, you may also go as follows:

1
2
3
4
5
6
7
8
9
10
import static org.refcodes.properties.ext.application.ApplicationPropertiesSugar.*;
// ...
public class Foo {
	public static void main( String[] args ) throws IOException, ParseException {
		ArgsSyntax theArgsSyntax = ... 
		ApplicationProperties theProperties = withRootCondition( theArgsSyntax ).withArgs( args );
		// ...
	}
	// ...
}

On how to write your syntax for your command line arguments, please refer to the blog on the refcodes-cli artifact.

Further reading

See the blog post on the refcodes-properties artifact for further details on the usage of this artifact. Also see the blog posts Dead simple Java application configuration, Automatically obfuscate your Java application’s configuration and All-in-one Java configuration properties at hand. For examples and usage, please take a look at the according Unit-Tests here. On chaos-based encryption, see also the refcodes-security artifact and the according Chaos-based encryption blog post. On how to write your command line arguments parser, please refer to the blog post refcodes-cli.