Rick

Rick
Rick

Wednesday, April 27, 2016

Konf - Typed Java Config System

Konf - Typed Java Config System

Java configuration library similar in concept to TypeSafe config, but uses full JavaScript, YAML or JSON for configuration.
(See Konf webiste - Java typed config system for JSON, YAML and JavaScript based Java config for more details.).
Uses JavaScript/JSON/YAML as config for Java.
You can use full JavaScript for configuration as long as you define a variable called config that results in a JavaScript object which equates to a Java map.

Using Konf on your project

Konf is in the public maven repo.

Using konf from maven

<dependency>
    <groupId>io.advantageous.konf</groupId>
    <artifactId>konf</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

Using konf from gradle

compile 'io.advantageous.konf:konf:1.0.0.RELEASE'

Using konf from scala sbt

libraryDependencies += "io.advantageous.konf" % "konf" % "1.0.0.RC1"

Using konf from clojure leiningen

[io.advantageous.konf/konf "1.0.0.RC1"]
Here is an example config for JavaScript.
Konf expects the conf variable to be set to a JavaScript object with properties.

JavaScript based configuration for Java

var config = {

  myUri: uri("http://host:9000/path?foo=bar"),

  someKey: {
    nestedKey: 234,
    other: "this text"
  }

};
The interface for Konf is Config. You can get a sub Config from Config (getConfig(path)). The pathis always in dot notation (this.that.foo.bar). You can also use:
  • getInt(path)
  • getLong(path)
  • getDouble(path)
  • getString(path)
  • getStringList(path) gets a list of strings
  • getConfig(path) gets a sub-config.
  • getMap(path) gets a map which is a sub-config.
  • getConfigList(path) gets a list of configs at the location specified.
getMap works with JavaScript objects. getStringList and getConfigList works with JavaScript array of string and a JavaScript array of JavaScript objects.
Not you get an exception if the path requested is not found. Use hasPath(path) if you think the config path might be missing.
Here is the full interface.

Config interface

public interface Config {

    /** Get string at location. */
    String getString(String path);

    /** Checks to see if config has the path specified. */
    boolean hasPath(String path);

    /** Get int at location. */
    int getInt(String path);

    /** Get float at location. */
    float getFloat(String path);

    /** Get double at location. */
    double getDouble(String path);

    /** Get long at location. */
    long getLong(String path);

    /** Get list of strings at location. */
    List<String> getStringList(String path);

    /** Get map at location. */
    Map<String, Object> getMap(String path);

    /** Get a sub-config at location. */
    Config getConfig(String path);

    /** Get list of sub-configs at location. */
    List<Config> getConfigList(String path);

    /**  Get a single POJO out of config at path. */
    <T> T get(String path, Class<T> type);

    /**  Get a list of POJOs. */
    <T> List<T> getList(String path, Class<T> componentType);
}
The getX methods work like you would expect. Given this config file.

Sample config for testing and showing how config works

var config = {

  myUri: uri("http://host:9000/path?foo=bar"),

  someKey: {
    nestedKey: 234,
    other: "this text"
  },

  int1: 1,
  float1: 1.0,
  double1: 1.0,
  long1: 1,
  string1: "rick",
  stringList: ['Foo', 'Bar'],
  configInner: {
    int2: 2,
    float2: 2.0
  },
  uri: uri("http://localhost:8080/foo"),
  myClass: "java.lang.Object",
  myURI: "http://localhost:8080/foo",
  employee: {"id": 123, "name": "Geoff"},
  employees: [
    {id: 123, "name": "Geoff"},
    {id: 456, "name": "Rick"},
    {id: 789, 'name': "Paul"}
  ]
};
We can do the following operations.
First we load the config.

Loading the config.

    private Config config;

    @Before
    public void setUp() throws Exception {
        config = ConfigLoader.load("test-config.js");
    }
Then we show reading basic types with the config object using getX.

