Skip to content

fabriziocucci/yacl4j

Repository files navigation

Maven Central License Travis CI

yacl4j

yacl4j is a configuration library for Java highly inspired by cfg4j and Spring Boot.

Overview

yacl4j:

  • is open source;
  • is easy to use and extend;
  • is heavily based on the well-known and battle-tested Jackson library;
  • supports hierarchical configurations by design;
  • supports configurations in Yaml, Json and Properties format;
  • supports configurations from file, classpath, system properties, environment variables and user-defined sources;
  • supports placeholders resolution.

Dependecy

Set up your favorite dependency management tool:

Maven

<dependencies>
  <dependency>
    <groupId>com.github.fabriziocucci</groupId>
    <artifactId>yacl4j-core</artifactId>
    <version>0.9.2</version>
  </dependency>
</dependencies>

Gradle

dependencies {
  compile group: "com.github.fabriziocucci", name:"yacl4j-core", version: "0.9.2"
}

Usage

yacl4j API is really small and can be easily described with an example:

Configuration definition

interface MyConfiguration {
  
  String getProperty();
  
  MyNestedConfiguration getMyNestedConfiguration();
  
  interface MyNestedConfiguration {
    String getNestedProperty();
  }
  
}

Configuration instantiation

MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder() // #0
    .source().fromFile(new File("some-path/application.yaml"))      // #1
    .source().fromFileOnClasspath("application.yaml")               // #2
    .source().fromFileOnPath("/Users/yacl4j/application.yaml")      // #3
    .source().fromSystemProperties()                                // #4
    .source().fromEnvironmentVariables()                            // #5
    .source(new MyCustomConfigurationSource());                     // #6
    .build(MyConfiguration.class);                                  // #7

In the previous example:

  • at line 0, we are creating a new ConfigurationBuilder;
  • at lines 1-6, we are adding 6 configuration sources, in increasing order of priority;
  • at line 7, we are building the configuration bean based on the MyConfiguration interface;
  • if one property is defined in multiple sources, the source with higher priority win.

Default values? 42

yacl4j is heavily based on Jackson (at this stage) and, unfortunately, Jackson does not support default values in interfaces...yet. Disappointed? A bit. In trouble? No way. You just need to be a little bit more verbose:

public class MyConfiguration {
  
  private String property = "42";
  
  public String getProperty() {
    return property;
  }
  
}

Placeholders support? ${Yes}

yacl4j supports placeholders resolution with the syntax ${relaxed-json-pointer}.

Simple property placeholder

Let's consider an example:

greeting: Hello ${name}
name: yacl4j

The above configuration becomes:

greeting: Hello yacl4j
name: yacl4j

Object property placeholder

Let's consider an example:

greeting: Hello ${person/name}
person:
  name: yacl4j

The above configuration becomes:

greeting: Hello yacl4j
person:
  name: yacl4j

Array element placeholder

Let's consider an example:

greeting: Hello ${persons/0}
persons:
  - yacl4j

The above configuration becomes:

greeting: Hello yacl4j
persons:
  - yacl4j

Object placeholder

Let's consider an example:

object: 
  property: value
myObject: ${object}

The above configuration becomes:

object: 
  property: value
myObject:
  object: 
    property: value

Array placeholder

Let's consider an example:

array: 
  - value
myArray: ${array} 

The above configuration becomes:

array:
  - value
myArray:
  array:
    - value

Other placeholders

For a list of all supported placeholders check the PlaceholderResolver test.

Properties support? Yes, but...

yacl4j supports hierarchical configurations by design. Properties are not really hierarchical, so yacl4j leverages the Json Pointer RFC to transform properties-based configurations into hierarchical ones.

Let's consider an example:

java.runtime.name=Java(TM) SE Runtime Environment
java.runtime.version=1.8.0_77-b03
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
java.vm.vendor=Oracle Corporation
java.vm.version=25.77-b03

The above configuration becomes:

java.runtime.name: Java(TM) SE Runtime Environment
java.runtime.version: 1.8.0_77-b03
java.vm.name: Java HotSpot(TM) 64-Bit Server VM
java.vm.vendor: Oracle Corporation
java.vm.version: 25.77-b03

Mmmmm...that doesn't look really "hierarchical" to me! Let's change a little bit the properties:

java/runtime/name=Java(TM) SE Runtime Environment
java/runtime/version=1.8.0_77-b03
java/vm/name=Java HotSpot(TM) 64-Bit Server VM
java/vm/vendor=Oracle Corporation
java/vm/version=25.77-b03

Hey, did we just replace all '.' with '/' ? Yes, indeed! This is because yacl4j is currently based on the Json Pointer RFC with one simple exception: the leading '/' is optional.

The above configuration becomes:

java:
  runtime:
    name: Java(TM) SE Runtime Environment
    version: 1.8.0_77-b03
  vm:
    name: Java HotSpot(TM) 64-Bit Server VM
    vendor: Oracle Corporation
    version: 25.77-b03

Better, right? So, it's really easy to transform flat Properties into hierarchical configurations.

My suggestion? Just use YAML-based configurations if you can!

Optional configuration sources

In some cases, you may want to specify configuration sources as optional.

Optional file

The classic use case is loading the configuration from one or multiple files that can optionally exist. There are three convenient methods for this:

  • optional File:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
    .optionalSource().fromFile(new File("some-path/application.yaml"))
    .build(MyConfiguration.class);
  • optional file on classpath:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
    .optionalSource().fromFileOnClasspath("application.yaml")
    .build(MyConfiguration.class);
  • optional file on path:
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
    .optionalSource().fromFileOnPath("/Users/yacl4j/application.yaml"))
    .build(MyConfiguration.class);

Optional configuration source

Now suppose you have defined a custom configuration source and you want it to be optional.

You just need to:

  1. throw a ConfigurationSourceNotAvailableException in your ConfigurationSource implementation when appropriate, e.g.
public class MyConfigurationSource implements ConfigurationSource {

  @Override
  public JsonNode getConfiguration() {
    if (isSourceAvailable()) {
      // ...
    } else {
      throw new ConfigurationSourceNotAvailableException();
    }
  }
  
}
  1. use the optionalSource method on the ConfigurationBuilder which accepts a ConfigurationSource as parameter, e.g.
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
    .optionalSource(new MyConfigurationSource())
    .build(MyConfiguration.class);

If, for some reason, you configuration source eagerly checks the availability of the source while it is being instantiated, you can:

  1. throw a ConfigurationSourceNotAvailableException in the constructor or factory of your ConfigurationSource implementation, e.g.
public class MyConfigurationSource implements ConfigurationSource {

  public MyConfigurationSource() {
    if (isSourceAvailable()) {
      // ...
    } else {
      throw new ConfigurationSourceNotAvailableException();
    }
  }
		
}
  1. use the optionalSource method on the ConfigurationBuilder which accepts a Supplier<ConfigurationSource> as parameter, e.g.
MyConfiguration myConfiguration = ConfigurationBuilder.newBuilder()
    .optionalSource(() -> new MyConfigurationSource())
    .build(MyConfiguration.class);

License

yacl4j is released under the Apache 2.0 license.

About

Yet Another Configuration Library For Java

Resources

License

Stars

Watchers

Forks

Packages

No packages published