Microprofile CustomConfigSource with Database

With the new Microprofile-Config API there is a new and easy way to deal with configuration properties in an application. The Microprofile-Config API allows you to access config and property values form different sources like:

  • System.getProperties() (ordinal=400)
  • System.getenv() (ordinal=300)
  • all META-INF/microprofile-config.properties files

You can find a good introduction into the Microprofile Config API here. And of course your can also implement your own config source. But most of the examples are based on reading custom config values from am existing file, like in the example here. Now in this Blog I will show how you can implement a Micorprofile ConfigSource based on values read from a Database.

How To Access a Database?

In the following example I will explain how you can implement a custom ConfigSource reading values from a JPA Datasource or a EJB Service. At the first glance it looks quite easy to inject an external resource or a service to access custom config values provided by your application:

public class MyConfigSource implements ConfigSource {

    @PersistenceContext(unitName = ".....")
    private EntityManager manager;

    @Override
    public String getValue(String key) {
       .....
    }
    @Override
    public Map<String, String> getProperties() {
       // read data form JPA Entity manager
       ....
    }
}

But there is a problem with this direct approach. If you try to inject a JPA Entity Manager or just another EJB into your CustomConfigSource you will note that your values are not available as expected because the entity manager will be null.

The reason is, that in Microprofile-Config all ConfigSources are treated as POJOs. And so the injected values will not be available. There is a good reason why a ConfigSource is handled as a POJO: Imagine another CDI bean may expect a config value to be injected during it startup phase. If your custom ConfigSource had itself dependencies on CDI’s you could get into a startup looping issue. So how can we solve this problem?

The solution I will explain here is – as often in Java Enterprise – quite simple. To make sure your EntityManager is already injected you can annotate your custom ConfigSource with @Startup and implement a method annotated with @PostConstruct:

@Startup
@Singleton
public class MyConfigSource implements ConfigSource {
    @PersistenceContext(unitName = ".....")
    private EntityManager manager;

    @PostConstruct
    void init() {
        // load your data from teh JPA source or EJB
        ....
    }
   ...
}

Now you can access your entity manager (or whatever you have injected) in the init() method. Because of the MIícroprofile Config API still treats your config source as a POJO your class will be constructed twice – once at the beginning from the Config-API and later from the CDI implementation on @PostConstruct. So how can we provide the values for both instances?

The solution is quite simple. Because your configSource is a POJO you can use static member variables to store your values. And in this way each instance of your custom source will see the same values. With the @PostConstruct annotation we will provide the values in a kind of lazy loading. Take a look at the full example:

@Startup
@Singleton
public class MyConfigSource implements ConfigSource {

    public static final String NAME = "MyConfigSource";
    public static Map<String, String> properties = null; // note to use static here!

    @PersistenceContext(unitName = ".....")
    private EntityManager manager;

    @PostConstruct
    void init() {
        // load your data from teh JPA source or EJB
        ....
        // override the static property map..
        properties.put(....)
    }

    @Override
    public int getOrdinal() {
        return 890;
    }

    @Override
    public String getValue(String key) {
        if (properties != null) {
            return properties.get(key);
        } else {
            return null;
        }
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public Map<String, String> getProperties() {
        return properties;
    }

}

With the static member variable ‘properties‘ we can overload the values from the already constructed ConfigSource. So any instance of our ConfigSource shares the same values. The values are loaded at a later point of time. For that reason our configSource will not show values during the startup phase. This means if you have another CDI bean you can not access those values during @PostConstruct. But the values will be available at runtime in any case.

With the disadvantage of the lazy loading mechanism the solution is quite simple and easy to implement. Of course you can also use a JNDI Lookup to get the data form a data source without the trick of lazy loading. But the solution shown here allows you to access not only data sources but also any kind of CDI’s. We use this solution in our open source project ‘Imixs-Workflow‘ which is based in its new version on Microprofile 2.2.

In this way I hope you will get started with your own Microprofile ConfigSource soon.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.