diff --git a/vertx-config/src/main/asciidoc/index.adoc b/vertx-config/src/main/asciidoc/index.adoc index 63ec3383..c44156d1 100644 --- a/vertx-config/src/main/asciidoc/index.adoc +++ b/vertx-config/src/main/asciidoc/index.adoc @@ -217,6 +217,15 @@ must be listed individually: {@link examples.ConfigExamples#env3()} ---- +Furthermore, there is the `hierarchical` attribute (`false` by default). If `hierarchical` is `true`, the environment variables will be parsed as a nested JSON object, using the dot-separated property name as the path in the JSON object. + +Example: + +[source, $lang] +---- +{@link examples.ConfigExamples#envHierarchical()} +---- + === System Properties This configuration store transforms system properties to a JSON Object contributed to the diff --git a/vertx-config/src/main/java/examples/ConfigExamples.java b/vertx-config/src/main/java/examples/ConfigExamples.java index 736b5f3e..5ac8c21e 100644 --- a/vertx-config/src/main/java/examples/ConfigExamples.java +++ b/vertx-config/src/main/java/examples/ConfigExamples.java @@ -129,6 +129,11 @@ public void env3() { .setConfig(new JsonObject().put("keys", new JsonArray().add("SERVICE1_HOST").add("SERVICE2_HOST"))); } + public void envHierarchical() { + ConfigStoreOptions envHierarchical = new ConfigStoreOptions() + .setType("env") + .setConfig(new JsonObject().put("hierarchical", true)); + } public void http() { ConfigStoreOptions http = new ConfigStoreOptions() diff --git a/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStore.java b/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStore.java index d1e385e9..d94dc02f 100644 --- a/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStore.java +++ b/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStore.java @@ -26,7 +26,6 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; -import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -44,13 +43,15 @@ public class EnvVariablesConfigStore implements ConfigStore { private final VertxInternal vertx; private final boolean rawData; + private final boolean hierarchical; private final Set keys; private final Supplier> getenv; private final AtomicReference cached = new AtomicReference<>(); - public EnvVariablesConfigStore(Vertx vertx, boolean rawData, JsonArray keys, Supplier> getenv) { + public EnvVariablesConfigStore(Vertx vertx, boolean rawData, boolean hierarchical, JsonArray keys, Supplier> getenv) { this.vertx = (VertxInternal) vertx; this.rawData = rawData; + this.hierarchical = hierarchical; this.keys = (keys == null) ? null : new HashSet<>(keys.getList()); this.getenv = getenv; } @@ -59,20 +60,19 @@ public EnvVariablesConfigStore(Vertx vertx, boolean rawData, JsonArray keys, Sup public Future get() { Buffer value = cached.get(); if (value == null) { - value = all(getenv.get(), rawData, keys).toBuffer(); + value = all(getenv.get(), rawData, hierarchical, keys).toBuffer(); cached.set(value); } return vertx.getOrCreateContext().succeededFuture(value); } - private static JsonObject all(Map env, boolean rawData, Set keys) { + private static JsonObject all(Map env, boolean rawData, boolean hierarchical, Set keys) { JsonObject json = new JsonObject(); - Collection localKeys = keys == null ? env.keySet() : keys; - env.forEach((key, value) -> { - if (localKeys.contains(key)) { - JsonObjectHelper.put(json, key, value, rawData); + for (Map.Entry entry : env.entrySet()) { + if (keys == null || keys.contains(entry.getKey())) { + JsonObjectHelper.put(json, entry.getKey(), entry.getValue(), rawData, hierarchical); } - }); + } return json; } diff --git a/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreFactory.java b/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreFactory.java index c1faaedb..f74e1726 100644 --- a/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreFactory.java +++ b/vertx-config/src/main/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreFactory.java @@ -49,6 +49,6 @@ public String name() { @Override public ConfigStore create(Vertx vertx, JsonObject configuration) { - return new EnvVariablesConfigStore(vertx, configuration.getBoolean("raw-data", false), configuration.getJsonArray("keys"), getenv); + return new EnvVariablesConfigStore(vertx, configuration.getBoolean("raw-data", false), configuration.getBoolean("hierarchical", false), configuration.getJsonArray("keys"), getenv); } } diff --git a/vertx-config/src/main/java/io/vertx/config/spi/utils/JsonObjectHelper.java b/vertx-config/src/main/java/io/vertx/config/spi/utils/JsonObjectHelper.java index 687070f9..5376c1fa 100644 --- a/vertx-config/src/main/java/io/vertx/config/spi/utils/JsonObjectHelper.java +++ b/vertx-config/src/main/java/io/vertx/config/spi/utils/JsonObjectHelper.java @@ -49,6 +49,27 @@ public static void put(JsonObject json, String name, String value, boolean rawDa json.put(name, rawData ? value : convert(value)); } + public static void put(JsonObject json, String name, String value, boolean rawData, boolean hierarchical) { + if (!hierarchical) { + put(json, name, value, rawData); + } else { + List path = Arrays.asList(name.split("\\.")); + if (path.size() == 1) { + put(json, name, value, rawData); + } else { + JsonObject current = json; + for (int i = 0; i < path.size() - 1; i++) { + String key = path.get(i); + if (!current.containsKey(key)) { + current.put(key, new JsonObject()); + } + current = current.getJsonObject(key); + } + put(current, path.get(path.size() - 1), value, rawData); + } + } + } + public static Object convert(String value) { Objects.requireNonNull(value); diff --git a/vertx-config/src/test/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreWithMockEnvTest.java b/vertx-config/src/test/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreWithMockEnvTest.java index 1f4e1ff5..b76fea8a 100644 --- a/vertx-config/src/test/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreWithMockEnvTest.java +++ b/vertx-config/src/test/java/io/vertx/config/impl/spi/EnvVariablesConfigStoreWithMockEnvTest.java @@ -42,8 +42,8 @@ public void testLoadingFromEnvUsingRawData() { retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions() .addStore(new ConfigStoreOptions().setType("mock-env").setConfig(new JsonObject() - .put("raw-data", true) - .put("env", Collections.singletonMap("name", "12345678901234567890")) + .put("raw-data", true) + .put("env", Collections.singletonMap("mock.name", "12345678901234567890")) ) ) ); @@ -53,7 +53,7 @@ public void testLoadingFromEnvUsingRawData() { retriever.getConfig().onComplete(ar -> { assertThat(ar.succeeded()).isTrue(); assertThat(ar.result().getString("PATH")).isNotNull(); - assertThat(ar.result().getString("name")).isEqualTo("12345678901234567890"); + assertThat(ar.result().getString("mock.name")).isEqualTo("12345678901234567890"); done.set(true); }); await().untilAtomic(done, is(true)); @@ -63,7 +63,36 @@ public void testLoadingFromEnvUsingRawData() { public void testLoadingFromEnvWithoutRawData() { retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions() - .addStore(new ConfigStoreOptions().setType("mock-env").setConfig(new JsonObject().put("env", Collections.singletonMap("name", "12345678901234567890")))) + .addStore(new ConfigStoreOptions().setType("mock-env").setConfig(new JsonObject() + .put("env", Collections.singletonMap("mock.name", "12345678901234567890")) + ) + ) + ); + + AtomicBoolean done = new AtomicBoolean(); + + retriever.getConfig().onComplete(ar -> { + assertThat(ar.succeeded()).isTrue(); + try { + // We don't mind the value (truncated, we just want to make sure it doesn't throw an exception) + assertThat(ar.result().getInteger("mock.name")).isNotNull(); + } catch (ClassCastException e) { + throw new AssertionError("Should not throw exception", e); + } + done.set(true); + }); + await().untilAtomic(done, is(true)); + } + + @Test + public void testHierarchicalLoadingFromEnv() { + retriever = ConfigRetriever.create(vertx, + new ConfigRetrieverOptions() + .addStore(new ConfigStoreOptions().setType("mock-env").setConfig(new JsonObject() + .put("hierarchical", true) + .put("env", Collections.singletonMap("mock.name", "12345678901234567890")) + ) + ) ); AtomicBoolean done = new AtomicBoolean(); @@ -72,7 +101,8 @@ public void testLoadingFromEnvWithoutRawData() { assertThat(ar.succeeded()).isTrue(); try { // We don't mind the value (truncated, we just want to make sure it doesn't throw an exception) - assertThat(ar.result().getInteger("name")).isNotNull(); + assertThat(ar.result().getJsonObject("mock")).isNotNull(); + assertThat(ar.result().getJsonObject("mock").getInteger("name")).isNotNull(); } catch (ClassCastException e) { throw new AssertionError("Should not throw exception", e); } diff --git a/vertx-config/src/test/java/io/vertx/config/impl/spi/MockEnvVariablesConfigStoreFactory.java b/vertx-config/src/test/java/io/vertx/config/impl/spi/MockEnvVariablesConfigStoreFactory.java index c86c2e7b..3c40156a 100644 --- a/vertx-config/src/test/java/io/vertx/config/impl/spi/MockEnvVariablesConfigStoreFactory.java +++ b/vertx-config/src/test/java/io/vertx/config/impl/spi/MockEnvVariablesConfigStoreFactory.java @@ -44,7 +44,7 @@ public String name() { @Override public ConfigStore create(Vertx vertx, JsonObject configuration) { JsonObject envConfig = configuration.getJsonObject("env"); - return new EnvVariablesConfigStore(vertx, configuration.getBoolean("raw-data", false), configuration.getJsonArray("keys"), () -> { + return new EnvVariablesConfigStore(vertx, configuration.getBoolean("raw-data", false), configuration.getBoolean("hierarchical", false), configuration.getJsonArray("keys"), () -> { Map env = new HashMap<>(System.getenv()); if (envConfig != null) { for (Map.Entry entry : envConfig) {