Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev UI small fixes #33660

Merged
merged 4 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ metadata:
categories:
- "data"
status: "stable"
unlisted: true
unlisted: true
config:
- "quarkus.datasource."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- "quarkus.datasource."
- "quarkus.datasource."

Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,44 @@

import static io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor.cleanUpAsciiDocIfNecessary;
import static io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor.isSetByDevServices;
import static io.smallrye.config.Expressions.withoutExpansion;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem;
import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem;
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devui.deployment.InternalPageBuildItem;
import io.quarkus.devui.runtime.config.ConfigJsonRPCService;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.vertx.http.deployment.devmode.console.ConfigEditorProcessor;
import io.quarkus.vertx.http.runtime.devmode.ConfigDescription;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.SmallRyeConfig;

/**
* This creates Extensions Page
*/
public class ConfigurationProcessor {

private static final String QUOTED_DOT = "\".\"";
private static final String QUOTED_DOT_KEY = "$$QUOTED_DOT$$";

@BuildStep(onlyIf = IsDevelopment.class)
InternalPageBuildItem createConfigurationPages(List<ConfigDescriptionBuildItem> configDescriptionBuildItems,
Optional<DevServicesLauncherConfigResultBuildItem> devServicesLauncherConfig) {
Expand All @@ -46,6 +64,16 @@ InternalPageBuildItem createConfigurationPages(List<ConfigDescriptionBuildItem>
return configurationPages;
}

@BuildStep(onlyIf = IsDevelopment.class)
JsonRPCProvidersBuildItem registerJsonRpcService() {
DevConsoleManager.register("config-update-property", map -> {
Map<String, String> values = Collections.singletonMap(map.get("name"), map.get("value"));
ConfigEditorProcessor.updateConfig(values, false);
return null;
});
return new JsonRPCProvidersBuildItem("devui-configuration", ConfigJsonRPCService.class);
}

private List<ConfigDescription> getAllConfig(List<ConfigDescriptionBuildItem> configDescriptionBuildItems,
Optional<DevServicesLauncherConfigResultBuildItem> devServicesLauncherConfig) {
List<ConfigDescription> configDescriptions = new ArrayList<>();
Expand All @@ -59,7 +87,228 @@ private List<ConfigDescription> getAllConfig(List<ConfigDescriptionBuildItem> co
item.getAllowedValues(),
item.getConfigPhase().name()));
}
return configDescriptions;

Set<String> devServicesConfig = new HashSet<>();
if (devServicesLauncherConfig.isPresent()) {
devServicesConfig.addAll(devServicesLauncherConfig.get().getConfig().keySet());
}

return calculate(configDescriptions, devServicesConfig);
}

