YAML configuration for DeltaSpike


YAML is a nice and readable format for configuration allowing to set your properties hierarchically making them organized and readable.

Let see how to use this format with Apache DeltaSpike @ConfigProperty injections!

The idea is to create a ConfigSource reading a particular yaml file and converting it to a flat Map.

Tip: if you want it a bit more dynamic you can add it using ConfigResolver API and read the set of files to take into account from another property.

For our case we’ll check ${openejb.base}/conf/app.yml, ${app.base}/conf/app.yml, ${openejb.base}/etc/app.yml and ${app.base}/etc/app.yml and take the first file we find but feel free to use any other strategy.

Once we find the file we’ll use snakeyaml to read it and then we’ll just convert the Map snakeyaml will give us to a flat map of string keys and values:

import org.apache.deltaspike.core.impl.config.MapConfigSource;
import org.yaml.snakeyaml.Yaml;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class AppYamlConfigSource extends MapConfigSource {
    public JwpYamlConfigSource() {
        super(
            Stream.of("app.base", "openejb.base")
                .map(System::getProperty)
                .filter(s -> s != null)
                .map(File::new)
                .filter(File::isDirectory)
                .flatMap(f -> Stream.of(new File(f, "conf/app.yml"), new File(f, "etc/app.yml")))
                .filter(File::isFile)
                .findFirst()
                .map(file -> {
                    final Yaml yaml = new Yaml();
                    final Map<String, String> map = new HashMap<>();
                    try (final Reader reader = new BufferedReader(new FileReader(file))) {
                        for (final Object object : yaml.loadAll(reader)) {
                            if (object == null || !Map.class.isInstance(object)) {
                                continue;
                            }

                            enrich(map, Map.class.cast(object), "");
                        }
                    } catch (final IOException e) {
                        throw new IllegalArgumentException(e);
                    }
                    return map;
                }).orElse(new HashMap<>()));
    }

    @Override
    public String getConfigName() {
        return "app";
    }

    private static void enrich(final Map<String, String> map, final Map<?, ?> object, final String s) {
        for (final Map.Entry<?, ?> entry : object.entrySet()) {
            final String key = (s.isEmpty() ? s : (s + '.')) + entry.getKey();
            final Object val = entry.getValue();
            if (String.class.isInstance(val)) {
                map.put(key, String.valueOf(val));
            } else if (Map.class.isInstance(val)) {
                enrich(map, Map.class.cast(val), key);
            } else if (Collection.class.isInstance(val)) {
                final StringBuilder builder = new StringBuilder();
                for (final Object o : Collection.class.cast(val)) {
                    builder.append(String.valueOf(o)).append(',');
                }
                if (builder.length() > 0) {
                    builder.setLength(builder.length() - 1);
                }
                map.put(key, builder.toString());
            } else {
                throw new IllegalArgumentException("type not supported: " + val);
            }
        }
    }
}

Once you added this class to your project don’t forget to add a META-INF/services/org.apache.deltaspike.core.spi.config.ConfigSource containing the fully qualified name of AppYamlConfigSource to let DeltaSpike pick it up automatically.

And that’s it! Now if you use these injections:

@Inject
@ConfigProperty(name = "app.remote.service1.url")
String url1;


@Inject
@ConfigProperty(name = "app.remote.service1.retries")
Integer retries1;

you can set it up in your app.yml with (I supposed we have 2 services):

app:
  remote:
    service1:
      url: 'http://myservice1.com/endpoint1'
      retries: 3
    service2:
      url: 'http://myservice2.com/endpoint2'
      retries: 5
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s