Reading basic types from config

    @Test
    public void testSimple() throws Exception {

        //getInt
        assertEquals(1, config.getInt("int1"));

        //getStringList
        assertEquals(asList("Foo", "Bar"), 
               config.getStringList("stringList"));

        //getString       
        assertEquals("rick", config.getString("string1"));

        //getDouble
        assertEquals(1.0, config.getDouble("double1"), 0.001);

        //getLong
        assertEquals(1L, config.getLong("long1"));

        //getFloat
        assertEquals(1.0f, config.getFloat("float1"), 0.001);

        //Basic JDK value types are supported like class.
        assertEquals(Object.class, config.get("myClass", Class.class));

        //Basic JDK value types are supported like URI.
        assertEquals(URI.create("http://localhost:8080/foo"), 
                config.get("myURI", URI.class));

        assertEquals(URI.create("http://localhost:8080/foo"), 
                config.get("uri", URI.class));

    }
You can work with nested properties as well.

Reading a nested config from the config

    @Test
    public void testGetConfig() throws Exception {
        //Read nested config.
        final Config configInner = config.getConfig("configInner");
        assertEquals(2, configInner.getInt("int2"));
        assertEquals(2.0f, configInner.getFloat("float2"), 0.001);
    }

    @Test
    public void testGetMap() throws Exception {
        //Read nested config as a Java map.
        final Map<String, Object> map = config.getMap("configInner");
        assertEquals(2, (int) map.get("int2"));
        assertEquals(2.0f, (double) map.get("float2"), 0.001);
    }
You can read deeply nested config items as well by specifying the property path using dot notation.

Reading nested properties with dot notation from config

    @Test
    public void testSimplePath() throws Exception {

        assertTrue(config.hasPath("configInner.int2"));
        assertFalse(config.hasPath("configInner.foo.bar"));
        assertEquals(2, config.getInt("configInner.int2"));
        assertEquals(2.0f, config.getFloat("configInner.float2"), 0.001);
    }
You can also read POJOs directly out of the config file.

Reading a pojo directly out of the config file

    @Test
    public void testReadClass() throws Exception {
        final Employee employee = config.get("employee", Employee.class);
        assertEquals("Geoff", employee.name);
        assertEquals("123", employee.id);
    }
You can read a list of POJOs at once.

Reading a pojo list directly out of the config file

    @Test
    public void testReadListOfClass() throws Exception {
        final List<Employee> employees = config.getList("employees", Employee.class);
        assertEquals("Geoff", employees.get(0).name);
        assertEquals("123", employees.get(0).id);
    }
You can also read a list of config objects out of the config as well.

Reading a config list directly out of the config file

    @Test
    public void testReadListOfConfig() throws Exception {
        final List<Config> employees = config.getConfigList("employees");
        assertEquals("Geoff", employees.get(0).getString("name"));
        assertEquals("123", employees.get(0).getString("id"));
    }
## Using Config with YAML
First include a YAML to object parser like YAML Beans or a library like this.
#### Example YAML
   name: Nathan Sweet
     age: 28
     address: 4011 16th Ave S
     phone numbers:
      - name: Home
        number: 206-555-5138
      - name: Work
        number: 425-555-2306

Using Konf with YAML

//Use YamlReader to load YAML file.
YamlReader reader = new YamlReader(new FileReader("contact.yml"));

//Convert object read from YAML into Konf config
Config config = ConfigLoader.loadFromObject(reader.read());

//Now you have strongly typed access to fields
String address = config.getString("address");
You can also read Pojos from anywhere in the YAML file as well as sub configs.

You can also use Konf with JSON using Boon

See Boon JSON parser project, and Boon in five minutes

Using Konf with JSON

ObjectMapper mapper =  JsonFactory.create();


/* Convert object read from YAML into Konf config.
  'src' can be File, InputStream, Reader, String. */
Config config = ConfigLoader.loadFromObject(mapper.fromJson(src));


//Now you have strongly typed access to fields
String address = config.getString("address");
Boon supports LAX JSON (Json with comments, and you do not need to quote the field).
If you like our configuration project, please try our Reactive Java project or our Actor based microservices lib.
Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training