Search

Dark theme | Light theme

July 2, 2016

Spring Sweets: Using Groovy Configuration As PropertySource

We have many ways to provide configuration properties to a Spring (Boot) application. We can add our own custom configuration properties format. For example we can use Groovy's ConfigObject object to set configuration properties. We need to read a configuration file using ConfigSlurper and make it available as a property source for Spring. We need to implement two classes and add configuration file to support a Groovy configuration file in a Spring application.

First we need to write a class that extends the PropertySource in the package org.springframework.core.env. This class has methods to get property values based on a given key. There are already some subclasses for specific property sources. There is for example also a MapPropertySource class. We will extend that class for our implementation, because we can pass our flattened ConfigObject and rely on all existing functionality of the MapPropertySource class:

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySource.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import org.springframework.core.env.MapPropertySource;

/**
 * Property source that supports {@link ConfigObject}. The {@link ConfigObject}
 * is flattened and all functionality is delegated to the
 * {@link MapPropertySource}.
 */
public class ConfigObjectPropertySource extends MapPropertySource {
    
    public ConfigObjectPropertySource(
            final String name, 
            final ConfigObject config) {
        
        super(name, config.flatten());
    }
    
}

Next we must implement a class that loads the configuration file. The configuration file must be parsed and we must our ConfigObjectPropertySource so we can use it in our Spring context. We need to implement the PropertySourceLoader interface, where we can define the file extensions the loader must be applied for. Also we override the load method to load the actual file. In our example we also add some extra binding variables we can use when we define our Groovy configuration file.

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySourceLoader.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import groovy.util.ConfigSlurper;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ConfigObjectPropertySourceLoader implements PropertySourceLoader {

    /**
     * Allowed Groovy file extensions for configuration files.
     *
     * @return List of extensions for Groovy configuration files.
     */
    @Override
    public String[] getFileExtensions() {
        return new String[]{"groovy", "gvy", "gy", "gsh"};
    }

    /**
     * Load Groovy configuration file with {@link ConfigSlurper}.
     */
    @Override
    public PropertySource<?> load(
            final String name,
            final Resource resource,
            final String profile) throws IOException {

        if (isConfigSlurperAvailable()) {
            final ConfigSlurper configSlurper = profile != null ?
                    new ConfigSlurper(profile) :
                    new ConfigSlurper();

            // Add some extra information that is accessible
            // in the Groovy configuration file.
            configSlurper.setBinding(createBinding(profile));
            final ConfigObject config = configSlurper.parse(resource.getURL());

            // Return ConfigObjectPropertySource if configuration
            // has key/value pairs.
            return config.isEmpty() ?
                    null :
                    new ConfigObjectPropertySource(name, config);
        }

        return null;
    }

    private boolean isConfigSlurperAvailable() {
        return ClassUtils.isPresent("groovy.util.ConfigSlurper", null);
    }

    private Map<String, Object> createBinding(final String profile) {
        final Map<String, Object> bindings = new HashMap<>();
        bindings.put("userHome", System.getProperty("user.home"));
        bindings.put("appDir", System.getProperty("user.dir"));
        bindings.put("springProfile", profile);
        return bindings;
    }

}

Finally we need to add our ConfigObjectPropertySourceLoader to a Spring configuration file, so it used in our Spring application. We need to create the file spring.factories in the META-INF directory with the following contents:

# File: src/main/resources/META-INF/spring.factories
org.springframework.boot.env.PropertySourceLoader=\
  mrhaki.spring.configobject.ConfigObjectPropertySourceLoader

Now when we create a Spring application with all our classes and configuration file in the classpath we can provide an application.groovy file in the root of the classpath:

// File: src/main/resources/application.groovy
app {
    // Using binding property appDir,
    // which is added to the ConfigSlurper 
    // in ConfigObjectPropertySourceLoader.
    startDir = appDir

    // Configuration hierarchy.
    message {
        text = "Text from Groovy configuration!"
    }
}

environments {
    // When starting application with 
    // -Dspring.profiles.active=blog this
    // environment configuration is used.
    blog {
        app {
            message {
                text = "Groovy Goodness"
            }
        }
    }
}

Written with Spring Boot 1.3.5.RELEASE