Package org.refcodes.configuration

What is this package for?

This artifact provides means to read configuration data from various different locations such as properties from JAR files, file system files or remote HTTP addresses or GIT repositories.

Jump start

 import static org.refcodes.configuration.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 set up?

To get up and running, include the following dependency (without the three dots "...") in your pom.xml:
 
 <dependencies>
 	...
 	<dependency>
 		<artifactId>refcodes-configuration</artifactId>
 		<groupId>org.refcodes</groupId>
 		<version>1.1.9</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:
 
 <dependencies>
 	...
 	<dependency>
 		<artifactId>refcodes-configuration-ext-observer</artifactId>
 		<groupId>org.refcodes</groupId>
 		<version>1.1.9</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.

How do I get started?

Use the *static import* syntactic sugar to easily harness the refcoces-configuration features.
 import static org.refcodes.configuration.PropertiesSugar.*;
 // ...
 
Create some properties and play around with them:
 // ...
 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:
 /user/firstName=Nolan
 /user/lastName=Bushnell
 /commodore/user/lastName=Tramiel
 /commodore/user/firstName=Jack
 
The Properties.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 propertiesusing (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):
 // ...
 Properties theCommodoreProperties = theProperties.retrieveFrom( "/commodore" );
 // ...
 
This results in your commodore specific properties to look as such:
 /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 Properties.PropertiesBuilder type. Common functionality produces Properties instances which easily can be converted into a mutable Properties.PropertiesBuilder instance as follows:
 // ...
 PropertiesBuilder theBuilder = toPropertiesBuilder( theCommodoreProperties );
 // ...
 

Storing properties

Considering the example from above, storing properties is as easy as this:
 // ...
 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:
 // ...
 ResourcePropertiesBuilder theResourceProperties = fileToTomlProperties( theProperties, "properties.toml" );
 // ...
 
See the RuntimeUtility on how a suitable location is determines by the library.

Loading properties

Considering the example from above, loading properties back again is as easy as this:
 // ...
 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:
 // ...
 ResourcePropertiesBuilder theResourceProperties = seekFromTomlProperties( "properties.toml" );
 // ...
 
See the RuntimeUtility on how a suitable location is determines by the library.

Properties parsers

There are some notations being supported by the refcodes-configuration artifact:
  • Java based properties
  • TOML based properties
  • XML based properties
  • YAMLbased properties
  • JSON based properties

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:
 // ...
 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 ResourceProperties.ResourcePropertiesBuilder form above which we attached to a file, we now can schedule a *reload* of the properties:
 // ...
 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.

Observe properties

You may also listen to any create, update or delete changes applied to your properties. To do so, you must encapsulate your ResourceProperties.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:
 
 import static org.refcodes.configuration.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-configuration-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 FunctionalInterface 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:
 // ...
 Properties theProperties = toPrecedence( fromSystemProperties(), fromEnvironmentVariables(), seekFromJavaProperties( "application.config" ) );
 // ...
 
Above we created a composite containing various 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..

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 Unit-Tests. For examples and usage on the observable extensions, please take a look at the according Unit-Tests.