Simple Kotlin and Java configuration library with recursive placeholders resolution and zero magic!

Related tags

Testing properlty
Overview

Properlty - Simple configuration library with placeholders resolution and no magic!

Properlty provides a simple way to configure an application from multiple sources — built in resources, system properties, property files, environment variables, and whatever else you like.

Some features:

  • Recursive placeholders resolution
  • Only 30Kb and no external dependencies
  • Java and Kotlin versions

Getting Started

  1. To get started, add Properlty dependency to your project.

For Kotlin:

    <dependency>
        <groupId>com.ufoscout.properlty</groupId>
        <artifactId>properlty-kotlin</artifactId>
        <version>1.8.1</version>
    </dependency>

For Java:

    <dependency>
        <groupId>com.ufoscout.properlty</groupId>
        <artifactId>properlty</artifactId>
        <version>1.9.0</version>
    </dependency>

WARN: use one OR the other. Do not import both the dependencies!

  1. Define some properties. You can use placeholders, for example, in config.properties:
    server.port=9090
    server.host=127.0.0.1
    server.url=http://${server.host}:${server.port}/
  1. Build a Properlty object that loads properties:

Kotlin:

    val properlty = Properlty.builder()
            .add("classpath:config.properties") // loads from the classpath
            .build()

Java:

    final Properlty properlty = Properlty.builder()
            .add("classpath:config.properties")  // loads from the classpath
            .build();
  1. Look up properties by key:

Kotlin:

    val port: Int = properlty.getInt("server.port", 8080); // returns 9090
    val serverUrl = properlty["server.url"] // returns http://127.0.0.1:9090/
    val defaultVal = properlty["unknownKey", "defaultValue"] // returns defaultValue

Java:

    int port = properlty.getInt("server.port", 8080); // returns 9090
    
    // the Java API uses Optional for methods that could not produce a valid result.
    // In this case the Optional contains the Strind 'http://127.0.0.1:9090/' 
    Optional<String> serverUrl = properlty.get("server.url") 
    String defaultVal = properlty.get("unknownKey", "defaultValue") // returns defaultValue

(All examples are in Kotlin from now on; btw, Java code is exactly the same)

Readers

In Properlty a "reader" is whatever source of properties. By default, Properlty offers readers to load properties from:

  • Java property files in the file system and in the classpath
  • Java system properties
  • Environment variables
  • Programmatically typed properties

Custom properties readers can be created implementing the com.ufoscout.properlty.reader.Reader interface.

Placeholders resolution

Properlty is able to resolve placeholders recursively. For example:

fileOne.properties:

    server.url=http://${${environment}.server.host}:${server.port}
    server.port=8080

fileTwo.properties:

    environment=PROD
    PROD.server.host=10.10.10.10
    DEV.server.host=127.0.0.1
    val properlty = Properlty.builder()
            .add("./fileOne.properties") // loads from the file system
            .add("./fileTwo.properties") // loads from the file system
            .build()
            
    println(properlty["server.url"]) // this prints 'http://10.10.10.10:8080'

By default ${ and } delimiters are used. Custom delimiters can be easily defined:

    val properlty = Properlty.builder()
            .delimiters("%(", ")") // using %( and ) as delimiters
            .add( bla bla bla)

Default Values

Placeholders can have default values which are used if the key is not otherwise provided. Example:

config.properties:

# the default value "8080" is used if 'PORT_NUMBER' is not provided
server.port=${PORT_NUMBER:8080}
    
# default is 127.0.0.1
server.ip=${IP:127.0.0.1}

server.url=${server.ip}/${server.port}

The default separator for the default value is ":". A custom value can be set through the 'defaultValueSeparator()' method of Properlty.builder().

Case Sensitive settings

By default keys and placeholders are case sensitive. The default behavior can be modified with the caseSensitive() builder method:

    server.PORT=9090
    server.host=127.0.0.1
    server.url=http://${SerVeR.host}:${SERVER.port}/

Kotlin:

    val properlty = Properlty.builder()
            .caseSensitive(false)
            .add("classpath:config.properties") // loads from the classpath
            .build()
    val serverUrl = properlty["server.url"] // returns http://127.0.0.1:9090/

Case insensitive can simplify key overriding through environment variables.

Readers priority -> Last one wins

Properties defined in later readers will override properties defined earlier readers, in case of overlapping keys. Hence, make sure that the most specific readers are the last ones in the given list of locations.

For example:

fileOne.properties:

    server.url=urlFromOne

