Skip to main content
CDIEJBMicroProfile Config JSRTutorial

Microprofile CustomConfigSource with Database

By June 18, 2019No Comments

With the 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

Developers can find a good introduction into the Microprofile Config API here. Of course, developers can also implement your own config source. However most of the examples are based on reading custom config values from an existing file, like in the example here.

In this Blog I will show how you can implement a Microprofile ConfigSource based on values read from a Database.

How To Access a Database?

The following example shows how developers 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
       ....
    }
}

However, 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: the injected values will not be available. For example, 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 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 developers can access your entity manager (or whatever you have injected) in the init() method. Because Microprofile Config API still treats your config source as a POJO, your class will be constructed twice – first at the beginning from the Config-API and second 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. 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‘ you 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. 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.

Ralph Soika

Author Ralph Soika

More posts by Ralph Soika

Leave a Reply