diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java index 2ad89389cf35b..d4083f30145a7 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/ConfigEditorProcessor.java @@ -1,63 +1,40 @@ package io.quarkus.vertx.http.deployment.devmode.console; +import static io.quarkus.runtime.LaunchMode.DEVELOPMENT; + import java.io.BufferedWriter; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Properties; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.arc.runtime.ConfigRecorder; +import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; import io.quarkus.dev.console.DevConsoleManager; import io.quarkus.devconsole.runtime.spi.DevConsolePostHandler; import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; -import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; +import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; +import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.vertx.http.runtime.devmode.ConfigDescription; +import io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsSupplier; import io.vertx.core.MultiMap; import io.vertx.ext.web.RoutingContext; public class ConfigEditorProcessor { - - @BuildStep - DevConsoleTemplateInfoBuildItem config(List<ConfigDescriptionBuildItem> config) throws Exception { - List<CurrentConfig> configs = new ArrayList<>(); - Config current = ConfigProvider.getConfig(); - - Properties appProperties = new Properties(); - Path appProps = null; - for (Path i : DevConsoleManager.getHotReplacementContext().getResourcesDir()) { - Path app = i.resolve("application.properties"); - if (Files.exists(app)) { - appProps = app; - break; - } - } - if (appProps != null) { - try (InputStream in = Files.newInputStream(appProps)) { - appProperties.load(in); - } - } - - for (ConfigDescriptionBuildItem i : config) { - if (i.getPropertyName().contains("*")) { - continue; //TODO: complex properties - } - configs.add(new CurrentConfig(i.getPropertyName(), i.getDocs(), i.getDefaultValue(), - current.getOptionalValue(i.getPropertyName(), String.class).orElse(null), - appProperties.getProperty(i.getPropertyName()))); + @BuildStep(onlyIf = IsDevelopment.class) + @Record(ExecutionTime.RUNTIME_INIT) + public DevConsoleRuntimeTemplateInfoBuildItem config(ConfigRecorder recorder, + List<ConfigDescriptionBuildItem> configDescriptionBuildItems) { + List<ConfigDescription> configDescriptions = new ArrayList<>(); + for (ConfigDescriptionBuildItem item : configDescriptionBuildItems) { + configDescriptions.add( + new ConfigDescription(item.getPropertyName(), item.getDocs(), item.getDefaultValue())); } - Collections.sort(configs); - - return new DevConsoleTemplateInfoBuildItem("config", configs); + return new DevConsoleRuntimeTemplateInfoBuildItem("config", new ConfigDescriptionsSupplier(configDescriptions)); } @BuildStep @@ -65,107 +42,55 @@ DevConsoleRouteBuildItem handlePost() { return new DevConsoleRouteBuildItem("config", "POST", new DevConsolePostHandler() { @Override protected void handlePost(RoutingContext event, MultiMap form) throws Exception { - String key = event.request().getFormAttribute("name"); + String name = event.request().getFormAttribute("name"); String value = event.request().getFormAttribute("value"); - Properties appProperties = new Properties(); - Path appProps = null; - for (Path i : DevConsoleManager.getHotReplacementContext().getResourcesDir()) { - Path app = i.resolve("application.properties"); - if (Files.exists(app)) { - appProps = app; + List<Path> resourcesDir = DevConsoleManager.getHotReplacementContext().getResourcesDir(); + if (resourcesDir.isEmpty()) { + throw new IllegalStateException("Unable to manage configurations - no resource directory found"); + } + + // In the current project only + Path path = resourcesDir.get(0); + Path configPath = path.resolve("application.properties"); + if (!Files.exists(configPath)) { + configPath = Files.createFile(path.resolve("application.properties")); + } + + String profile = ProfileManager.getActiveProfile(); + name = !profile.equals(DEVELOPMENT.getDefaultProfile()) ? "%" + profile + "." + name : name; + List<String> lines = Files.readAllLines(configPath); + int nameLine = -1; + for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { + final String line = lines.get(i); + if (line.startsWith(name + "=")) { + nameLine = i; break; } } - boolean present = false; - if (appProps != null) { - try (InputStream in = Files.newInputStream(appProps)) { - appProperties.load(in); - present = appProperties.containsKey(key); + + if (nameLine != -1) { + if (value.isEmpty()) { + lines.remove(nameLine); + } else { + lines.set(nameLine, name + "=" + value); } } else { - // If there is no application.properties file then create a new one in the first module - List<Path> resourcesDir = DevConsoleManager.getHotReplacementContext().getResourcesDir(); - if (resourcesDir.isEmpty()) { - throw new IllegalStateException( - "Unable to create application.properties - no resource directory found"); + if (!value.isEmpty()) { + lines.add(name + "=" + value); } - appProps = Files.createFile(resourcesDir.get(0).resolve("application.properties")); } - if (!present) { - try (OutputStream out = Files.newOutputStream(appProps, StandardOpenOption.APPEND)) { - out.write(("\n" + key + "=" + value).getBytes(StandardCharsets.UTF_8)); //TODO: escpaing - } - } else { - List<String> lines = Files.readAllLines(appProps); - Iterator<String> it = lines.iterator(); - while (it.hasNext()) { - String val = it.next(); - if (val.startsWith(key + "=")) { - it.remove(); - } - } - lines.add(key + "=" + value); - try (BufferedWriter writer = Files.newBufferedWriter(appProps)) { - for (String i : lines) { - writer.write(i); - writer.newLine(); - } + + try (BufferedWriter writer = Files.newBufferedWriter(configPath)) { + for (String i : lines) { + writer.write(i); + writer.newLine(); } } + DevConsoleManager.getHotReplacementContext().doScan(true); flashMessage(event, "Configuration updated"); } }); } - - public static class CurrentConfig implements Comparable<CurrentConfig> { - private final String propertyName; - private final String description; - private final String defaultValue; - private final String currentValue; - private final String appPropertiesValue; - - public CurrentConfig(String propertyName, String description, String defaultValue, String currentValue, - String appPropertiesValue) { - this.propertyName = propertyName; - this.description = description; - this.defaultValue = defaultValue; - this.currentValue = currentValue; - this.appPropertiesValue = appPropertiesValue; - } - - public String getPropertyName() { - return propertyName; - } - - public String getDescription() { - return description; - } - - public String getDefaultValue() { - return defaultValue; - } - - public String getCurrentValue() { - return currentValue; - } - - public String getAppPropertiesValue() { - return appPropertiesValue; - } - - @Override - public int compareTo(CurrentConfig o) { - if (appPropertiesValue == null && o.appPropertiesValue != null) { - return 1; - } - if (appPropertiesValue != null && o.appPropertiesValue == null) { - return -1; - } - - return propertyName.compareTo(o.propertyName); - } - } - } diff --git a/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/config.html b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/config.html index 35fdf42d2b2a9..7fe8362c5d56a 100644 --- a/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/config.html +++ b/extensions/vertx-http/deployment/src/main/resources/dev-templates/io.quarkus.quarkus-vertx-http/config.html @@ -13,19 +13,20 @@ <th scope="col">Value</th> <th scope="col">Update</th> <th scope="col">Default Value</th> + <th scope="col">Source</th> <th scope="col">Description</th> </tr> </thead> <tbody> {#for item in info:config} - <tr class="{item.appPropertiesValue ? 'table-info' : ''}"> + <tr> <form method="post" enctype="application/x-www-form-urlencoded"> - <input type="hidden" name="name" value="{item.propertyName}"/> + <input type="hidden" name="name" value="{item.configValue.name}"/> <td> - {item.propertyName} + {item.configValue.name} </td> <td> - <input type="text" name="value" value="{item.currentValue}"/> + <input type="text" name="value" value="{item.configValue.value}"/> </td> <td> <input type="submit" value="Update" > @@ -33,6 +34,9 @@ <td> {item.defaultValue} </td> + <td> + {item.configValue.configSourceName} + </td> <td> {item.description.fmtJavadoc} </td> diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleConfigEditorTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleConfigEditorTest.java index 57e3773670741..e1e0237e94adb 100644 --- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleConfigEditorTest.java +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devconsole/DevConsoleConfigEditorTest.java @@ -36,4 +36,20 @@ public void testChangeHttpRoute() { } + @Test + public void testSetEmptyValue() { + RestAssured.with() + .get("q/arc/beans") + .then() + .statusCode(200); + RestAssured.with().formParam("name", "quarkus.http.root-path").formParam("value", "") + .redirects().follow(false) + .post("q/dev/io.quarkus.quarkus-vertx-http/config") + .then() + .statusCode(303); + RestAssured.with() + .get("q/arc/beans") + .then() + .statusCode(200); + } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescription.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescription.java new file mode 100644 index 0000000000000..4c7451ad9cc49 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescription.java @@ -0,0 +1,71 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import io.smallrye.config.ConfigValue; + +public class ConfigDescription implements Comparable<ConfigDescription> { + private String name; + private String description; + private String defaultValue; + private ConfigValue configValue; + + public ConfigDescription() { + } + + public ConfigDescription(final String name, final String description, final String defaultValue) { + this.name = name; + this.description = description; + this.defaultValue = defaultValue; + } + + public ConfigDescription( + final String name, + final String description, + final String defaultValue, + final ConfigValue configValue) { + this.name = name; + this.description = description; + this.defaultValue = defaultValue; + this.configValue = configValue; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(final String defaultValue) { + this.defaultValue = defaultValue; + } + + public ConfigValue getConfigValue() { + return configValue; + } + + public void setConfigValue(final ConfigValue configValue) { + this.configValue = configValue; + } + + @Override + public int compareTo(ConfigDescription o) { + int ordinal = Integer.compare(o.configValue.getConfigSourceOrdinal(), this.configValue.getConfigSourceOrdinal()); + if (ordinal == 0) { + return this.configValue.getName().compareTo(o.configValue.getName()); + } + return ordinal; + } +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescriptionsSupplier.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescriptionsSupplier.java new file mode 100644 index 0000000000000..cc78c07996020 --- /dev/null +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ConfigDescriptionsSupplier.java @@ -0,0 +1,58 @@ +package io.quarkus.vertx.http.runtime.devmode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.config.SmallRyeConfig; + +public class ConfigDescriptionsSupplier implements Supplier<List<ConfigDescription>> { + private List<ConfigDescription> configDescriptions; + + public ConfigDescriptionsSupplier() { + } + + public ConfigDescriptionsSupplier(final List<ConfigDescription> configDescriptions) { + this.configDescriptions = configDescriptions; + } + + @Override + public List<ConfigDescription> get() { + List<String> properties = new ArrayList<>(); + SmallRyeConfig current = (SmallRyeConfig) ConfigProvider.getConfig(); + + for (ConfigDescription item : configDescriptions) { + properties.add(item.getName()); + item.setConfigValue(current.getConfigValue(item.getName())); + } + + for (ConfigSource configSource : current.getConfigSources()) { + if (configSource.getName().equals("PropertiesConfigSource[source=Build system]")) { + properties.addAll(configSource.getPropertyNames()); + } + } + + for (String propertyName : current.getPropertyNames()) { + if (properties.contains(propertyName)) { + continue; + } + + configDescriptions.add(new ConfigDescription(propertyName, null, null, current.getConfigValue(propertyName))); + } + + Collections.sort(configDescriptions); + return configDescriptions; + } + + public List<ConfigDescription> getConfigDescriptions() { + return configDescriptions; + } + + public void setConfigDescriptions(final List<ConfigDescription> configDescriptions) { + this.configDescriptions = configDescriptions; + } +}