fileTwo.properties:

    server.url=urlFromTwo
    val properlty = Properlty.builder()
            .add("classpath:fileOne.properties") // loads from the classpath
            .add("file:./fileTwo.properties") // loads from the file system
            .add(SystemPropertiesReader()) // loads the Java system properties
            .build()
    
    // this prints 'urlFromTwo'
    println(properlty["server.url"])

BTW, due to the fact that we used SystemPropertiesReader() as last reader, if the "server.url" system property is specified at runtime, it will override the other values.

In addition, it is possible to specify a custom priority:

    val properlty = Properlty.builder()

            // load the properties from the file system and specify their priority
            .add(resourcePath = "file:./fileTwo.properties", priority = Default.HIGHEST_PRIORITY)
            
            .add("classpath:fileOne.properties") // loads from the classpath
            .add(SystemPropertiesReader()) // loads the Java system properties            
            .build()
            
    // this prints 'urlFromTwo'
    println(properlty["server.url"]) 

The default priority is 100. The highest priority is 0. As usual, when more readers have the same priority, the last one wins in case of overlapping keys.

Real life example

A typical real life configuration would look like:

    val properlty = Properlty.builder()

            // case insensitive behavior simplifies matching of environment variables, especially in Linux systems
            .caseSensitive(false)

            // load a resource from the classpath
            .add("classpath:default.properties")
            
             // load a file from the file system
            .add("file:./config/config.properties")
            
            // Add a not mandatory reader, resource not found exceptions are ignored.
            // Here I am adding properties present only during testing.
            .add(resourcePath = "classpath:test.properties", ignoreNotFound = true)

             // load the Environment variables and convert their keys
             // from JAVA_HOME=XXX to JAVA.HOME=XXX
             // This could be desired to override default properties
            .add(EnvironmentVariablesReader().replace("_", "."))
            
             // Load the Java system properties. They override the Environment variables.
            .add(SystemPropertiesReader())
            
            // build the properlty object 
            .build()

Properlty API

Properlty has a straightforward API that hopefully does not need detailed documentation.

Some examples:

    val properlty = Properlty.builder()
            .add("classpath:test.properties")
            .build()

    // get a String. "defaultValue" is returned if the key is not found
    val aString = properlty["key", "defaultValue"]

    // get an array from the comma separated tokens of the property value
    val anArray = properlty.getArray("key", ",")

    // get an Enum
    val anEnum = properlty.getEnum<NeedSomebodyToLoveEnum>("key")

    // get a Long
    val aLong = properlty.getLong("key")

    // get a Long or default value
    val anotherLong = properlty.getLong("key", 10L)
    
    // get a BigDecimal
    val aBigDecimal = properlty.getBigDecimal("key")
    
    // or get a BigDecimal by applying a transformation function to the returned String value
    val anotherBigDecimal = properlty["key", { BigDecimal(it) }]

    // get a list of BigDecimal. The property value is split in tokens using the default list 
    // separator (a comma) then the transformation function is applied to each token
    val aListOfBigDecimals = properlty.getList("key") {BigDecimal(it)} 
You might also like...
Strikt is an assertion library for Kotlin intended for use with a test runner such as JUnit, Minutest, Spek, or KotlinTest.

Strikt is an assertion library for Kotlin intended for use with a test runner such as JUnit, Minutest, Spek, or KotlinTest.

A Kotlin Android library for heuristics evasion that prevents your code from being tested.

EvadeMe An Android library for heuristics evasion that prevents your code from being tested. User Instructions Add the maven repository to your projec

Kotlin wrapper for React Test Renderer, which can be used to unit test React components in a Kotlin/JS project.

Kotlin API for React Test Renderer Kotlin wrapper for React Test Renderer, which can be used to unit test React components in a Kotlin/JS project. How

Library to simplify and speed up the creation and work with adapters with payload.

Novalles Library to simplify and speed up the creation and work with adapters with payload. How to use Annotate your UI model with UIModel Annotation.

Android library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests.
Android library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests.

Green Coffee Green Coffee is a library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests using the

Linkester is an Android library that aims to help Android developers test their deep links implementation.
Linkester is an Android library that aims to help Android developers test their deep links implementation.

Linkester Linkester is an Android library that aims to help Android developers test their deep links implementation. The idea is to have a new launche

Jitpack Library Tester

jitpack-library-test Repository for testing build from jitpack.io Red : Failed Green : Success / Pass Colaborator Very open to anyone, I'll write your

Turbine is a small testing library for kotlinx.coroutines Flow.

A small testing library for kotlinx.coroutines Flow

A library that makes it easier to write high quality automated acceptance tests

Getting started with Serenity and Cucumber Serenity BDD is a library that makes it easier to write high quality automated acceptance tests, with power

