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
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):
// ...
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
YAML
based 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
.