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

Support Hocon/Json Configuration Source for MP #4218

Merged
merged 15 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@
<artifactId>helidon-config-yaml-mp</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-hocon-mp</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-git</artifactId>
Expand Down
4 changes: 0 additions & 4 deletions config/config-mp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml-mp</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
240 changes: 49 additions & 191 deletions config/config-mp/src/main/java/io/helidon/config/mp/MpConfigBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2021 Oracle and/or its affiliates.
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,7 +24,6 @@
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
Expand All @@ -47,15 +46,14 @@
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.ServiceLoader;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
Expand All @@ -66,7 +64,7 @@
import io.helidon.config.ConfigMappers;
import io.helidon.config.ConfigValue;
import io.helidon.config.mp.spi.MpConfigFilter;
import io.helidon.config.yaml.mp.YamlMpConfigSource;
import io.helidon.config.mp.spi.MpMetaConfigProvider;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigBuilder;
Expand All @@ -79,11 +77,31 @@
/**
* Configuration builder.
*/
@Deprecated
public class MpConfigBuilder implements ConfigBuilder {
class MpConfigBuilder implements ConfigBuilder {
private static final Logger LOGGER = Logger.getLogger(MpConfigBuilder.class.getName());
private static final String DEFAULT_CONFIG_SOURCE = "META-INF/microprofile-config.properties";

private static final Map<String, MpMetaConfigProvider> MP_META_PROVIDERS;
static {
List<MpMetaConfigProvider> mpMetaConfigProviders =
HelidonServiceLoader.builder(ServiceLoader.load(MpMetaConfigProvider.class))
.addService(new MpEnvironmentVariablesMetaConfigProvider())
.addService(new MpSystemPropertiesMetaConfigProvider())
.addService(new MpPropertiesMetaConfigProvider())
.build()
.asList();

Map<String, MpMetaConfigProvider> theMap = new HashMap<>();
// ordered by priority
for (MpMetaConfigProvider mpMetaConfigProvider : mpMetaConfigProviders) {
for (String supportedType : mpMetaConfigProvider.supportedTypes()) {
theMap.putIfAbsent(supportedType, mpMetaConfigProvider);
}
}
MP_META_PROVIDERS = Map.copyOf(theMap);
}


private final List<OrdinalSource> sources = new LinkedList<>();
private final List<OrdinalConverter> converters = new LinkedList<>();

Expand Down Expand Up @@ -154,14 +172,6 @@ private static Class<?> doGetType(Class<?> clazz) {
return doGetType(clazz.getSuperclass());
}

private static String toProfileName(String fileName, String profile) {
int i = fileName.lastIndexOf('.');
if (i > -1) {
return fileName.substring(0, i) + "-" + profile + fileName.substring(i);
}
return fileName + "-" + profile;
}

@Override
public ConfigBuilder addDefaultSources() {
useDefaultSources = true;
Expand Down Expand Up @@ -237,25 +247,14 @@ private void processMetaSources(List<io.helidon.config.Config> configs) {
for (io.helidon.config.Config config : configs) {
String type = config.get("type").asString()
.orElseThrow(() -> new ConfigException("Meta configuration sources must have a \"type\" property defined"));
// in MP, we have a hardcoded list of supported configuration source types
List<ConfigSource> delegates;
switch (type) {
case "system-properties":
delegates = List.of(MpConfigSources.systemProperties());
break;
case "environment-variables":
delegates = List.of(MpConfigSources.environmentVariables());
break;
case "properties":
delegates = propertiesSource(config);
break;
case "yaml":
delegates = yamlSource(config);
break;
default:
throw new ConfigException("Meta configuration source type \"" + type + "\" is not supported. Use on of: "
+ "system-properties, environment-variables, properties, yaml");
MpMetaConfigProvider mpMetaConfigProvider = MP_META_PROVIDERS.get(type);
if (mpMetaConfigProvider == null) {
throw new ConfigException("Wrong meta configuration, type " + type
+ " not supported, only supporting: " + MP_META_PROVIDERS.keySet());
}

List<? extends ConfigSource> delegates = mpMetaConfigProvider.create(type, config, profile);

boolean shouldCount = delegates.size() > 1;
int counter = 0;

Expand All @@ -279,163 +278,6 @@ private void processMetaSources(List<io.helidon.config.Config> configs) {
}
}

private List<ConfigSource> propertiesSource(io.helidon.config.Config config) {
return sourceFromMeta(config,
MpConfigSources::create,
MpConfigSources::classPath,
MpConfigSources::classPath,
MpConfigSources::create);
}

private List<ConfigSource> yamlSource(io.helidon.config.Config config) {
return sourceFromMeta(config,
YamlMpConfigSource::create,
YamlMpConfigSource::classPath,
YamlMpConfigSource::classPath,
YamlMpConfigSource::create);
}

private List<ConfigSource> sourceFromMeta(io.helidon.config.Config config,
Function<Path, ConfigSource> fromPath,
Function<String, List<ConfigSource>> fromClasspath,
BiFunction<String, String, List<ConfigSource>> fromClasspathWithProfile,
Function<URL, ConfigSource> fromUrl) {

boolean optional = config.get("optional").asBoolean().orElse(false);

String location;
Exception cause = null;

ConfigValue<Path> pathConfig = config.get("path").as(Path.class);
if (pathConfig.isPresent()) {
Path path = pathConfig.get();
List<ConfigSource> result = sourceFromPathMeta(path, fromPath);

if (!result.isEmpty()) {
return result;
}
// else the file was not found, check optional
location = "path " + path.toAbsolutePath();
} else {
ConfigValue<String> classpathConfig = config.get("classpath").as(String.class);
if (classpathConfig.isPresent()) {
String classpath = classpathConfig.get();
List<ConfigSource> sources;

if (profile == null) {
sources = fromClasspath.apply(classpath);
} else {
sources = fromClasspathWithProfile.apply(classpath, profile);
}

if (!sources.isEmpty()) {
return sources;
}
location = "classpath " + classpath;
} else {
ConfigValue<URL> urlConfig = config.get("url").as(URL.class);
if (urlConfig.isPresent()) {
URL url = urlConfig.get();
List<ConfigSource> sources = null;
try {
sources = sourceFromUrlMeta(url, fromUrl);
} catch (ConfigException e) {
cause = e;
}

if (sources != null && !sources.isEmpty()) {
return sources;
}
location = "url " + url;
} else {
throw new ConfigException("MP meta configuration does not contain config source location. Node: " + config
.key());
}
}
}

if (optional) {
return List.of();
}
String message = "Meta configuration could not find non-optional config source on " + location;
if (cause == null) {
throw new ConfigException(message);
} else {
throw new ConfigException(message, cause);
}
}

private List<ConfigSource> sourceFromUrlMeta(URL url, Function<URL, ConfigSource> fromUrl) {
ConfigSource profileSource = null;
ConfigSource mainSource = null;
Exception cause = null;

if (profile != null) {
try {
String profileUrl = toProfileName(url.toString(), profile);
profileSource = fromUrl.apply(new URL(profileUrl));
} catch (Exception e) {
cause = e;
}
}

try {
mainSource = fromUrl.apply(url);
if (cause != null) {
LOGGER.log(Level.FINEST, "Failed to load profile URL resource, succeeded loading main from " + url, cause);
}
} catch (ConfigException e) {
if (cause != null) {
e.addSuppressed(cause);
throw e;
} else {
if (profileSource == null) {
throw e;
} else {
LOGGER.log(Level.FINEST, "Did not find main URL config source from " + url + ", have profile source", e);
}
}
}
return composite(mainSource, profileSource);
}

private List<ConfigSource> sourceFromPathMeta(Path path, Function<Path, ConfigSource> fromPath) {
ConfigSource profileSource = null;
ConfigSource mainSource = null;

if (profile != null) {
Path fileNamePath = path.getFileName();
String fileName = (fileNamePath == null ? "" : fileNamePath.toString());
fileName = toProfileName(fileName, profile);
Path profileSpecific = path.resolveSibling(fileName);
if (Files.exists(profileSpecific) && Files.isRegularFile(profileSpecific)) {
profileSource = fromPath.apply(profileSpecific);
}
}

if (Files.exists(path) && Files.isRegularFile(path)) {
mainSource = fromPath.apply(path);
}

// now handle profile
return composite(mainSource, profileSource);
}

private List<ConfigSource> composite(ConfigSource mainSource, ConfigSource profileSource) {
// now handle profile
if (profileSource == null) {
if (mainSource == null) {
return List.of();
}
return List.of(mainSource);
}
if (mainSource == null) {
return List.of(profileSource);
}

return List.of(MpConfigSources.composite(profileSource, mainSource));
}

@Override
public Config build() {
// the build method MUST NOT modify builder state, as it may be called more than once
Expand Down Expand Up @@ -474,6 +316,10 @@ public Config build() {
Collections.reverse(ordinalSources);
Collections.reverse(ordinalConverters);

if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("The following config sources are used (ordered): " + ordinalSources);
}

List<ConfigSource> targetSources = new LinkedList<>();
HashMap<Class<?>, Converter<?>> targetConverters = new HashMap<>();

Expand All @@ -484,6 +330,9 @@ public Config build() {

// if we already have a profile configured, we have loaded it and can safely return
if (profile != null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Built MP config for profile " + profile);
}
return result;
}

Expand All @@ -492,7 +341,12 @@ public Config build() {

// nope, return the result
if (configuredProfile == null) {
LOGGER.fine("Built MP config with no profile");
return result;
} else {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("MP profile configured, rebuilding: " + configuredProfile);
}
}

// yes, update it and re-build with profile information
Expand Down Expand Up @@ -545,6 +399,10 @@ private void addDefaultSources(List<OrdinalSource> targetConfigSources) {
.map(OrdinalSource::new)
.forEach(targetConfigSources::add);
}

if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("The following default config sources discovered: " + targetConfigSources);
}
}
}

Expand Down Expand Up @@ -627,7 +485,7 @@ private OrdinalSource(ConfigSource source) {
}

private OrdinalSource(ConfigSource source, int ordinal) {
this.ordinal = ordinal;
this.ordinal = findOrdinal(source, ordinal);
this.source = source;
}

Expand Down
Loading