io.quarkus.vertx.http.runtime.devmode.ConfigDescriptionsManager Maven / Gradle / Ivy
The newest version!
package io.quarkus.vertx.http.runtime.devmode;
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.Set;
import java.util.TreeMap;
import java.util.function.Supplier;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;
import io.quarkus.devui.runtime.config.ConfigDescription;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.SmallRyeConfig;
import io.vertx.core.impl.ConcurrentHashSet;
public class ConfigDescriptionsManager implements Supplier {
private final List configDescriptions;
private final Set devServicesProperties;
private ClassLoader currentCl;
private static final String QUOTED_DOT = "\".\"";
private static final String QUOTED_DOT_KEY = "$$QUOTED_DOT$$";
volatile Map> values;
/**
* named config groups that have been added, these are not represented in the config, but just stored in memory.
*
* This is static to persist across restarts
*/
private static Set addedConfigKeys = new ConcurrentHashSet<>();
public ConfigDescriptionsManager() {
this(List.of());
}
public ConfigDescriptionsManager(final List configDescriptions) {
this(configDescriptions, Set.of());
}
public ConfigDescriptionsManager(final List configDescriptions, Set devServicesProperties) {
this.configDescriptions = Collections.unmodifiableList(new ArrayList<>(configDescriptions));
this.devServicesProperties = devServicesProperties;
currentCl = Thread.currentThread().getContextClassLoader();
}
public Map> values() {
ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(currentCl);
if (values == null) {
values = calculate();
}
return values;
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
public void addNamedConfigGroup(String name) {
addedConfigKeys.add(name + ".fake");
values = calculate();
}
public Map> calculate() {
List configDescriptions = new ArrayList<>(this.configDescriptions);
Map> ordered = new TreeMap<>();
List properties = new ArrayList<>();
SmallRyeConfig current = (SmallRyeConfig) ConfigProvider.getConfig();
Map, Set> allPropertySegments = new HashMap<>();
Set propertyNames = new HashSet<>(addedConfigKeys);
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 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 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, Set> wildcardsToAdd = new HashMap<>();
Map foundItems = new HashMap<>();
Set 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(current.getConfigValue(item.getName()));
String configSourceName = item.getConfigValue().getConfigSourceName();
int configSourceOrdinal = item.getConfigValue().getConfigSourceOrdinal();
ordered.putIfAbsent(new ConfigSourceName(configSourceName, configSourceOrdinal), new ArrayList<>());
ordered.get(new ConfigSourceName(configSourceName, configSourceOrdinal)).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> componentParts = new ArrayList<>();
List 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, Set> building = new HashMap<>();
building.put(List.of(), new HashSet<>());
for (List currentPart : componentParts) {
Map, Set> newBuilding = new HashMap<>();
for (Map.Entry, Set> entry : building.entrySet()) {
List attempt = entry.getKey();
List newBase = new ArrayList<>(attempt);
newBase.addAll(currentPart);
wildcardsToAdd.put(newBase, entry.getValue());
Set potential = allPropertySegments.get(newBase);
if (potential != null) {
bannedExpansionCombos.add(String.join(".", newBase).replace("\"", ""));
for (String definedName : potential) {
List 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 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 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 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(current.getConfigValue(newDesc.getName()));
String configSourceName = newDesc.getConfigValue().getConfigSourceName();
int configSourceOrdinal = newDesc.getConfigValue().getConfigSourceOrdinal();
ordered.putIfAbsent(new ConfigSourceName(configSourceName, configSourceOrdinal), new ArrayList<>());
ordered.get(new ConfigSourceName(configSourceName, configSourceOrdinal)).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 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(current.getConfigValue(newDesc.getName()));
String configSourceName = newDesc.getConfigValue().getConfigSourceName();
int configSourceOrdinal = newDesc.getConfigValue().getConfigSourceOrdinal();
ordered.putIfAbsent(new ConfigSourceName(configSourceName, configSourceOrdinal), new ArrayList<>());
ordered.get(new ConfigSourceName(configSourceName, configSourceOrdinal)).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, current.getConfigValue(propertyName));
ConfigValue configValue = current.getConfigValue(propertyName);
ConfigSourceName csn = new ConfigSourceName(configValue.getConfigSourceName(),
configValue.getConfigSourceOrdinal());
ordered.putIfAbsent(csn, new ArrayList<>());
ordered.get(csn).add(item);
configDescriptions.add(item);
}
});
for (List i : ordered.values()) {
Collections.sort(i);
}
return ordered;
}
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) == '\"';
}
@Override
public ConfigDescriptionsManager get() {
currentCl = Thread.currentThread().getContextClassLoader();
return this;
}
static class Holder {
final Set expansionKeys;
final ConfigDescription configDescription;
private Holder(Set expansionKeys, ConfigDescription configDescription) {
this.expansionKeys = expansionKeys;
this.configDescription = configDescription;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy