= Vert.x Config

Vert.x Config provides a way to configure your Vert.x application.
It:

* offers multiple configuration syntaxes (JSON, properties, Yaml (extension), Hocon
(extension)...
* offers multiple configuration stores such as files, directories, HTTP, git (extension), Redis
(extension), system properties and environment properties.
* lets you define the processing order and overloading
* supports runtime reconfiguration

== Concepts

The library is structured around:

**Config Retriever** instantiated and used by the Vert.x application. It
configures a set of configuration store
**Configuration store** defines a location from where the configuration data is read and also a format (JSON by default)

The configuration is retrieved as a JSON Object.

== Using the Config Retriever

To use the Config Retriever, add the following dependency to the
_dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

[source,xml,subs="+attributes"]
----
<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-config</artifactId>
 <version>4.5.6</version>
</dependency>
----

* Gradle (in your `build.gradle` file):

[source,groovy,subs="+attributes"]
----
compile 'io.vertx:vertx-config:4.5.6'
----

Once done, you first need to instantiate the `link:../../apidocs/io/vertx/config/ConfigRetriever.html[ConfigRetriever]`:

[source]
----
ConfigRetriever retriever = ConfigRetriever.create(vertx);
----

By default, the Config Retriever is configured with the following stores (in
this order):

* The Vert.x verticle `config()`
* The system properties
* The environment variables
* A `conf/config.json` file. This path can be overridden using the `vertx-config-path` system property or
`VERTX_CONFIG_PATH` environment variable.

You can configure your own stores:

[source]
----
ConfigStoreOptions httpStore = new ConfigStoreOptions()
  .setType("http")
  .setConfig(new JsonObject()
    .put("host", "localhost").put("port", 8080).put("path", "/conf"));

ConfigStoreOptions fileStore = new ConfigStoreOptions()
  .setType("file")
  .setConfig(new JsonObject().put("path", "my-config.json"));

ConfigStoreOptions sysPropsStore = new ConfigStoreOptions().setType("sys");


ConfigRetrieverOptions options = new ConfigRetrieverOptions()
  .addStore(httpStore).addStore(fileStore).addStore(sysPropsStore);

ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
----

More details about the overloading rules and available stores are available below. Each store can be marked as
`optional`. If a failure is caught while retrieving (or processing) the configuration from an optional store, the failure
is logged, but the processing does not fail. Instead, an empty JSON object is returned (`{}`). To mark a store as
optional, use the `optional` attribute:

[source]
----
ConfigStoreOptions fileStore = new ConfigStoreOptions()
  .setType("file")
  .setOptional(true)
  .setConfig(new JsonObject().put("path", "my-config.json"));
ConfigStoreOptions sysPropsStore = new ConfigStoreOptions().setType("sys");

ConfigRetrieverOptions options = new ConfigRetrieverOptions().addStore(fileStore).addStore(sysPropsStore);

ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
----

Once you have the instance of the Config Retriever, _retrieve_ the configuration
as follows:

[source]
----
retriever.getConfig().onComplete(ar -> {
  if (ar.failed()) {
    // Failed to retrieve the configuration
  } else {
    JsonObject config = ar.result();
  }
});
----

== Overloading rules

The declaration order of the configuration store is important as it defines the
overloading. For conflicting key, configuration stores arriving _last_ overloads the
value provided by the previous configuration stores. Let's take an example. We have 2
configuration stores:

* `A` provides `{a:value, b:1}`
* `B` provides `{a:value2, c:2}`

Declared in this order (A, B), the resulting configuration would be:
`{a:value2, b:1, c:2}`.

If you declare them in the reverse order (B, A), you will get: `{a:value, b:1, c:2}`.

== Using the retrieve configuration

The retrieve configuration allows:

* configuring verticles,
* configure ports, clients, locations and so on,
* configuring Vert.x itself

This section gives a few examples of usage.

=== Configuring a single verticle

The following example can be placed in the `start` method of a verticle. It retrieves the configuration (using the
default stores), and configure an HTTP server with the content of the configuration.

[source]
----
ConfigRetriever retriever = ConfigRetriever.create(vertx);
retriever.getConfig().onComplete(json -> {
  JsonObject result = json.result();

  vertx.createHttpServer()
    .requestHandler(req -> result.getString("message"))
    .listen(result.getInteger("port"));
});
----

=== Configuring a set of verticles

The following example configures 2 verticles using the configurations contained in the `verticles.json` file:

[source]
----
ConfigRetriever retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions()
  .addStore(new ConfigStoreOptions().setType("file").setConfig(new JsonObject().put("path", "verticles.json"))));

retriever.getConfig().onComplete(json -> {
  JsonObject a = json.result().getJsonObject("a");
  JsonObject b = json.result().getJsonObject("b");
  vertx.deployVerticle(GreetingVerticle.class.getName(), new DeploymentOptions().setConfig(a));
  vertx.deployVerticle(GreetingVerticle.class.getName(), new DeploymentOptions().setConfig(b));
});
----

=== Configuring Vert.x itself

You can also configure Vert.x directly. For this, you need a temporary Vert.x instance used to retrieve the
configuration. Then the actual instance is created:

[source]
----
Vertx vertx = Vertx.vertx();
// Create the config retriever
ConfigRetriever retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions()
  .addStore(new ConfigStoreOptions().setType("file").setConfig(new JsonObject().put("path", "vertx.json"))));

// Retrieve the configuration
retriever.getConfig().onComplete(json -> {
  JsonObject result = json.result();
  // Close the vert.x instance, we don't need it anymore.
  vertx.close();

  // Create a new Vert.x instance using the retrieve configuration
  VertxOptions options = new VertxOptions(result);
  Vertx newVertx = Vertx.vertx(options);

  // Deploy your verticle
  newVertx.deployVerticle(GreetingVerticle.class.getName(), new DeploymentOptions().setConfig(result.getJsonObject("a")));
});
----

=== Propagating configuration changes to the event bus

Vert.x Config notifies you when the configuration changes. If you want to react to this event, you need to implement
the reaction yourself. For instance, you can un-deploy and redeploy verticle or send the new configuration on the event bus.
The following example shows this latter case. It sends the new configuration on the event bus. Interested verticles can
listen for this address and update themselves:

[source]
----
ConfigRetriever retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions()
  .addStore(new ConfigStoreOptions().setType("file").setConfig(new JsonObject().put("path", "verticles.json"))));

retriever.getConfig().onComplete(json -> {
  //...
});

retriever.listen(change -> {
  JsonObject json = change.getNewConfiguration();
  vertx.eventBus().publish("new-configuration", json);
});
----

== Available configuration stores

The Config Retriever provides a set of configuration stores and formats.
More are available as extensions, and you can also implement your own.

=== Structure of the configuration

Each declared data store must specify the `type`. It can also define the `format`. If
not set JSON is used.

Some configurations tore requires additional configuration (such a path...). This
configuration is passed as a Json Object using `link:../../apidocs/io/vertx/config/ConfigStoreOptions.html#setConfig-io.vertx.core.json.JsonObject-[setConfig]`

=== File

This configuration store just read the configuration from a file. It supports all
supported formats.

[source, java]
----
ConfigStoreOptions file = new ConfigStoreOptions()
  .setType("file")
  .setFormat("properties")
  .setConfig(new JsonObject().put("path", "path-to-file.properties"));
----

The `path` configuration is required.

=== JSON

The JSON configuration store serves the given JSON config as it is.

[source, java]
----
ConfigStoreOptions json = new ConfigStoreOptions()
  .setType("json")
  .setConfig(new JsonObject().put("key", "value"));
----

The only supported format for this configuration store is JSON.

=== Environment Variables

This configuration store transforms environment variables to a JSON Object contributed to
the global configuration.

[source, java]
----
ConfigStoreOptions env = new ConfigStoreOptions()
  .setType("env");
----

This configuration store does not support the `format` configuration. By default, the retrieved value is
transformed into JSON compatible structures (number, string, boolean, JSON object and JSON array). To avoid this
conversion, configure the `raw-data` attribute:

[source, java]
----
ConfigStoreOptions env = new ConfigStoreOptions()
  .setType("env")
  .setConfig(new JsonObject().put("raw-data", true));
----

You can configure the `raw-data` attribute (`false` by default). If `raw-data` is `true` no attempts to convert
values are made, and you'll be able to get raw values using `config.getString(key)`. It is useful when manipulating
large integers.

If you want to select the set of keys to import, use the `keys` attributes. It filters out all not selected keys. Keys
must be listed individually:

[source, java]
----
ConfigStoreOptions env = new ConfigStoreOptions()
  .setType("env")
  .setConfig(new JsonObject().put("keys", new JsonArray().add("SERVICE1_HOST").add("SERVICE2_HOST")));
----

=== System Properties

This configuration store transforms system properties to a JSON Object contributed to the
global configuration.

[source, java]
----
ConfigStoreOptions sys = new ConfigStoreOptions()
  .setType("sys")
  .setConfig(new JsonObject().put("cache", false));
----

This configuration store does not support the `format` configuration.

You can configure the `cache` attribute (`true` by default) let you decide whether or
not it caches the system properties on the first access and does not reload them.

You can also configure the `raw-data` attribute (`false` by default). If `raw-data` is `true` no attempts to convert
values are made, and you'll be able to get raw values using `config.getString(key)`. It is useful when manipulating
large integers.

Furthermore, there is the `hierarchical` attribute (`false` by default). If `hierarchical` is `true`, the system
properties will be parsed as a nested JSON object, using the dot-separated property name as the path in the JSON object.

Example:

[source, java]
----
ConfigStoreOptions sysHierarchical = new ConfigStoreOptions()
  .setType("sys")
  .setConfig(new JsonObject().put("hierarchical", true));
----
....
java -Dserver.host=localhost -Dserver.port=8080 -jar your-application.jar
....

This will read the system properties as JSON object equivalent to

[source,json]
----
{
 "server": {
   "host": "localhost",
   "port": 8080
 }
}
----

=== HTTP

This configuration store retrieves the configuration from an HTTP location. It can use
any supported format.

[source, java]
----
ConfigStoreOptions http = new ConfigStoreOptions()
  .setType("http")
  .setConfig(new JsonObject()
    .put("host", "localhost")
    .put("port", 8080)
    .put("path", "/A"));
----

It creates a Vert.x HTTP Client with the store configuration (see next snippet). To
ease the configuration; you can also configure the `host`, `port` and `path` with the
`host`, `port` and `path`
properties. You can also configure optional HTTP request headers with `headers` property,
the timeout (in milliseconds, 3000 by default) to retrieve the configuration with `timeout` property,
if following redirects (false by default) with `followRedirects` property.

[source, java]
----
ConfigStoreOptions http = new ConfigStoreOptions()
  .setType("http")
  .setConfig(new JsonObject()
    .put("defaultHost", "localhost")
    .put("defaultPort", 8080)
    .put("ssl", true)
    .put("path", "/A")
    .put("headers", new JsonObject().put("Accept", "application/json")));
----

=== Event Bus

This event bus configuration store receives the configuration from the event bus. This
stores let you distribute your configuration among your local and distributed components.

[source, java]
----
ConfigStoreOptions eb = new ConfigStoreOptions()
  .setType("event-bus")
  .setConfig(new JsonObject()
    .put("address", "address-getting-the-conf")
  );
----

This configuration store supports any format.

=== Directory

This configuration store is similar to the `file` configuration store, but instead of
reading a single file, read several files from a directory.

This configuration store configuration requires:

* a `path` - the root directory in which files are located
* at least one `fileset` - an object to select the files
* for properties file, you can indicate if you want to disable the type conversion using the `raw-data` attribute

Each `fileset` contains:

* a `pattern` : an Ant-style pattern to select files. The pattern is applied to the
relative path of the files from the current working directory.
* an optional `format` indicating the format of the files (each fileset can use a
different format, BUT files in a fileset must share the same format).

[source, java]
----
ConfigStoreOptions dir = new ConfigStoreOptions()
  .setType("directory")
  .setConfig(new JsonObject().put("path", "config")
    .put("filesets", new JsonArray()
      .add(new JsonObject().put("pattern", "dir/*json"))
      .add(new JsonObject().put("pattern", "dir/*.properties")
        .put("format", "properties"))
    ));

ConfigStoreOptions dirWithRawData = new ConfigStoreOptions()
  .setType("directory")
  .setConfig(new JsonObject().put("path", "config")
    .put("filesets", new JsonArray()
      .add(new JsonObject().put("pattern", "dir/*json"))
      .add(new JsonObject().put("pattern", "dir/*.properties")
        .put("format", "properties").put("raw-data", true))
    ));
----

=== Properties file and raw data

Vert.x Config can read a properties file. When reading such a file, you can pass the `raw-data` attribute to
indicate to Vert.x to not attempt to convert values. It is useful when manipulating large integers. Values can be
retrieved using `config.getString(key)`.

[source, java]
----
ConfigStoreOptions propertyWithRawData = new ConfigStoreOptions()
  .setFormat("properties")
  .setType("file")
  .setConfig(new JsonObject().put("path", "raw.properties").put("raw-data", true)
  );
----

Some properties configuration maybe is hierarchical in nature.
When reading such a file, you can pass the `hierarchical` attribute to
indicate to Vert.x to convert the configuration to a json object while maintaining this hierarchy,
in contrast to the previous method with a flat structure.

Example:

....
server.host=localhost
server.port=8080
multiple.values=1,2,3
....

Get values:

