In the previous blog post “An Overview of MicroProfile Configuration” we have seen how to start using MicroProfile Configuration and covered the most common cases where it can be used. In today’s blog post, we are going to show you one of the advanced features of MicroProfile Configuration: the ability to add your own ConfigSource.
To refresh your memory, MicroProfile Config already ships with a few ConfigSources by default: microprofile-config.properties
file, system properties, and environment variables.
What if your source of Configuration is somewhere else? Maybe a database or an API? Good news! MicroProfile Configuration allows you to create your own ConfigSources and add them to the chain, so they can also load configuration values.
Implement your own ConfigSource
To create your own ConfigSource, you just need to create a regular Java POJO and implement the interface org.eclipse.microprofile.config.spi.ConfigSource
. The loading of the new ConfigSource type is done via the Java Service Loader.
There are three methods that you need to implement with org.eclipse.microprofile.config.spi.ConfigSource
:
String getValue(String propertyName);
This is the main method you need to implement. Here you need to write your logic to read the configuration value from the location you have it stored using the propertyName, which is the key, and just return the value
Map<String, String> getProperties();
This is a convenience method that returns the full list of available configurations values as a Map
in a key-value pair format from the original source of configurations, for instance, a Database, an XML file, etc. Please, take into consideration that this method does not load and cache the configurations. If you want to do that, you need to do it in the ConfigSource
constructor.
String getName();
This is just the name of the ConfigSource that you want to use for logging and analysis of the configured values.
Additionally, there are two more default methods that you can override:
default int getOrdinal()
Override this to set the order in which you want the ConfigSource to load the configuration value. The default is 100
. ConfigSources with the highest ordinal are queried when loading the configuration values. The System Properties configuration source is set at 400
, Environment Variables at 300
and microprofile-config.properties file is also set at 100
. If multiple ConfigSources share the same ordinal, then they will be sorted by name.
default Set getPropertyNames()
Returns all the ConfigSource properties (or keys) without evaluating the configuration values. The default implementation retrieves the keys from the getProperties() method.
The Database ConfigSource Sample
Now that we are familiarized with the methods that we need to implement, let’s try to create a custom ConfigSource
that reads values from a database. The database has a table named CONFIGURATIONS
with two columns, NAME
and VALUE
.
public class DatabaseConfigSource implements ConfigSource {
private DataSource dataSource;
public DatabaseConfigSource() {
try {
dataSource = (DataSource) new InitialContext().lookup("openejb:Resource/config-source-database");
} catch (final NamingException e) {
throw new IllegalStateException(e);
}
}
@Override
public Map<String, String> getProperties() {
final Map<String, String> properties = new HashMap<>();
try {
final Connection connection = dataSource.getConnection();
final PreparedStatement query = connection.prepareStatement("SELECT NAME, VALUE FROM CONFIGURATIONS");
final ResultSet names = query.executeQuery();
while (names.next()) {
properties.put(names.getString(0), names.getString(1));
}
DbUtils.closeQuietly(names);
DbUtils.closeQuietly(query);
DbUtils.closeQuietly(connection);
} catch (final SQLException e) {
e.printStackTrace();
}
return properties;
}
@Override
public String getValue(final String propertyName) {
try {
final Connection connection = dataSource.getConnection();
final PreparedStatement query =
connection.prepareStatement("SELECT VALUE FROM CONFIGURATIONS WHERE NAME = ?");
query.setString(1, propertyName);
final ResultSet value = query.executeQuery();
if (value.next()) {
return value.getString(1);
}
DbUtils.closeQuietly(value);
DbUtils.closeQuietly(query);
DbUtils.closeQuietly(connection);
} catch (final SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public String getName() {
return DatabaseConfigSource.class.getSimpleName();
}
}
Notice that we grab a Datasource
instance and then we just use that Datasource
to perform the required queries to our table. In this case, we are not caching the database results, so everytime you change something in the database and a config value is requested, you will get the new updated value.
Register your ConfigSource
Finally, for your ConfigSource to work properly, you need to register it in the Java Service Loader. Add a file named org.eclipse.microprofile.config.spi.ConfigSource
in the folder /META-INF/services/
and include the fully-qualified class name of the custom implementation in the file:
/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource contents:
#
org.tomitribe.microprofile.samples.config.source.database.DatabaseConfigSource
Try it out!
That is it! You have learned how to implement your own custom MicroProfile Configuration ConfigSource.
For your convenience, you can find this sample here:
https://github.com/apache/tomee/tree/master/examples/mp-config-source-database
Instructions to run the sample can be found in the project README file.
Final Words
ConfigSources are a convenient way to load configuration values that your application can use to customize itself. While MicroProfile Config already ships with a few ConfigSources to get you started, more complex applications may need different solutions.
It is common to have Configuration information on a Database. REST API’s are also a common way to retrieve Configuration information, but there are others. XML, YAML, VCS’s or even a static Java Class.
If you have a different type of Configuration Source, we would love to see it. You could have your sample added to the list of TomEE examples! Please, read the TomEE contribution guidelines and fire away your PR!
Nice article! But I found that getPropertyNames() wasn’t optional – was getting a compile error until I went ahead and provided an implementation of that method (for which I just returned keySet() on what I return from getProperties(), so an easy fix).