Creating Custom Configuration in Quarkus Loaded from JSON File
Introduction
Quarkus, a framework for building lightweight, fast, and efficient Java applications, offers developers the flexibility to create custom configurations loaded from JSON files. These custom configurations can be seamlessly integrated into your Quarkus application, enhancing its configurability and adaptability. To achieve this, Quarkus utilizes Eclipse MicroProfile Config ( MP-Config), with the SmallRye implementation providing the necessary tools. In this article, we’ll delve into the process of crafting custom configurations and loading them from JSON files within Quarkus, all while exploring the mechanics of SmallRye’s MP-Config implementation. Additionally, we’ll showcase the creation and registration of a custom ConfigSource and ConfigSourceFactory, which play a pivotal role in this configuration management approach.
Understanding MicroProfile Config in Quarkus
Quarkus internally relies on the SmallRye implementation of MP-Config. This implementation allows developers to incorporate Configuration Sources, which provide configuration data from various origins. These sources can be files with non-standard formats, or even data retrieved from a central repository. MP-Config ensures a deterministic ordering of configuration sources based on their ordinal values when multiple sources contain the same configuration key.
SmallRye’s implementation of MP-Config facilitates the creation of new ConfigSources and
ConfigSourceFactories. The ConfigSourceFactory has knowledge of all previously defined sources,
enabling developers to read those values and pass them to a newly created ConfigSource. The
registration of these custom sources and factories is accomplished through the Java ServiceLoader
interface, with a specific file called io.smallrye.config.ConfigSourceFactory
being placed in the
META-INF/services/
directory. This file provides the fully qualified names of the custom sources
and
factories.
Implementing a custom JSON Configuration
To demonstrate the creation and registration of a custom ConfigSource
and ConfigSourceFactory
in
Quarkus, we’ll focus on a practical example - an JsonConfigSource
and JsonConfigSourceFactory
pair.
This custom configuration source allows you to read external JSON configuration files and integrate
them into your Quarkus application.
The JsonConfigSource
class is responsible for reading and providing configuration properties from
a JSON file. It implements the ConfigSource
interface and overrides several methods to interact
with
the Quarkus configuration system.
Here is an overview of its key functionalities:
- Reading a JSON file and parsing it into a JsonObject.
- Providing configuration properties, including handling default values.
- Specifying the source’s ordinal value.
- Assigning a unique name to the source, which will be used for registration.
1
2@Slf4j
3public class JsonConfigSource implements ConfigSource {
4
5 private final Map<String, ConfigValue> existingValues;
6
7 private JsonObject root;
8
9 public JsonConfigSource(final Map<String, ConfigValue> exProp) {
10 existingValues = exProp;
11 }
12
13 public void addJsonConfigurations(final ConfigValue config) {
14 final File file = new File(config.getValue());
15
16 if (!file.canRead()) {
17 log.warn("Can't read config from " + file.getAbsolutePath() + "");
18 } else {
19 try (final InputStream fis = new FileInputStream(file);
20 final JsonReader reader = Json.createReader(fis)) {
21
22 root = reader.readObject();
23 } catch (final IOException ioe) {
24 log.warn("Reading the config failed: " + ioe.getMessage());
25 }
26 }
27 }
28
29 @Override
30 public Map<String, String> getProperties() {
31
32 final Map<String, String> props = new HashMap<>();
33 final Set<Map.Entry<String, ConfigValue>> entries = existingValues.entrySet();
34 for (final Map.Entry<String, ConfigValue> entry : entries) {
35 String newVal = getValue(entry.getKey());
36 if (newVal == null) {
37 newVal = entry.getValue().getValue();
38 }
39 props.put(entry.getKey(), newVal);
40 }
41
42 return props;
43 }
44
45 @Override
46 public Set<String> getPropertyNames() {
47 return existingValues.keySet();
48 }
49
50 @Override
51 public int getOrdinal() {
52 return 270;
53 }
54
55 @Override
56 public String getValue(final String configKey) {
57
58 final JsonValue jsonValue = root.get(configKey);
59
60 if (jsonValue != null) {
61 return getStringValue(jsonValue);
62 }
63
64 if (existingValues.containsKey(configKey)) {
65 return existingValues.get(configKey).getValue();
66 } else {
67 return null;
68 }
69 }
70
71 @Override
72 public String getName() {
73 return "EXTERNAL_JSON";
74 }
75
76 private String getStringValue(final JsonValue jsonValue) {
77 if (jsonValue != null) {
78 final JsonValue.ValueType valueType = jsonValue.getValueType();
79
80 if (valueType == JsonValue.ValueType.STRING) {
81 return ((JsonString) jsonValue).getString();
82 } else if (valueType == JsonValue.ValueType.NUMBER) {
83 // Handle integer and floating-point numbers
84 return jsonValue.toString();
85 } else if (valueType == JsonValue.ValueType.TRUE || valueType == JsonValue.ValueType.FALSE) {
86 // Handle boolean values
87 return Boolean.toString(jsonValue.getValueType() == JsonValue.ValueType.TRUE);
88 } else if (valueType == JsonValue.ValueType.NULL) {
89 // Handle null values
90 return null;
91 }
92 }
93 return null;
94 }
95}
The JsonConfigSourceFactory
class is a custom ConfigSourceFactory
responsible for creating and
configuring instances of the JsonConfigSource
. It also defines a unique priority for this factory.
Here is an overview of its key functionalities:
- Retrieving the path of the JSON configuration file from the Quarkus configuration.
- Building an instance of JsonConfigSource.
- Assigning a priority value to the factory.
1
2@Slf4j
3public class JsonConfigSourceFactory implements ConfigSourceFactory {
4
5 public static final String CONFIG_JSON_FILE = "config.json.file";
6
7 @Override
8 public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext configSourceContext) {
9 final ConfigValue value = configSourceContext.getValue(CONFIG_JSON_FILE);
10
11 if (value == null || value.getValue() == null) {
12 return Collections.emptyList();
13 }
14
15 final Map<String, ConfigValue> exProp = new HashMap<>();
16 final Iterator<String> stringIterator = configSourceContext.iterateNames();
17
18 while (stringIterator.hasNext()) {
19 final String key = stringIterator.next();
20 final ConfigValue cValue = configSourceContext.getValue(key);
21 exProp.put(key, cValue);
22 }
23
24 final JsonConfigSource configSource = new JsonConfigSource(exProp);
25 final List<ConfigValue> configValueList = List.of(value);
26
27 for (final ConfigValue config : configValueList) {
28 if (ConfigExists(config)) {
29 configSource.addJsonConfigurations(config);
30 }
31 }
32
33 return Collections.singletonList(configSource);
34 }
35
36 @Override
37 public OptionalInt getPriority() {
38 return OptionalInt.of(270);
39 }
40
41 private boolean ConfigExists(final ConfigValue config) {
42
43 if (config == null || config.getValue() == null) {
44 log.warn("The given ConfigValue object is null");
45 return false;
46 } else if (!(Files.exists(Path.of(config.getValue())))) {
47 return false;
48 }
49
50 return true;
51 }
52}
Registration via ServiceLoader
Both the JsonConfigSource
and JsonConfigSourceFactory
are registered with the Quarkus
application through the Java ServiceLoader mechanism. A file
named io.smallrye.config.ConfigSourceFactory
is placed in the META-INF/services/
directory. This
file contains the fully qualified name of the JsonConfigSourceFactory
, enabling Quarkus to
discover
and use it.
The JSON file
1{
2 "simple.service": "pusher",
3 "simple.source": "source",
4 "simple.destination": "destination"
5}
Define Your Configuration Interface
1
2@ConfigMapping(prefix = "simple")
3public interface SimpleConfig {
4
5 @WithName("source")
6 String source();
7
8 @WithName("service")
9 String service();
10
11 @WithName("destination")
12 String destination();
13}
Conclusion
Eclipse MicroProfile Config, in conjunction with the SmallRye implementation, empowers Quarkus
developers to manage their application’s configuration efficiently. The ability to create and
register custom ConfigSources
and ConfigSourceFactories
, as demonstrated with
the JsonConfigSource
and JsonConfigSourceFactory
, extends the flexibility and utility of the framework. By following
these guidelines, you can seamlessly integrate external configuration data, such as JSON files, into
your Quarkus application, enhancing its configurability and adaptability.
By understanding these concepts and leveraging the power of MicroProfile Config, you can further optimize your Quarkus application’s configuration management and streamline the development process.
The full source code is available at Github