Comments
  • UnresolvablePlaceholdersException thrown inappropriately

    UnresolvablePlaceholdersException thrown inappropriately

    Properlty's getBoolean(String, Boolean) behaves unexpectedly and, I would argue, undesirably.

    The behavior

    Rather than returning null as implied by this block...

    fun getBoolean(key: String): Boolean? {
        val value = get(key)
        return if (value == null)
            value
      // elided
    }
    
    

    An exception is thrown, which confused me. So I investigated.

    Investigation

    The stack trace points to this block of code in ReplacerDecoratorReader.java:

    if (!valuesToBeReplacedMap.isEmpty() && !ignoreUnresolvablePlaceholders) {
    	final StringBuilder message = new StringBuilder("Unresolvable placeholders: \n");
    	valuesToBeReplacedMap.forEach((key, value) -> {
    		message.append("key: [");
    		message.append(key);
    		message.append("] value: [");
    		message.append(value.getValue());
    		message.append("]\n");
    	});
    	throw new UnresolvablePlaceholdersException(message.toString());
    }
    
    

    Just looking at the Kotlin code, I would expect to get back a null for an "unresolvablePlaceholder" (after setting the ignoreUnresolvablePlaceholders, of course).

    Conclusion

    I have a application.properties file that I am loading, but I don't want to declare the default in that file. At my company, changing configuration files and change source files has the same turn around time. Also, we have environment variable loaded in certain environments and I wanted to leave those out for local development. As a result, I would prefer the particular property that I am choosing to leave "unresolved" return null and defer to the code-level default.

    For visuals:

    object Configuration {
        val properlty: Properlty.builder()
                                  .add("classpath:application.properties")
                                  .build()
    
        val myBoolean = properlty.getBoolean("my.boolean", defaultValue = true) // this throws the exception
    }
    
    

    I can submit a PR, but I wanted to discuss first. Given the lack of documentation, I wonder if there's a design guideline I'm simply misunderstanding.

    opened by stephenjfox 3
  • Use 'Last one wins' priority for readers

    Use 'Last one wins' priority for readers

    From the spring javadoc about properties files order:

    Properties defined in later files will override properties defined earlier files, in case of overlapping keys. Hence, make sure that the most specific files are the last ones in the given list of locations.

    I would use the same strategy as it seems more logical when thinking in terms of "override"

    opened by ufoscout 0
Owner
Francesco Cinà
Francesco Cinà
PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.

Writing unit tests can be hard and sometimes good design has to be sacrificed for the sole purpose of testability. Often testability corresponds to go

PowerMock 3.9k Jan 5, 2023
A programmer-oriented testing framework for Java.

JUnit 4 JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks. For more infor

JUnit 8.4k Dec 28, 2022
PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.

Writing unit tests can be hard and sometimes good design has to be sacrificed for the sole purpose of testability. Often testability corresponds to go

PowerMock 3.9k Jan 2, 2023
Most popular Mocking framework for unit tests written in Java

Most popular mocking framework for Java Current version is 3.x Still on Mockito 1.x? See what's new in Mockito 2! Mockito 3 does not introduce any bre

mockito 13.6k Jan 4, 2023
A simple project to help developers in writing their unit tests in Android Platform.

AndroidUnitTesting A simple project to help developers in writing their unit tests in Android Platform. This is not a multi-module project, but has th

Bruno Gabriel dos Santos 4 Nov 10, 2021
Project for testing intern candidate simple level

RickAndMorty-TestTask Тестовый проект 'Гайд по мультфильму Рик и Морти' для практикантов начального и продвинутого уровня. Структура проекта Структура

null 0 Nov 18, 2021
Simple, lightweight, modular components to help conjure your app architecture

Magic Simple, lightweight, modular components and utilities to help conjure your app architecture. Built with Kotlin coroutines to provide flexible as

TeraThought 0 Jan 9, 2022
Very simple Morse API text translator. Mainly to do some testing with jitpack, API development etc.

Very simple Morse text translator API. Far from finish or even being reliable. Use for single sentence. Mainly to test github dependency for Android

Piotr Dymala 0 Dec 30, 2021
A multiplatform assertion library for Kotlin

Atrium is an open-source multiplatform assertion library for Kotlin with support for JVM, JS and Android. It is designed to support multiple APIs, dif

Robert Stoll 439 Dec 29, 2022
mocking library for Kotlin

Kotlin Academy articles Check the series of articles "Mocking is not rocket science" at Kt. Academy describing MockK from the very basics of mocking u

MockK 4.8k Jan 3, 2023