diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index a74c019713e3b..fd2595f15fdcd 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -6,11 +6,15 @@ import java.util.Optional; import java.util.stream.Collectors; +import javax.inject.Singleton; + import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.jandex.DotName; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.builder.BuildException; +import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -30,11 +34,14 @@ import io.quarkus.vertx.core.deployment.EventLoopCountBuildItem; import io.quarkus.vertx.core.deployment.InternalWebVertxBuildItem; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.quarkus.vertx.http.runtime.HandlerType; import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.http.runtime.RouterProducer; import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.quarkus.vertx.http.runtime.cors.CORSRecorder; +import io.quarkus.vertx.http.runtime.devmode.ConfigHolder; +import io.quarkus.vertx.http.runtime.devmode.ConfigViewerHandler; import io.quarkus.vertx.http.runtime.filters.Filter; import io.vertx.core.Handler; import io.vertx.core.impl.VertxImpl; @@ -166,4 +173,17 @@ ServiceStartBuildItem finalizeRouter( RuntimeInitializedClassBuildItem configureNativeCompilation() { return new RuntimeInitializedClassBuildItem("io.vertx.ext.web.handler.sockjs.impl.XhrTransport"); } + + @BuildStep(onlyIf = IsDevelopment.class) + public void configViewerRoute(HttpBuildTimeConfig httpBuildTimeConfig, + BuildProducer beans, + BuildProducer routes) { + beans.produce(AdditionalBeanBuildItem.builder() + .addBeanClass(ConfigHolder.class) + .setDefaultScope(DotName.createSimple(Singleton.class.getName())) + .setUnremovable() + .build()); + routes.produce(new RouteBuildItem(httpBuildTimeConfig.configPath, + new ConfigViewerHandler(), HandlerType.BLOCKING)); + } } diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/configviewer/ShowConfigTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/configviewer/ShowConfigTest.java new file mode 100644 index 0000000000000..86b8d184e1b6b --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/configviewer/ShowConfigTest.java @@ -0,0 +1,29 @@ +package io.quarkus.vertx.http.configviewer; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.hasItem; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.http.ContentType; + +public class ShowConfigTest { + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("foo=bar"), "application.properties")); + + @Test + void testConfig() { + when().get("/config").then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("sources.properties.foo", hasItem("bar")); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java index 41147163da72f..bd36c2e4110e1 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/HttpBuildTimeConfig.java @@ -21,4 +21,10 @@ public class HttpBuildTimeConfig { */ @ConfigItem public boolean virtual; + + /** + * The path for the config viewer. + */ + @ConfigItem(defaultValue = "/config") + public String configPath; } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigHolder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigHolder.java new file mode 100644 index 0000000000000..f242ae59cc636 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigHolder.java @@ -0,0 +1,11 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.Config; + +public class ConfigHolder { + + @Inject + Config config; +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewer.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewer.java new file mode 100644 index 0000000000000..9472752a2fecb --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewer.java @@ -0,0 +1,81 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.jboss.logging.Logger; + +import io.quarkus.runtime.configuration.ExpandingConfigSource; + +/** + * Turns a {@link Config} into a JSON object with all config sources and properties as JSON. The config sources are + * sorted descending by ordinal, the properties by name. If no config is defined an empty JSON object is returned. + * + *

+ * A typical output might look like: + *

+ * + *
+ * {
+ *   "sources": [
+ *     {
+ *       "source": "source0",
+ *       "ordinal": 200,
+ *       "properties": {
+ *         "key": "value"
+ *       }
+ *     },
+ *     {
+ *       "source": "source1",
+ *       "ordinal": 100,
+ *       "properties": {
+ *         "key": "value"
+ *       }
+ *     }
+ *   ]
+ * }
+ * 
+ */ +class ConfigViewer { + + private static final Logger LOGGER = Logger.getLogger(ConfigViewer.class.getName()); + + String dump(Config config) { + JsonObject json = new JsonObject(); + if (config != null) { + boolean old = ExpandingConfigSource.setExpanding(false); + try { + if (config.getConfigSources().iterator().hasNext()) { + JsonArray jsonSources = new JsonArray(); + for (ConfigSource source : config.getConfigSources()) { + JsonObject jsonSource = new JsonObject(); + jsonSource.put("source", source.getName()) + .put("ordinal", source.getOrdinal()); + Set propertyNames = source.getPropertyNames(); + if (!propertyNames.isEmpty()) { + SortedSet sortedPropertyNames = new TreeSet<>(propertyNames); + JsonObject jsonProperties = new JsonObject(); + for (String propertyName : sortedPropertyNames) { + try { + jsonProperties.put(propertyName, source.getValue(propertyName)); + } catch (Throwable t) { + LOGGER.errorf("Cannot get configuration value for '%s': %s", + propertyName, t.getMessage()); + } + } + jsonSource.put("properties", jsonProperties); + } + jsonSources.put(jsonSource); + } + json.put("sources", jsonSources); + } + } finally { + ExpandingConfigSource.setExpanding(old); + } + } + return json.toString(2); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewerHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewerHandler.java new file mode 100644 index 0000000000000..8fd48e51c384c --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigViewerHandler.java @@ -0,0 +1,20 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import javax.enterprise.inject.spi.CDI; + +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +public class ConfigViewerHandler implements Handler { + + @Override + public void handle(RoutingContext routingContext) { + ConfigHolder configHolder = CDI.current().select(ConfigHolder.class).get(); + String json = new ConfigViewer().dump(configHolder.config); + HttpServerResponse resp = routingContext.response(); + resp.putHeader("content-type", "application/json"); + resp.end(Buffer.buffer(json)); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonArray.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonArray.java new file mode 100644 index 0000000000000..d7c060067ca3f --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonArray.java @@ -0,0 +1,69 @@ +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +package io.quarkus.vertx.http.runtime.devmode; + +import java.io.StringWriter; +import java.util.ArrayList; + +class JsonArray { + + private final ArrayList list; + + JsonArray() { + this.list = new ArrayList(); + } + + JsonArray put(JsonObject value) { + list.add(value); + return this; + } + + void write(StringWriter writer, int indentFactor, int indent) { + boolean needsComma = false; + int length = list.size(); + writer.write('['); + + if (length == 1) { + JsonObject.writeValue(writer, list.get(0), indentFactor, indent); + } else if (length != 0) { + int newIndent = indent + indentFactor; + for (int i = 0; i < length; i += 1) { + if (needsComma) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JsonObject.indent(writer, newIndent); + JsonObject.writeValue(writer, list.get(i), indentFactor, newIndent); + needsComma = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JsonObject.indent(writer, indent); + } + writer.write(']'); + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonObject.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonObject.java new file mode 100644 index 0000000000000..41ef3ed109bd1 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/JsonObject.java @@ -0,0 +1,170 @@ +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +package io.quarkus.vertx.http.runtime.devmode; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +class JsonObject { + + static void writeValue(StringWriter writer, Object value, int indentFactor, int indent) { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JsonArray) { + ((JsonArray) value).write(writer, indentFactor, indent); + } else if (value instanceof JsonObject) { + ((JsonObject) value).write(writer, indentFactor, indent); + } else { + writer.write(quote(value.toString())); + } + } + + static String quote(String string) { + if (string == null || string.isEmpty()) { + return "\"\""; + } + + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); + StringWriter w = new StringWriter(); + + w.write('"'); + for (i = 0; i < len; i += 1) { + b = c; + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + w.write('\\'); + w.write(c); + break; + case '/': + if (b == '<') { + w.write('\\'); + } + w.write(c); + break; + case '\b': + w.write("\\b"); + break; + case '\t': + w.write("\\t"); + break; + case '\n': + w.write("\\n"); + break; + case '\f': + w.write("\\f"); + break; + case '\r': + w.write("\\r"); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w.toString(); + } + + static void indent(StringWriter writer, int indent) { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + private final Map map; + + JsonObject() { + this.map = new HashMap(); + } + + JsonObject put(String key, int value) { + map.put(key, Integer.valueOf(value)); + return this; + } + + JsonObject put(String key, String value) { + map.put(key, value); + return this; + } + + JsonObject put(String key, JsonArray value) { + map.put(key, value); + return this; + } + + JsonObject put(String key, JsonObject value) { + map.put(key, value); + return this; + } + + public String toString(int indentFactor) { + StringWriter w = new StringWriter(); + write(w, indentFactor, 0); + return w.toString(); + } + + private void write(StringWriter writer, int indentFactor, int indent) { + boolean needsComma = false; + writer.write('{'); + + int newIndent = indent + indentFactor; + for (Entry entry : map.entrySet()) { + if (needsComma) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newIndent); + String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, entry.getValue(), indentFactor, newIndent); + needsComma = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + writer.write('}'); + } +}