refcodes-properties: Managing your application's configuration

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.

What is this repository for?

This artifact provides a canonical model for processing properties from various different formats (*.yaml, *.ini, *.toml, *.json, *.xml or *.properties) and locations (JAR files, file system files, streams, HTTP resources or GIT repositories) and for converting properties to and from POJOs (plain java objects) and for applying various transformations and views to the properties.

How do I get set up?

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</artifactId>
		<groupId>org.refcodes</groupId>
		<version>3.3.8</version>
	</dependency>
	...
</dependencies>

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

If you also want to observe your properties (e.g. listen for create , update or delete operations), you may instead add the following dependency to your pom.xml:

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

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

Jump start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import static org.refcodes.properties.PropertiesSugar.*;
// ...
public class Foo {
	public void bar() throws IOException, ParseException {
		// ...
		PropertiesBuilder theProperties =  fromProperties( 
			toProperty( "message", "Hello world!" ),
			toProperty( "foo", "bar" )
		);
		fileToTomlProperties( theProperties, "application.toml");
		// ...
		Properties thePrecedence = toPrecedence( 
			fromSystemProperties(), 
			fromEnvironmentVariables(), 
			seekFromTomlProperties( "application.toml" )
		);
		FooConfig theConfig = thePrecedence.toType( FooConfig.class );
		// ...
	}
	// ...
	public static class FooConfig {
		String message;
		int frequency;
		TemperatureUnit unit;
		int port;
	}
	// ...
}

See also the blog post Dead simple Java application configuration.

How do I get started?

Use the static import syntactic sugar to easily harness the refcoces-properties features.

1
2
import static org.refcodes.properties.PropertiesSugar.*;
// ...

Create some properties and play around with them:

1
2
3
4
5
6
7
8
// ...
PropertiesBuilder theProperties = fromProperties( 
	toProperty( "/user/firstName", "Nolan" ),
	toProperty( "/user/lastName", "Bushnell" ),
	toProperty( "/commodore/user/firstName", "Jack" ),
	toProperty( "/commodore/user/lastName", "Tramiel" )
);
// ...

The content of your properties looks as follows:

1
2
3
4
/user/firstName=Nolan
/user/lastName=Bushnell
/commodore/user/lastName=Tramiel
/commodore/user/firstName=Jack 

The PropertiesBuilder inherits from the Map interface, it is fully compatible with the Java collections framework.

You may have noted that the keys in your properties look like path declarations. This is no coincidence, you can now manipulate your properties using (sub-)paths. See more in the article The canonical model, an ace upon your sleeve. E.g. you may now retrieve the commodore specific properties (the properties below the /commodore path):

1
2
3
// ...
Properties theCommodoreProperties = theProperties.retrieveFrom( "/commodore" );
// ...

This results in your commodore specific properties to look as such:

1
2
/user/lastName=Tramiel
/user/firstName=Jack

There are many more features hidden in the Properties type, just browse the Javadoc.

The Properties type is the read-only super-type of the PropertiesBuilder type. Common functionality produces Properties instances which easily can be converted into a mutable PropertiesBuilder instance as follows:

1
2
3
// ...
PropertiesBuilder theBuilder = toPropertiesBuilder( theCommodoreProperties );
// ...

Snippets of interest

Below find some code snippets which demonstrate the various aspects of using the refcodes-properties artifact (and , if applicable, its offsprings). See also the example source codes of this artifact for further information on the usage of this artifact.

Storing properties

Considering the example from above, storing properties is as easy as this:

1
2
3
// ...
ResourcePropertiesBuilder theResourceProperties = saveToTomlProperties( theProperties, "/some/path/to/my/properties.toml" );
// ...

You can make the library choose a suitable place for you where to save your properties to; being near the launcher (JAR) of your application:

1
2
3
// ...
ResourcePropertiesBuilder theResourceProperties = fileToTomlProperties( theProperties, "properties.toml" );
// ...

See the ConfigLocator on how a suitable location is determined by the library.

Loading properties

Considering the example from above, loading properties back again is as easy as this:

1
2
3
// ...
ResourcePropertiesBuilder theResourceProperties = loadFromTomlProperties( "/some/path/to/my/properties.toml" ):
// ...

You can make the library seek for a suitable properties for you to load; being near the launcher (JAR) of your application:

1
2
3
// ...
ResourcePropertiesBuilder theResourceProperties = seekFromTomlProperties( "properties.toml" );
// ...

See the ConfigLocator on how the properties file is discovered by the library and where it is to be placed.

Properties parsers

There are some notations being supported by the refcodes-properties artifact:

  • Java PROPERTIES based properties
  • INI based properties
  • TOML based properties
  • XML based properties
  • YAML based properties
  • JSON based properties

Please note that some notations use the this keyword to denote the value of the enclosing element as some notations do not support (or make it hard) to assign a value to an element which has child elements (see the blog post refcodes-logger regarding the various notations).

Profiles from properties

A profile is identified by the first level path hierarchy in your originating properties. In the example above, commodore represents a profile specific configuration which we can use to get our profile view:

1
2
3
// ...
Properties theProfile = fromProfile( theProperties, "commodore" );
// ...

We can also specify a property in our properties for the path /runtime/profiles identifying the profiles to be considered (comma separated).