[source, java]
----
ConfigStoreOptions propertyWithHierarchical = new ConfigStoreOptions()
  .setFormat("properties")
  .setType("file")
  .setConfig(new JsonObject().put("path", "hierarchical.properties").put("hierarchical", true)
  );
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
  .addStore(propertyWithHierarchical);

ConfigRetriever configRetriever = ConfigRetriever.create(Vertx.vertx(), options);

configRetriever.configStream().handler(config -> {
  String host = config.getJsonObject("server").getString("host");
  Integer port = config.getJsonObject("server").getInteger("port");
  JsonArray multiple = config.getJsonObject("multiple").getJsonArray("values");
  for (int i = 0; i < multiple.size(); i++) {
    Integer value = multiple.getInteger(i);
  }
});
----

== Listening for configuration changes

The Configuration Retriever periodically retrieves the configuration, and if the outcome
is different from the current one, your application can be reconfigured. By default, the
configuration is reloaded every 5 seconds.

[source, java]
----
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
  .setScanPeriod(2000)
  .addStore(store1)
  .addStore(store2);

ConfigRetriever retriever = ConfigRetriever.create(Vertx.vertx(), options);
retriever.getConfig().onComplete(json -> {
  // Initial retrieval of the configuration
});

retriever.listen(change -> {
  // Previous configuration
  JsonObject previous = change.getPreviousConfiguration();
  // New configuration
  JsonObject conf = change.getNewConfiguration();
});
----

== Retrieving the last retrieved configuration

You can retrieve the last retrieved configuration without "waiting" to be retrieved
using:

[source, java]
----
JsonObject last = retriever.getCachedConfig();
----

== Reading configuration as a stream

The `link:../../apidocs/io/vertx/config/ConfigRetriever.html[ConfigRetriever]` provide a way to access the stream of configuration.
It's a `link:../../apidocs/io/vertx/core/streams/ReadStream.html[ReadStream]` of `link:../../apidocs/io/vertx/core/json/JsonObject.html[JsonObject]`. By registering the right
set of handlers you are notified:

* when a new configuration is retrieved
* when an error occur while retrieving a configuration
* when the configuration retriever is closed (the
`link:../../apidocs/io/vertx/core/streams/ReadStream.html#endHandler-io.vertx.core.Handler-[endHandler]` is called).

[source, java]
----
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
  .setScanPeriod(2000)
  .addStore(store1)
  .addStore(store2);

ConfigRetriever retriever = ConfigRetriever.create(Vertx.vertx(), options);
retriever.configStream()
  .endHandler(v -> {
    // retriever closed
  })
  .exceptionHandler(t -> {
    // an error has been caught while retrieving the configuration
  })
  .handler(conf -> {
    // the configuration
  });
----

== Processing the configuration

You can configure a _processor_ that can validate and update the configuration. This is done using the
`link:../../apidocs/io/vertx/config/ConfigRetriever.html#setConfigurationProcessor-java.util.function.Function-[setConfigurationProcessor]` method.

The processing must not return `null`. It takes the retrieved configuration and returns the processed one. If the processor
does not update the configuration, it must return the input configuration. If the processor can throw an exception (for
example for validation purpose).

== Retrieving the configuration as a Future

The `link:../../apidocs/io/vertx/config/ConfigRetriever.html[ConfigRetriever]` provide a way to retrieve the configuration as a
`link:../../apidocs/io/vertx/core/Future.html[Future]`:

[source, java]
----
Future<JsonObject> future = retriever.getConfig();
future.onComplete(ar -> {
  if (ar.failed()) {
    // Failed to retrieve the configuration
  } else {
    JsonObject config = ar.result();
  }
});
----

== Extending the Config Retriever

You can extend the configuration by implementing:

* the `link:../../apidocs/io/vertx/config/spi/ConfigProcessor.html[ConfigProcessor]` SPI to add support for a
format
* the `link:../../apidocs/io/vertx/config/spi/ConfigStoreFactory.html[ConfigStoreFactory]` SPI to add support for
configuration store (place from where the configuration data is retrieved)

== Additional formats

Besides the out of the box format supported by this library, Vert.x Config provides additional
formats you can use in your application.

include::hocon-format.adoc[]

include::yaml-format.adoc[]

== Additional stores

Besides the out of the box stores supported by this library, Vert.x Config provides additional
stores you can use in your application.

include::git-store.adoc[]

include::kubernetes-store.adoc[]

include::redis-store.adoc[]

include::zookeeper-store.adoc[]

include::consul-store.adoc[]

include::spring-store.adoc[]

include::vault-store.adoc[]