private List<ConfigDescription> calculate(List<ConfigDescription> cd, Set<String> devServicesProperties) {
List<ConfigDescription> configDescriptions = new ArrayList<>(cd);

List<ConfigDescription> ordered = new ArrayList<>();
List<String> properties = new ArrayList<>();
SmallRyeConfig current = (SmallRyeConfig) ConfigProvider.getConfig();

Map<List<String>, Set<String>> allPropertySegments = new HashMap<>();
Set<String> propertyNames = new HashSet<>();
current.getPropertyNames().forEach(propertyNames::add);
for (String propertyName : propertyNames) {
propertyName = propertyName.replace(QUOTED_DOT, QUOTED_DOT_KEY); // Make sure dots can be quoted
String[] parts = propertyName.split("\\.");

List<String> accumulate = new ArrayList<>();
//we never want to add the full string
//hence -1
for (int i = 0; i < parts.length - 1; ++i) {
if (parts[i].isEmpty()) {
//this can't map to a quarkus prop as it has an empty segment
//so skip
break;
}
// If there was a quoted dot, put that back
if (parts[i].contains(QUOTED_DOT_KEY)) {
parts[i] = parts[i].replaceAll(QUOTED_DOT_KEY, QUOTED_DOT);
}

accumulate.add(parts[i]);
//if there is both a quoted and unquoted version we only want to apply the quoted version
//and remove the unquoted one
Set<String> potentialSegmentSet = allPropertySegments.computeIfAbsent(List.copyOf(accumulate),
(k) -> new HashSet<>());
if (isQuoted(parts[i + 1])) {
potentialSegmentSet.add(parts[i + 1]);
potentialSegmentSet.remove(parts[i + 1].substring(1, parts[i + 1].length() - 1));
} else {
if (!potentialSegmentSet.contains(ensureQuoted(parts[i + 1]))) {
potentialSegmentSet.add(parts[i + 1]);
}
}

}
}

Map<List<String>, Set<String>> wildcardsToAdd = new HashMap<>();
Map<String, Holder> foundItems = new HashMap<>();
Set<String> bannedExpansionCombos = new HashSet<>();
//we iterate over every config description
for (ConfigDescription item : configDescriptions) {
//if they are a non-wildcard description we just add them directly
if (!item.getName().contains("{*}")) {
//we don't want to accidentally use these properties as name expansions
//we ban them which means that the only way the name can be expanded into a map
//is if it is quoted
bannedExpansionCombos.add(item.getName());
for (int i = 0; i < item.getName().length(); ++i) {
//add all possible segments to the banned list
if (item.getName().charAt(i) == '.') {
bannedExpansionCombos.add(item.getName().substring(0, i));
}
}
properties.add(item.getName());
item.setConfigValue(getConfigValue(current, item.getName()));
ordered.add(item);
} else if (!item.getName().startsWith("quarkus.log.filter")) { //special case, we use this internally and we don't want it clogging up the editor
//we need to figure out how to expand it
//this can have multiple stars
List<List<String>> componentParts = new ArrayList<>();
List<String> accumulator = new ArrayList<>();
//keys that were used to expand, checked against the banned list before adding
for (var i : item.getName().split("\\.")) {
if (i.equals("{*}")) {
componentParts.add(accumulator);
accumulator = new ArrayList<>();
} else {
accumulator.add(i);
}
}
//note that accumulator is still holding the final part
//we need it later, but we don't want it in this loop
Map<List<String>, Set<String>> building = new HashMap<>();
building.put(List.of(), new HashSet<>());
for (List<String> currentPart : componentParts) {
Map<List<String>, Set<String>> newBuilding = new HashMap<>();
for (Map.Entry<List<String>, Set<String>> entry : building.entrySet()) {
List<String> attempt = entry.getKey();
List<String> newBase = new ArrayList<>(attempt);
newBase.addAll(currentPart);
wildcardsToAdd.put(newBase, entry.getValue());
Set<String> potential = allPropertySegments.get(newBase);
if (potential != null) {
bannedExpansionCombos.add(String.join(".", newBase).replace("\"", ""));
for (String definedName : potential) {
List<String> toAdd = new ArrayList<>(newBase);
toAdd.add(definedName);
//for expansion keys we always use unquoted values, same with banned
//so we are always comparing unquoted
Set<String> expansionKeys = new HashSet<>(entry.getValue());
expansionKeys.add(String.join(".", newBase) + "." + definedName);
newBuilding.put(toAdd, expansionKeys);
}
}
}
building = newBuilding;
}
//now we have our config properties
for (var entry : building.entrySet()) {
List<String> segments = entry.getKey();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < segments.size(); ++i) {
if (i > 0) {
sb.append(".");
}
sb.append(segments.get(i));
}
//accumulator holds the find string
for (String s : accumulator) {
sb.append(".").append(s);
}
String expandedName = sb.toString();
foundItems.put(expandedName, new Holder(entry.getValue(), item));
}
}
}
for (Map.Entry<String, Holder> e : foundItems.entrySet()) {
boolean ok = true;
for (String key : e.getValue().expansionKeys) {
if (bannedExpansionCombos.contains(key)) {
ok = false;
break;
}
}
if (!ok) {
continue;
}
String expandedName = e.getKey();
var item = e.getValue().configDescription;
ConfigDescription newDesc = new ConfigDescription(expandedName, item.getDescription(),
item.getDefaultValue(), devServicesProperties.contains(expandedName), item.getTypeName(),
item.getAllowedValues(),
item.getConfigPhase());

properties.add(newDesc.getName());
newDesc.setConfigValue(getConfigValue(current, newDesc.getName()));
ordered.add(newDesc);
}

//now add our star properties
for (var entry : wildcardsToAdd.entrySet()) {
boolean ok = true;
for (String key : entry.getValue()) {
if (bannedExpansionCombos.contains(key)) {
ok = false;
break;
}
}
if (!ok) {
continue;
}
List<String> segments = entry.getKey();
StringBuilder sb = new StringBuilder();
for (String segment : segments) {
sb.append(segment);
sb.append(".");
}
String expandedName = sb.toString();
ConfigDescription newDesc = new ConfigDescription(expandedName, true);

properties.add(newDesc.getName());
newDesc.setConfigValue(getConfigValue(current, newDesc.getName()));
ordered.add(newDesc);
}

for (ConfigSource configSource : current.getConfigSources()) {
if (configSource.getName().equals("PropertiesConfigSource[source=Build system]")) {
properties.addAll(configSource.getPropertyNames());
}
}

withoutExpansion(() -> {
for (String propertyName : current.getPropertyNames()) {
if (properties.contains(propertyName)) {
continue;
}

ConfigDescription item = new ConfigDescription(propertyName, null, null, getConfigValue(current, propertyName));
ordered.add(item);

configDescriptions.add(item);
}
});

return ordered;
}

private ConfigValue getConfigValue(SmallRyeConfig config, String name) {
try {
return config.getConfigValue(name);
} catch (java.util.NoSuchElementException nse) {
return null;
}
}

private String ensureQuoted(String part) {
if (isQuoted(part)) {
return part;
}
return "\"" + part + "\"";
}

private boolean isQuoted(String part) {
return part.length() >= 2 && part.charAt(0) == '\"' && part.charAt(part.length() - 1) == '\"';
}

private static final Pattern codePattern = Pattern.compile("(\\{@code )([^}]+)(\\})");
Expand All @@ -77,4 +326,14 @@ static String formatJavadoc(String val) {
val = val.replace("@deprecated", "<br><strong>Deprecated</strong>");
return val;
}

static class Holder {
final Set<String> expansionKeys;
final ConfigDescription configDescription;

private Holder(Set<String> expansionKeys, ConfigDescription configDescription) {
this.expansionKeys = expansionKeys;
this.configDescription = configDescription;
}
}
}
Loading