See also the ProfilePropertiesProjection type as well as the ProfilePropertiesDecorator type on more usages.

Schedule reloading of properties

Using the ResourcePropertiesBuilder form above which we attached to a file, we now can schedule a reload of the properties:

1
2
3
// ...
ResourceProperties theScheduled = schedule( theResourceProperties, 5000, ReloadMode.ORPHAN_REMOVAL );
// ...

We actually encapsulate the properties with a schedule decorator which reloads the encapsulated properties accordingly: Reload them each 5000 milliseconds and remove any properties not found in the attached resource (e.g. File) from your properties instance.

Observable properties

You may also listen to any create, update or delete changes applied to your properties. To do so, you must encapsulate your ResourcePropertiesBuilder instance with an observable decorator of type ObservableResourcePropertiesBuilder. This observable fires a sub-type of the PropertyEvent upon updates applied to the properties to each subscriber of those events:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import static org.refcodes.cli.ext.observer.ObservablePropertiesSugar.*;
// ...
PropertiesBuilder theProperties = toPropertiesBuilder();
ObservablePropertiesBuilder theObservable = observe( theProperties );
theObservable.subscribeObserver( aEvent -> {
	if ( aEvent.getAction() == PropertyAction.PROPERTY_UPDATED ) {
		System.out.println( aEvent.getClass().getSimpleName() + " (" + aEvent.getAction() + ") --> " + aEvent.getKey() + " := " + aEvent.getValue() );
	}
} );
theObservable.put( "/user/firstName", "Nolan" );
theObservable.put( "/user/lastName", "Bushnell" );
theObservable.put( "/user/firstName", "Jack" );
theObservable.put( "/user/lastName", "Tramiel" );
theObservable.remove( "/user/firstName" );
theObservable.remove( "/user/lastName" );
// ...

For the example above to work, make sure to include the refcodes-properties-ext-observer dependency (see at the beginning of this article) in your project’s pom.xml.

The lambda being subscribed acts as a listener which is a functional interface of type PropertiesObserver with which you explicitly may listen to the three event types PropertyCreatedEvent, PropertyUpdatedEvent and PropertyDeletedEvent as well as to the super-type PropertyEvent.

Observing just the PropertyEvent type catches all of its sub-types (as we did in the example above).

Multiple properties precedence

We can combine multiple Properties instances behind a composite behaving just like a Properties type:

1
2
3
4
5
6
// ...
Properties theProperties = toPrecedence( 
	fromSystemProperties(), 
	fromEnvironmentVariables(), 
	seekFromJavaProperties( "application.config" ) );
// ...

Above we created a composite containing various properties instances, the first one overrules the second one and the second one overrules the third one when accessing the resulting Properties composite.

Take a look at the SystemProperties as well as at the EnvironmentProperties instances we added: They provide means to access Java ‘s system properties or the operating system’s environment variables within your properties..

Obfuscate your properties

Ever been worried about credentials (passwords, secrets or access keys) residing in your host’s application configuration files as plain text? Read on how to automatically obfuscate your application’s properties residing in configuration files at a system-context’s level?

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

Now you can use the ObfuscationResourcePropertiesBuilder type as follows:

1
2
3
4
5
6
7
8
9
10
11
import static org.refcodes.properties.ext.obfuscation.ObfuscationPropertiesSugar.*;
// ...
public class Foo {
	public Foo() throws IOException, ParseException {
		ResourcePropertiesBuilder properties = seekFromJavaProperties( "application.properties" );
		ObfuscationResourcePropertiesBuilder obfuscateProperties = obfuscate( properties );
		obfuscateProperties.put("secret", "encrypt:Hello world!");
		obfuscateProperties.flush();
	}
	// ...
}

The referenced “application.properties” are now applied with obfuscation, any property value prefixed with “encrypt:” will be encrypted upon flushing, causing it to be prefixed with “decrypt:” and written (to the file system) after being encrypted! Accessing such a property within your application will provide you with the decrypted(!) value transparently.

For a detailed insight see also the blog post Automatically obfuscate your Java application’s configuration!

All in one

The ApplicationProperties and the ApplicationProperties types provide Properties compositions for common everyday application setups, combining many of the above mentioned featured.

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

For a detailed insight see also the blog post All-in-one Java configuration properties at hand!

Under the hood

The canonical model pattern is an ace up your sleeve in order to open your libraries for functionality otherwise to be implemented in a tiresome, error prone and redundant way. As you settle upon a canonical model within your library, your library’s will be able to interact with any existing and yet to be implemented functionality on top of your canonical model, making your bits and pieces work together magically.

The CanonicalMap is the super-type of the Properties related types. Read more in the blog post The canonical model, an ace upon your sleeve.

Examples

For examples and usage, please take a look at the according source code examples here. For examples and usage on the observable extensions, please take a look at the according source code examples here. For examples and usage on the “all in one” runtime extensions, please take a look at the according source code examples here.

Contribution guidelines

  • Report issues
  • Finding bugs
  • Helping fixing bugs
  • Making code and documentation better
  • Enhance the code

Who do I talk to?

  • Siegfried Steiner (steiner@refcodes.org)

Terms and conditions

The REFCODES.ORG group of artifacts is published under some open source licenses; covered by the refcodes-licensing (org.refcodes group) artifact - evident in each artifact in question as of the pom.xml dependency included in such artifact.