diff --git a/bom/pom.xml b/bom/pom.xml
index 62b5f4eca..7940cf898 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -421,11 +421,6 @@
helidon-microprofile-config${helidon.version}
-
- io.helidon.microprofile.config
- helidon-microprofile-config-cdi
- ${helidon.version}
- io.helidon.microprofilehelidon-microprofile-fault-tolerance
diff --git a/common/common/src/main/java/io/helidon/common/GenericType.java b/common/common/src/main/java/io/helidon/common/GenericType.java
index 8fa32dcce..a8faa73b4 100644
--- a/common/common/src/main/java/io/helidon/common/GenericType.java
+++ b/common/common/src/main/java/io/helidon/common/GenericType.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -171,4 +171,9 @@ public class GenericType implements Type {
return false;
}
+
+ @Override
+ public String toString() {
+ return type.toString();
+ }
}
diff --git a/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java b/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java
index 8ffea1d4c..49fc5f34c 100644
--- a/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java
+++ b/common/service-loader/src/main/java/io/helidon/common/serviceloader/HelidonServiceLoader.java
@@ -28,8 +28,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import javax.annotation.Priority;
-
import io.helidon.common.Prioritized;
/**
@@ -338,7 +336,7 @@ public final class HelidonServiceLoader implements Iterable {
}
private ServiceWithPriority(T service) {
- this(service, findPriority(service));
+ this(service, Priorities.find(service, Prioritized.DEFAULT_PRIORITY));
}
private int priority() {
@@ -353,17 +351,6 @@ public final class HelidonServiceLoader implements Iterable {
return instance.getClass().getName();
}
- private static int findPriority(Object o) {
- if (o instanceof Prioritized) {
- return ((Prioritized) o).priority();
- }
- Priority prio = o.getClass().getAnnotation(Priority.class);
- if (null == prio) {
- return Prioritized.DEFAULT_PRIORITY;
- }
- return prio.value();
- }
-
@Override
public String toString() {
return instance.toString();
diff --git a/common/service-loader/src/main/java/io/helidon/common/serviceloader/Priorities.java b/common/service-loader/src/main/java/io/helidon/common/serviceloader/Priorities.java
new file mode 100644
index 000000000..411e1a138
--- /dev/null
+++ b/common/service-loader/src/main/java/io/helidon/common/serviceloader/Priorities.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.common.serviceloader;
+
+import java.util.Comparator;
+import java.util.List;
+
+import javax.annotation.Priority;
+
+import io.helidon.common.Prioritized;
+
+/**
+ * Priority utilities.
+ */
+public final class Priorities {
+ private Priorities() {
+ }
+
+ /**
+ * Find priority from class annotation, or return default if none found.
+ *
+ * @param aClass class to analyzed
+ * @param defaultPriority default priority of this class
+ * @return priority from {@link Priority}or the default provided
+ */
+ public static int find(Class> aClass, int defaultPriority) {
+ Priority priorityAnnot = aClass.getAnnotation(Priority.class);
+ if (null != priorityAnnot) {
+ return priorityAnnot.value();
+ }
+
+ return defaultPriority;
+ }
+
+ /**
+ * Find priority for an instance.
+ * First checks if instance is {@link io.helidon.common.Prioritized}. If so,
+ * uses the value from it.
+ * Then checks for {@link Priority} annotation.
+ * If none of the above is found, returns the default priority.
+ *
+ * @param anObject object to find priority for
+ * @param defaultPriority default priority to use
+ * @return priority of the object or default provided
+ */
+ public static int find(Object anObject, int defaultPriority) {
+ if (anObject instanceof Class) {
+ return find((Class>) anObject, defaultPriority);
+ }
+ if (anObject instanceof Prioritized) {
+ return ((Prioritized) anObject).priority();
+ }
+ Priority prio = anObject.getClass().getAnnotation(Priority.class);
+ if (null == prio) {
+ return defaultPriority;
+ }
+ return prio.value();
+ }
+
+ /**
+ * Sort the prioritized list based on priorities.
+ * @param list list to sort
+ */
+ public static void sort(List extends Prioritized> list) {
+ list.sort(Comparator.comparingInt(Prioritized::priority));
+ }
+
+ /**
+ * Sort the list based on priorities.
+ *
+ *
If element implements {@link io.helidon.common.Prioritized}, uses its priority.
+ *
If element is a class and has annotation {@link javax.annotation.Priority}, uses its priority
+ *
If element is any object and its class has annotation {@link javax.annotation.Priority}, uses its priority
+ *
+ * @param list list to sort
+ * @param defaultPriority default priority for elements that do not have it
+ */
+ public static void sort(List> list, int defaultPriority) {
+ list.sort(Comparator.comparingInt(it -> {
+ if (it instanceof Class) {
+ return find((Class>) it, defaultPriority);
+ } else {
+ return find(it, defaultPriority);
+ }
+ }));
+ }
+}
diff --git a/config/config/pom.xml b/config/config/pom.xml
index ebae0851a..daa589cb6 100644
--- a/config/config/pom.xml
+++ b/config/config/pom.xml
@@ -50,10 +50,18 @@
io.helidon.commonhelidon-common-reactive
+
+ io.helidon.common
+ helidon-common-media-type
+ io.projectreactorreactor-core
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ org.junit.jupiterjunit-jupiter-api
diff --git a/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java b/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java
index d45226d3d..967dc2a71 100644
--- a/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java
+++ b/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java
@@ -17,11 +17,17 @@
package io.helidon.config;
import java.time.Instant;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -29,11 +35,13 @@ import java.util.logging.Logger;
import io.helidon.common.reactive.Flow;
import io.helidon.config.internal.ConfigKeyImpl;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
/**
* Abstract common implementation of {@link Config} extended by appropriate Config node types:
* {@link ConfigListImpl}, {@link ConfigMissingImpl}, {@link ConfigObjectImpl}, {@link ConfigLeafImpl}.
*/
-abstract class AbstractConfigImpl implements Config {
+abstract class AbstractConfigImpl implements Config, org.eclipse.microprofile.config.Config {
public static final Logger LOGGER = Logger.getLogger(AbstractConfigImpl.class.getName());
@@ -47,6 +55,9 @@ abstract class AbstractConfigImpl implements Config {
private final ConfigMapperManager mapperManager;
private volatile Flow.Subscriber subscriber;
private final ReentrantReadWriteLock subscriberLock = new ReentrantReadWriteLock();
+ private final AtomicReference latestConfig = new AtomicReference<>();
+ private boolean useSystemProperties;
+ private boolean useEnvironmentVariables;
/**
* Initializes Config implementation.
@@ -121,7 +132,7 @@ abstract class AbstractConfigImpl implements Config {
@Override
public T convert(Class type, String value) throws ConfigMappingException {
- return mapperManager.map(value, type, "");
+ return mapperManager.map(value, type, key().toString());
}
@Override
@@ -172,6 +183,118 @@ abstract class AbstractConfigImpl implements Config {
}
}
+ /*
+ * MicroProfile Config methods
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public T getValue(String propertyName, Class propertyType) {
+ Config config = latestConfig.get();
+ try {
+ return mpFindValue(config, propertyName, propertyType);
+ } catch (MissingValueException e) {
+ throw new NoSuchElementException(e.getMessage());
+ } catch (ConfigMappingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public Optional getOptionalValue(String propertyName, Class propertyType) {
+ try {
+ return Optional.of(getValue(propertyName, propertyType));
+ } catch (NoSuchElementException e) {
+ return Optional.empty();
+ } catch (ConfigMappingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public Iterable getPropertyNames() {
+ Set keys = new HashSet<>(latestConfig.get()
+ .asMap()
+ .orElseGet(Collections::emptyMap)
+ .keySet());
+
+ if (useSystemProperties) {
+ keys.addAll(System.getProperties().stringPropertyNames());
+ }
+
+ return keys;
+ }
+
+ @Override
+ public Iterable getConfigSources() {
+ Config config = latestConfig.get();
+ if (null == config) {
+ // maybe we are in progress of initializing this config (e.g. filter processing)
+ config = this;
+ }
+
+ if (config instanceof AbstractConfigImpl) {
+ return ((AbstractConfigImpl) config).mpConfigSources();
+ }
+ return Collections.emptyList();
+ }
+
+ private Iterable mpConfigSources() {
+ return new LinkedList<>(factory.mpConfigSources());
+ }
+
+ private T mpFindValue(Config config, String propertyName, Class propertyType) {
+ // TODO this is a hardcoded fix for TCK tests that expect system properties to be mutable
+ // Helidon config does the same, yet with a slight delay (polling reasons)
+ // we need to check if system properties are enabled and first - if so, do this
+
+ String property = null;
+ if (useSystemProperties) {
+ property = System.getProperty(propertyName);
+ }
+
+ if (null == property) {
+ ConfigValue value = config
+ .get(propertyName)
+ .as(propertyType);
+
+ if (value.isPresent()) {
+ return value.get();
+ }
+
+ // try to find in env vars
+ if (useEnvironmentVariables) {
+ T envVar = mpFindEnvVar(config, propertyName, propertyType);
+ if (null != envVar) {
+ return envVar;
+ }
+ }
+
+ return value.get();
+ } else {
+ return config.get(propertyName).convert(propertyType, property);
+ }
+ }
+
+ private T mpFindEnvVar(Config config, String propertyName, Class propertyType) {
+ String result = System.getenv(propertyName);
+
+ // now let's resolve all variants required by the specification
+ if (null == result) {
+ for (String alias : EnvironmentVariableAliases.aliasesOf(propertyName)) {
+ result = System.getenv(alias);
+ if (null != result) {
+ break;
+ }
+ }
+ }
+
+ if (null != result) {
+ return config.convert(propertyType, result);
+ }
+
+ return null;
+ }
+
/**
* We should wait for a subscription, otherwise, we might miss some changes.
*/
@@ -218,12 +341,56 @@ abstract class AbstractConfigImpl implements Config {
return factory;
}
-
@Override
public Flow.Publisher changes() {
return changesPublisher;
}
+ void initMp() {
+ this.latestConfig.set(this);
+
+ List configSources = factory.configSources();
+ if (configSources.isEmpty()) {
+ // if no config sources, then no changes
+ return;
+ }
+ if (configSources.size() == 1) {
+ if (configSources.get(0) == ConfigSources.EmptyConfigSourceHolder.EMPTY) {
+ // if the only config source is the empty one, then no changes
+ return;
+ }
+ }
+
+ io.helidon.config.spi.ConfigSource first = configSources.get(0);
+ if (first instanceof BuilderImpl.HelidonSourceWrapper) {
+ first = ((BuilderImpl.HelidonSourceWrapper) first).unwrap();
+ }
+
+ if (first instanceof ConfigSources.SystemPropertiesConfigSource) {
+ this.useSystemProperties = true;
+ }
+
+ for (io.helidon.config.spi.ConfigSource configSource : configSources) {
+ io.helidon.config.spi.ConfigSource it = configSource;
+ if (it instanceof BuilderImpl.HelidonSourceWrapper) {
+ it = ((BuilderImpl.HelidonSourceWrapper) it).unwrap();
+ }
+
+ if (it instanceof ConfigSources.EnvironmentVariablesConfigSource) {
+ // there is an env var config source
+ this.useEnvironmentVariables = true;
+ break;
+ }
+ }
+
+ // TODO this must be changed, as otherwise we would not get changes in MP
+ // and why did it work when the MpConfig was a separate implementation?
+ // onChange(newConfig -> {
+ // // TODO this does not work - seems that when there is more than one subscriber, the events are not delivered
+ // latestConfig.set(newConfig);
+ // });
+ }
+
/**
* {@link Flow.Publisher} implementation that filters general {@link ConfigFactory#changes()} events to be wrapped by
* {@link FilteringConfigChangeEventSubscriber} for appropriate Config key and subscribers on the config node.
diff --git a/config/config/src/main/java/io/helidon/config/BuilderImpl.java b/config/config/src/main/java/io/helidon/config/BuilderImpl.java
index 5a2d067af..941f9f752 100644
--- a/config/config/src/main/java/io/helidon/config/BuilderImpl.java
+++ b/config/config/src/main/java/io/helidon/config/BuilderImpl.java
@@ -16,14 +16,18 @@
package io.helidon.config;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;
@@ -35,49 +39,86 @@ import javax.annotation.Priority;
import io.helidon.common.CollectionsHelper;
import io.helidon.common.GenericType;
+import io.helidon.common.Prioritized;
import io.helidon.common.reactive.Flow;
+import io.helidon.common.serviceloader.HelidonServiceLoader;
+import io.helidon.common.serviceloader.Priorities;
import io.helidon.config.ConfigMapperManager.MapperProviders;
import io.helidon.config.internal.ConfigThreadFactory;
import io.helidon.config.internal.ConfigUtils;
+import io.helidon.config.spi.AbstractMpSource;
+import io.helidon.config.spi.AbstractSource;
import io.helidon.config.spi.ConfigContext;
import io.helidon.config.spi.ConfigFilter;
import io.helidon.config.spi.ConfigMapper;
import io.helidon.config.spi.ConfigMapperProvider;
+import io.helidon.config.spi.ConfigNode;
import io.helidon.config.spi.ConfigParser;
import io.helidon.config.spi.ConfigSource;
import io.helidon.config.spi.OverrideSource;
-import static io.helidon.config.ConfigSources.classpath;
+import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
+import org.eclipse.microprofile.config.spi.Converter;
/**
* {@link Config} Builder implementation.
*/
class BuilderImpl implements Config.Builder {
-
static final Executor DEFAULT_CHANGES_EXECUTOR = Executors.newCachedThreadPool(new ConfigThreadFactory("config"));
- private static final List DEFAULT_FILE_EXTENSIONS = Arrays.asList("yaml", "conf", "json", "properties");
- private List sources;
+ /*
+ * Config sources
+ */
+ // sources to be sorted by priority
+ private final List prioritizedSources = new ArrayList<>();
+ // sources "pre-sorted" - all user defined sources without priority will be ordered
+ // as added
+ private final List sources = new LinkedList<>();
+ private boolean configSourceServicesEnabled;
+ /*
+ * Config mapper providers
+ */
+ private final List prioritizedMappers = new ArrayList<>();
private final MapperProviders mapperProviders;
private boolean mapperServicesEnabled;
+ private boolean mpMapperServicesEnabled;
+ /*
+ * Config parsers
+ */
private final List parsers;
private boolean parserServicesEnabled;
+ /*
+ * Config filters
+ */
private final List> filterProviders;
private boolean filterServicesEnabled;
- private boolean cachingEnabled;
+ /*
+ * Changes (TODO to be removed)
+ */
private Executor changesExecutor;
private int changesMaxBuffer;
+ /*
+ * Other configuration.
+ */
+ private OverrideSource overrideSource;
+ private ClassLoader classLoader;
+ /*
+ * Other switches
+ */
+ private boolean cachingEnabled;
private boolean keyResolving;
private boolean systemPropertiesSourceEnabled;
private boolean environmentVariablesSourceEnabled;
- private OverrideSource overrideSource;
private boolean envVarAliasGeneratorEnabled;
+ private boolean mpDiscoveredSourcesAdded;
+ private boolean mpDiscoveredConvertersAdded;
BuilderImpl() {
- sources = null;
+ configSourceServicesEnabled = true;
overrideSource = OverrideSources.empty();
mapperProviders = MapperProviders.create();
mapperServicesEnabled = true;
+ mpMapperServicesEnabled = true;
parsers = new ArrayList<>();
parserServicesEnabled = true;
filterProviders = new ArrayList<>();
@@ -92,14 +133,30 @@ class BuilderImpl implements Config.Builder {
}
@Override
- public Config.Builder sources(List> sourceSuppliers) {
- sources = new ArrayList<>(sourceSuppliers.size());
- sourceSuppliers.stream().map(Supplier::get).forEach(source -> {
- sources.add(source);
- if (source instanceof ConfigSources.EnvironmentVariablesConfigSource) {
- envVarAliasGeneratorEnabled = true;
- }
- });
+ public Config.Builder disableSourceServices() {
+ this.configSourceServicesEnabled = false;
+ return this;
+ }
+
+ @Override
+ public Config.Builder sources(List> sourceSuppliers) {
+ // replace current config sources with the ones provided
+ sources.clear();
+ prioritizedSources.clear();
+
+ sourceSuppliers.stream()
+ .map(Supplier::get)
+ .forEach(this::addSource);
+
+ return this;
+ }
+
+ @Override
+ public Config.Builder addSource(ConfigSource source) {
+ sources.add(source);
+ if (source instanceof ConfigSources.EnvironmentVariablesConfigSource) {
+ envVarAliasGeneratorEnabled = true;
+ }
return this;
}
@@ -115,6 +172,10 @@ class BuilderImpl implements Config.Builder {
return this;
}
+ void disableMpMapperServices() {
+ this.mpMapperServicesEnabled = false;
+ }
+
@Override
public Config.Builder addStringMapper(Class type, Function mapper) {
Objects.requireNonNull(type);
@@ -250,43 +311,201 @@ class BuilderImpl implements Config.Builder {
}
@Override
- public Config build() {
+ public AbstractConfigImpl build() {
+ if (configSourceServicesEnabled) {
+ // add MP config sources from service loader (if not already done)
+ mpAddDiscoveredSources();
+ }
+ if (mpMapperServicesEnabled) {
+ // add MP discovered converters from service loader (if not already done)
+ mpAddDiscoveredConverters();
+ }
+
return buildProvider().newConfig();
}
@Override
- public Config.Builder mappersFrom(Config config) {
- if (config instanceof AbstractConfigImpl) {
- ConfigMapperManager mapperManager = ((AbstractConfigImpl) config).mapperManager();
- addMapper(new ConfigMapperProvider() {
- @Override
- public Map, Function> mappers() {
- return CollectionsHelper.mapOf();
- }
+ public Config.Builder config(Config metaConfig) {
+ metaConfig.get("caching.enabled").asBoolean().ifPresent(this::cachingEnabled);
+ metaConfig.get("key-resolving.enabled").asBoolean().ifPresent(this::keyResolvingEnabled);
+ metaConfig.get("value-resolving.enabled").asBoolean().ifPresent(this::valueResolvingEnabled);
+ metaConfig.get("parsers.enabled").asBoolean().ifPresent(this::parserServicesEnabled);
+ metaConfig.get("mappers.enabled").asBoolean().ifPresent(this::mapperServicesEnabled);
+ metaConfig.get("config-source-services.enabled").asBoolean().ifPresent(this::configSourceServicesEnabled);
- @Override
- public Optional> mapper(GenericType type) {
- Optional extends BiFunction> mapper = mapperManager.mapper(type);
- return Optional.ofNullable(mapper.orElse(null));
- }
- });
- } else {
- throw new ConfigException("Unexpected configuration implementation used to copy mappers: "
- + config.getClass().getName());
+ disableSystemPropertiesSource();
+ disableEnvironmentVariablesSource();
+
+ List sourceList = new LinkedList<>();
+
+ metaConfig.get("sources")
+ .asNodeList()
+ .ifPresent(list -> list.forEach(it -> sourceList.add(MetaConfig.configSource(it))));
+
+ sourceList.forEach(this::addSource);
+ sourceList.clear();
+
+ Config overrideConfig = metaConfig.get("override-source");
+ if (overrideConfig.exists()) {
+ overrides(() -> MetaConfig.overrideSource(overrideConfig));
}
return this;
}
+ private void configSourceServicesEnabled(boolean enabled) {
+ this.configSourceServicesEnabled = enabled;
+ }
+
+ void mpWithConverters(Converter>... converters) {
+ for (Converter> converter : converters) {
+ addMpConverter(converter);
+ }
+ }
+
+ void mpWithConverter(Class type, int ordinal, Converter converter) {
+ // priority 1 is highest, 100 is default
+ // MP ordinal 1 is lowest, 100 is default
+
+ // 100 - priority 1
+ // 101 - priority 0
+ int priority = 101 - ordinal;
+ prioritizedMappers.add(new MpConverterWrapper(type, converter, priority));
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addMpConverter(Converter converter) {
+ Class type = (Class) getTypeOfMpConverter(converter.getClass());
+ if (type == null) {
+ throw new IllegalStateException("Converter " + converter.getClass() + " must be a ParameterizedType");
+ }
+
+ mpWithConverter(type,
+ Priorities.find(converter.getClass(), 100),
+ converter);
+ }
+
+ void mpAddDefaultSources() {
+ prioritizedSources.add(new HelidonSourceWrapper(ConfigSources.systemProperties(), 100));
+ prioritizedSources.add(new HelidonSourceWrapper(ConfigSources.environmentVariables(), 100));
+ prioritizedSources.add(new HelidonSourceWrapper(ConfigSources.classpath("application.yaml").optional().build(), 100));
+ ConfigSources.classpathAll("META-INF/microprofile-config.properties")
+ .stream()
+ .map(AbstractSource.Builder::build)
+ .map(source -> new HelidonSourceWrapper(source, 100))
+ .forEach(prioritizedSources::add);
+ }
+
+ void mpAddDiscoveredSources() {
+ if (mpDiscoveredSourcesAdded) {
+ return;
+ }
+ this.mpDiscoveredSourcesAdded = true;
+ this.configSourceServicesEnabled = true;
+ final ClassLoader usedCl = ((null == classLoader) ? Thread.currentThread().getContextClassLoader() : classLoader);
+
+ List mpSources = new LinkedList<>();
+
+ // service loader MP sources
+ HelidonServiceLoader
+ .create(ServiceLoader.load(org.eclipse.microprofile.config.spi.ConfigSource.class, usedCl))
+ .forEach(mpSources::add);
+
+ // config source providers
+ HelidonServiceLoader.create(ServiceLoader.load(ConfigSourceProvider.class, usedCl))
+ .forEach(csp -> csp.getConfigSources(usedCl)
+ .forEach(mpSources::add));
+
+ for (org.eclipse.microprofile.config.spi.ConfigSource source : mpSources) {
+ prioritizedSources.add(new MpSourceWrapper(source));
+ }
+ }
+
+ void mpAddDiscoveredConverters() {
+ if (mpDiscoveredConvertersAdded) {
+ return;
+ }
+ this.mpDiscoveredConvertersAdded = true;
+ this.mpMapperServicesEnabled = true;
+
+ final ClassLoader usedCl = ((null == classLoader) ? Thread.currentThread().getContextClassLoader() : classLoader);
+
+ HelidonServiceLoader.create(ServiceLoader.load(Converter.class, usedCl))
+ .forEach(this::addMpConverter);
+ }
+
+ void mpForClassLoader(ClassLoader loader) {
+ this.classLoader = loader;
+ }
+
+ void mpWithSources(org.eclipse.microprofile.config.spi.ConfigSource... sources) {
+ for (org.eclipse.microprofile.config.spi.ConfigSource source : sources) {
+ PrioritizedConfigSource pcs;
+
+ if (source instanceof AbstractMpSource) {
+ pcs = new HelidonSourceWrapper((AbstractMpSource>) source);
+ } else {
+ pcs = new MpSourceWrapper(source);
+ }
+ this.prioritizedSources.add(pcs);
+ }
+ }
+
+ private Type getTypeOfMpConverter(Class> clazz) {
+ if (clazz.equals(Object.class)) {
+ return null;
+ }
+
+ Type[] genericInterfaces = clazz.getGenericInterfaces();
+ for (Type genericInterface : genericInterfaces) {
+ if (genericInterface instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericInterface;
+ if (pt.getRawType().equals(Converter.class)) {
+ Type[] typeArguments = pt.getActualTypeArguments();
+ if (typeArguments.length != 1) {
+ throw new IllegalStateException("Converter " + clazz + " must be a ParameterizedType.");
+ }
+ return typeArguments[0];
+ }
+ }
+ }
+
+ return getTypeOfMpConverter(clazz.getSuperclass());
+ }
+
+ private void cachingEnabled(boolean enabled) {
+ this.cachingEnabled = enabled;
+ }
+
+ private void mapperServicesEnabled(Boolean aBoolean) {
+ this.mapperServicesEnabled = aBoolean;
+ }
+
+ private void parserServicesEnabled(Boolean aBoolean) {
+ parserServicesEnabled = aBoolean;
+ }
+
+ private void valueResolvingEnabled(Boolean aBoolean) {
+ // TODO this is a noop as is disableValueResolving
+ }
+
+ private void keyResolvingEnabled(Boolean aBoolean) {
+ this.keyResolving = aBoolean;
+ }
+
private ProviderImpl buildProvider() {
//context
ConfigContext context = new ConfigContextImpl(buildParsers(parserServicesEnabled, parsers));
//source
- ConfigSource targetConfigSource = targetConfigSource(context);
+ ConfigSourceConfiguration targetConfigSource = targetConfigSource(context);
//mappers
+ Priorities.sort(prioritizedMappers);
+ // as the mapperProviders.add adds the last as first, we need to reverse order
+ Collections.reverse(prioritizedMappers);
+ prioritizedMappers.forEach(mapperProviders::add);
ConfigMapperManager configMapperManager = buildMappers(mapperServicesEnabled, mapperProviders);
if (filterServicesEnabled) {
@@ -294,8 +513,8 @@ class BuilderImpl implements Config.Builder {
}
Function> aliasGenerator = envVarAliasGeneratorEnabled
- ? EnvironmentVariableAliases::aliasesOf
- : null;
+ ? EnvironmentVariableAliases::aliasesOf
+ : null;
//config provider
return createProvider(configMapperManager,
@@ -309,9 +528,14 @@ class BuilderImpl implements Config.Builder {
aliasGenerator);
}
- private ConfigSource targetConfigSource(ConfigContext context) {
+ private ConfigSourceConfiguration targetConfigSource(ConfigContext context) {
List targetSources = new LinkedList<>();
+ if (systemPropertiesSourceEnabled
+ && !hasSourceType(ConfigSources.SystemPropertiesConfigSource.class)) {
+ targetSources.add(ConfigSources.systemProperties());
+ }
+
if (hasSourceType(ConfigSources.EnvironmentVariablesConfigSource.class)) {
envVarAliasGeneratorEnabled = true;
} else if (environmentVariablesSourceEnabled) {
@@ -319,25 +543,30 @@ class BuilderImpl implements Config.Builder {
envVarAliasGeneratorEnabled = true;
}
- if (systemPropertiesSourceEnabled
- && !hasSourceType(ConfigSources.SystemPropertiesConfigSource.class)) {
- targetSources.add(ConfigSources.systemProperties());
- }
-
- if (sources != null) {
+ if (sources.isEmpty()) {
+ // if there are no sources configured, use meta-configuration
+ targetSources.addAll(MetaConfig.configSources(mediaType -> context.findParser(mediaType).isPresent()));
+ } else {
targetSources.addAll(sources);
- } else {
- targetSources.add(defaultConfigSource());
}
- final ConfigSource targetConfigSource;
- if (targetSources.size() == 1) {
- targetConfigSource = targetSources.get(0);
- } else {
- targetConfigSource = ConfigSources.create(targetSources.toArray(new ConfigSource[0])).build();
+ // initialize all target sources
+ targetSources.forEach(it -> it.init(context));
+
+ if (!prioritizedSources.isEmpty()) {
+ // initialize all prioritized sources (before we sort them - otherwise we cannot get priority)
+ prioritizedSources.forEach(it -> it.init(context));
+ Priorities.sort(prioritizedSources);
+ targetSources.addAll(prioritizedSources);
}
- targetConfigSource.init(context);
- return targetConfigSource;
+
+ if (targetSources.size() == 1) {
+ // the only source does not require a composite wrapper
+ return new ConfigSourceConfiguration(targetSources.get(0), targetSources);
+ }
+
+ return new ConfigSourceConfiguration(ConfigSources.create(targetSources.toArray(new ConfigSource[0])).build(),
+ targetSources);
}
private boolean hasSourceType(Class> sourceType) {
@@ -353,7 +582,7 @@ class BuilderImpl implements Config.Builder {
@SuppressWarnings("ParameterNumber")
ProviderImpl createProvider(ConfigMapperManager configMapperManager,
- ConfigSource targetConfigSource,
+ ConfigSourceConfiguration targetConfigSource,
OverrideSource overrideSource,
List> filterProviders,
boolean cachingEnabled,
@@ -375,39 +604,6 @@ class BuilderImpl implements Config.Builder {
//
// utils
//
-
- private static ConfigSource defaultConfigSource() {
- final List sources = new ArrayList<>();
- final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- final List meta = defaultConfigSources(classLoader, "meta-config");
- if (!meta.isEmpty()) {
- sources.add(ConfigSources.load(toDefaultConfigSource(meta)).build());
- }
- sources.addAll(defaultConfigSources(classLoader, "application"));
- return ConfigSources.create(toDefaultConfigSource(sources)).build();
- }
-
- private static List defaultConfigSources(final ClassLoader classLoader, final String baseResourceName) {
- final List sources = new ArrayList<>();
- for (final String extension : DEFAULT_FILE_EXTENSIONS) {
- final String resource = baseResourceName + "." + extension;
- if (classLoader.getResource(resource) != null) {
- sources.add(classpath(resource).optional().build());
- }
- }
- return sources;
- }
-
- private static ConfigSource toDefaultConfigSource(final List sources) {
- if (sources.isEmpty()) {
- return ConfigSources.empty();
- } else if (sources.size() == 1) {
- return sources.get(0);
- } else {
- return new UseFirstAvailableConfigSource(sources);
- }
- }
-
static List buildParsers(boolean servicesEnabled, List userDefinedParsers) {
List parsers = new LinkedList<>(userDefinedParsers);
if (servicesEnabled) {
@@ -520,8 +716,11 @@ class BuilderImpl implements Config.Builder {
}
static final Config EMPTY = new BuilderImpl()
+ // the empty config source is needed, so we do not look for meta config or default
+ // config sources
.sources(ConfigSources.empty())
.overrides(OverrideSources.empty())
+ .disableSourceServices()
.disableEnvironmentVariablesSource()
.disableSystemPropertiesSource()
.disableParserServices()
@@ -546,4 +745,261 @@ class BuilderImpl implements Config.Builder {
return converterMap;
}
}
+
+ private interface PrioritizedMapperProvider extends Prioritized,
+ ConfigMapperProvider {
+ }
+
+ private static final class MpConverterWrapper implements PrioritizedMapperProvider {
+ private final Map, Function> converterMap = new HashMap<>();
+ private final Converter> converter;
+ private final int priority;
+
+ private MpConverterWrapper(Class> theClass,
+ Converter> converter,
+ int priority) {
+ this.converter = converter;
+ this.priority = priority;
+ this.converterMap.put(theClass, config -> {
+ return config.asString().as(converter::convert).get();
+ });
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public Map, Function> mappers() {
+ return converterMap;
+ }
+
+ @Override
+ public String toString() {
+ return converter.toString();
+ }
+ }
+
+ private static final class HelidonMapperWrapper implements PrioritizedMapperProvider {
+ private final ConfigMapperProvider delegate;
+ private final int priority;
+
+ private HelidonMapperWrapper(ConfigMapperProvider delegate, int priority) {
+ this.delegate = delegate;
+ this.priority = priority;
+ }
+
+ @Override
+ public int priority() {
+ return priority;
+ }
+
+ @Override
+ public Map, Function> mappers() {
+ return delegate.mappers();
+ }
+
+ @Override
+ public Map, BiFunction> genericTypeMappers() {
+ return delegate.genericTypeMappers();
+ }
+
+ @Override
+ public Optional> mapper(Class type) {
+ return delegate.mapper(type);
+ }
+
+ @Override
+ public Optional> mapper(GenericType type) {
+ return delegate.mapper(type);
+ }
+ }
+
+ private interface PrioritizedConfigSource extends Prioritized,
+ ConfigSource,
+ org.eclipse.microprofile.config.spi.ConfigSource {
+
+ }
+
+ private static final class MpSourceWrapper implements PrioritizedConfigSource {
+ private final org.eclipse.microprofile.config.spi.ConfigSource delegate;
+
+ private MpSourceWrapper(org.eclipse.microprofile.config.spi.ConfigSource delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public int priority() {
+ String value = delegate.getValue(CONFIG_ORDINAL);
+ if (null != value) {
+ return 101 - Integer.parseInt(value);
+ }
+
+ // priority from Prioritized and annotation (MP has it reversed)
+ return 101 - Priorities.find(delegate, 100);
+ }
+
+ @Override
+ public Optional load() throws ConfigException {
+ return Optional.of(ConfigUtils.mapToObjectNode(getProperties(), false));
+ }
+
+ @Override
+ public Map getProperties() {
+ return delegate.getProperties();
+ }
+
+ @Override
+ public String getValue(String propertyName) {
+ return delegate.getValue(propertyName);
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public String description() {
+ return delegate.toString();
+ }
+
+ @Override
+ public String toString() {
+ return description();
+ }
+ }
+
+ static final class HelidonSourceWrapper implements PrioritizedConfigSource {
+
+ private final AbstractMpSource> delegate;
+ private Integer explicitPriority;
+
+ private HelidonSourceWrapper(AbstractMpSource> delegate) {
+ this.delegate = delegate;
+ }
+
+ private HelidonSourceWrapper(AbstractMpSource> delegate, int explicitPriority) {
+ this.delegate = delegate;
+ this.explicitPriority = explicitPriority;
+ }
+
+ AbstractMpSource> unwrap() {
+ return delegate;
+ }
+
+ @Override
+ public int priority() {
+ // ordinal from data
+ String value = delegate.getValue(CONFIG_ORDINAL);
+ if (null != value) {
+ return 101 - Integer.parseInt(value);
+ }
+
+ if (null != explicitPriority) {
+ return explicitPriority;
+ }
+
+ // priority from Prioritized and annotation
+ return Priorities.find(delegate, 100);
+ }
+
+ @Override
+ public Map getProperties() {
+ return delegate.getProperties();
+ }
+
+ @Override
+ public String getValue(String propertyName) {
+ return delegate.getValue(propertyName);
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public Set getPropertyNames() {
+ return delegate.getPropertyNames();
+ }
+
+ @Override
+ public String description() {
+ return delegate.description();
+ }
+
+ @Override
+ public Optional load() throws ConfigException {
+ return delegate.load();
+ }
+
+ @Override
+ public ConfigSource get() {
+ return delegate.get();
+ }
+
+ @Override
+ public void init(ConfigContext context) {
+ delegate.init(context);
+ }
+
+ @Override
+ public Flow.Publisher> changes() {
+ return delegate.changes();
+ }
+
+ @Override
+ public void close() throws Exception {
+ delegate.close();
+ }
+
+ @Override
+ public String toString() {
+ return description();
+ }
+ }
+
+ static final class ConfigSourceConfiguration {
+ private static final ConfigSourceConfiguration EMPTY =
+ new ConfigSourceConfiguration(ConfigSources.empty(), CollectionsHelper.listOf(ConfigSources.empty()));
+ private final ConfigSource compositeSource;
+ private final List allSources;
+
+ private ConfigSourceConfiguration(ConfigSource compositeSource, List allSources) {
+ this.compositeSource = compositeSource;
+ this.allSources = allSources;
+ }
+
+ static ConfigSourceConfiguration empty() {
+ return EMPTY;
+ }
+
+ ConfigSource compositeSource() {
+ return compositeSource;
+ }
+
+ List allSources() {
+ return allSources;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ConfigSourceConfiguration that = (ConfigSourceConfiguration) o;
+ return compositeSource.equals(that.compositeSource)
+ && allSources.equals(that.allSources);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(compositeSource, allSources);
+ }
+ }
}
diff --git a/config/config/src/main/java/io/helidon/config/internal/ClasspathConfigSource.java b/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java
similarity index 69%
rename from config/config/src/main/java/io/helidon/config/internal/ClasspathConfigSource.java
rename to config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java
index 1c68696e4..a00af674d 100644
--- a/config/config/src/main/java/io/helidon/config/internal/ClasspathConfigSource.java
+++ b/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,25 +14,14 @@
* limitations under the License.
*/
-package io.helidon.config.internal;
+package io.helidon.config;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
-import java.util.Objects;
import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import io.helidon.common.OptionalHelper;
-import io.helidon.config.Config;
-import io.helidon.config.ConfigException;
-import io.helidon.config.ConfigHelper;
-import io.helidon.config.ConfigMappingException;
-import io.helidon.config.MissingValueException;
import io.helidon.config.spi.AbstractParsableConfigSource;
import io.helidon.config.spi.ConfigParser;
import io.helidon.config.spi.ConfigSource;
@@ -44,9 +33,6 @@ import io.helidon.config.spi.PollingStrategy;
* @see AbstractParsableConfigSource.Builder
*/
public class ClasspathConfigSource extends AbstractParsableConfigSource {
-
- private static final Logger LOGGER = Logger.getLogger(ClasspathConfigSource.class.getName());
-
private static final String RESOURCE_KEY = "resource";
private final String resource;
@@ -66,7 +52,7 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource
*
*
{@code resource} - type {@code String}
*
- * Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#init(Config)}.
+ * Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#config(io.helidon.config.Config)}.
*
* @param metaConfig meta-configuration used to initialize returned config source instance from.
* @return new instance of config source described by {@code metaConfig}
@@ -75,14 +61,23 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource
* @throws ConfigMappingException in case the mapper fails to map the (existing) configuration tree represented by the
* supplied configuration node to an instance of a given Java type.
* @see io.helidon.config.ConfigSources#classpath(String)
- * @see AbstractParsableConfigSource.Builder#init(Config)
+ * @see AbstractParsableConfigSource.Builder#config(Config)
*/
public static ClasspathConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException {
- return (ClasspathConfigSource) new ClasspathBuilder(metaConfig.get(RESOURCE_KEY).asString().get())
- .init(metaConfig)
+ return builder()
+ .config(metaConfig)
.build();
}
+ /**
+ * Create a new fluent API builder for classpath config source.
+ *
+ * @return a new builder instance
+ */
+ public static ClasspathBuilder builder() {
+ return new ClasspathBuilder();
+ }
+
@Override
protected String uid() {
return ClasspathSourceHelper.uid(resource);
@@ -107,24 +102,11 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource
@Override
protected ConfigParser.Content content() throws ConfigException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- InputStream inputStream = classLoader.getResourceAsStream(resource);
- if (inputStream == null) {
- LOGGER.log(Level.FINE,
- String.format("Error to get %s using %s CONTEXT ClassLoader.", description(), classLoader));
- throw new ConfigException(description() + " does not exist. Used ClassLoader: " + classLoader);
- }
- Optional resourceTimestamp = Optional.ofNullable(ClasspathSourceHelper.resourceTimestamp(resource));
- try {
- LOGGER.log(Level.FINE,
- String.format("Getting content from '%s'. Last modified at %s. Used ClassLoader: %s",
- ClasspathSourceHelper.resourcePath(resource), resourceTimestamp, classLoader));
- } catch (Exception ex) {
- LOGGER.log(Level.FINE, "Error to get resource '" + resource + "' path. Used ClassLoader: " + classLoader, ex);
- }
- return ConfigParser.Content.create(new InputStreamReader(inputStream, StandardCharsets.UTF_8),
- mediaType(),
- resourceTimestamp);
+ return ClasspathSourceHelper.content(resource,
+ description(),
+ (inputStreamReader, instant) -> ConfigParser.Content.create(inputStreamReader,
+ mediaType(),
+ instant));
}
/**
@@ -143,26 +125,40 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource
*
* If {@code media-type} not set it tries to guess it from resource extension.
*/
- public static final class ClasspathBuilder extends Builder {
+ public static final class ClasspathBuilder extends Builder {
private String resource;
/**
* Initialize builder.
- *
- * @param resource classpath resource name
*/
- public ClasspathBuilder(String resource) {
+ private ClasspathBuilder() {
super(Path.class);
-
- Objects.requireNonNull(resource, "resource name cannot be null");
-
- this.resource = resource;
}
+ /**
+ * Configure the classpath resource to load the configuration from.
+ *
+ * @param resource resource on classpath
+ * @return updated builder instance
+ */
+ public ClasspathBuilder resource(String resource) {
+ this.resource = resource;
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
{@code resource} - the classpath resource to load
+ *
+ * @param metaConfig configuration properties used to configure a builder instance.
+ * @return updated builder instance
+ */
@Override
- protected ClasspathBuilder init(Config metaConfig) {
- return super.init(metaConfig);
+ public ClasspathBuilder config(Config metaConfig) {
+ metaConfig.get(RESOURCE_KEY).asString().ifPresent(this::resource);
+ return super.config(metaConfig);
}
@Override
@@ -187,7 +183,10 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource
* @return new instance of Classpath ConfigSource.
*/
@Override
- public ConfigSource build() {
+ public ClasspathConfigSource build() {
+ if (null == resource) {
+ throw new IllegalArgumentException("resource must be defined");
+ }
return new ClasspathConfigSource(this, resource);
}
diff --git a/config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java b/config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java
similarity index 57%
rename from config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java
rename to config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java
index 4bbe1ec12..4df790f70 100644
--- a/config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java
+++ b/config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,12 @@
* limitations under the License.
*/
-package io.helidon.config.internal;
+package io.helidon.config;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
-import java.util.Objects;
import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import io.helidon.config.ConfigException;
import io.helidon.config.spi.AbstractOverrideSource;
import io.helidon.config.spi.OverrideSource;
import io.helidon.config.spi.PollingStrategy;
@@ -38,16 +30,15 @@ import io.helidon.config.spi.PollingStrategy;
* @see Builder
*/
public class ClasspathOverrideSource extends AbstractOverrideSource {
- private static final Logger LOGGER = Logger.getLogger(ClasspathOverrideSource.class.getName());
-
private final String resource;
- ClasspathOverrideSource(ClasspathBuilder builder, String resource) {
+ ClasspathOverrideSource(ClasspathBuilder builder) {
super(builder);
+ String builderResource = builder.resource;
- this.resource = resource.startsWith("/")
- ? resource.substring(1)
- : resource;
+ this.resource = builderResource.startsWith("/")
+ ? builderResource.substring(1)
+ : builderResource;
}
@Override
@@ -62,29 +53,31 @@ public class ClasspathOverrideSource extends AbstractOverrideSource {
@Override
public Data loadData() throws ConfigException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- InputStream inputStream = classLoader.getResourceAsStream(resource);
- if (inputStream == null) {
- LOGGER.log(Level.FINE,
- String.format("Error to get %s using %s CONTEXT ClassLoader.", description(), classLoader));
- throw new ConfigException(description() + " does not exist. Used ClassLoader: " + classLoader);
- }
- Instant resourceTimestamp = ClasspathSourceHelper.resourceTimestamp(resource);
- try {
- LOGGER.log(Level.FINE,
- String.format("Getting content from '%s'. Last modified at %s. Used ClassLoader: %s",
- ClasspathSourceHelper.resourcePath(resource), resourceTimestamp, classLoader));
- } catch (Exception ex) {
- LOGGER.log(Level.FINE, "Error to get resource '" + resource + "' path. Used ClassLoader: " + classLoader, ex);
- }
- try {
- return new Data<>(
- Optional.of(OverrideData.create(new InputStreamReader(inputStream, StandardCharsets.UTF_8))),
- Optional.ofNullable(resourceTimestamp)
- );
- } catch (IOException e) {
- throw new ConfigException("Cannot load dta from resource.", e);
- }
+ return ClasspathSourceHelper.content(resource,
+ description(),
+ (inputStreamReader, instant) -> {
+ return new Data<>(
+ Optional.of(OverrideData.create(inputStreamReader)),
+ instant);
+ });
+ }
+
+ /**
+ * Create a new classpath override source from meta configuration, containing {@code resource} key and other options.
+ * @param config meta configuration
+ * @return a new classpath override source
+ */
+ public static ClasspathOverrideSource create(Config config) {
+ return builder().config(config).build();
+ }
+
+ /**
+ * Create a new fluent API builder.
+ *
+ * @return a new builder
+ */
+ public static ClasspathBuilder builder() {
+ return new ClasspathBuilder();
}
/**
@@ -105,19 +98,39 @@ public class ClasspathOverrideSource extends AbstractOverrideSource {
/**
* Initialize builder.
- *
- * @param resource classpath resource name
*/
- public ClasspathBuilder(String resource) {
+ private ClasspathBuilder() {
super(Path.class);
+ }
- Objects.requireNonNull(resource, "resource name cannot be null");
-
+ /**
+ * Configure the classpath resource to be used as a source.
+ *
+ * @param resource classpath resource path
+ * @return updated builder instance
+ */
+ public ClasspathBuilder resource(String resource) {
this.resource = resource;
+ return this;
+ }
+
+ /**
+ * Update builder from meta configuration.
+ *
+ * @param metaConfig meta configuration to load this override source from
+ * @return updated builder instance
+ */
+ public ClasspathBuilder config(Config metaConfig) {
+ metaConfig.get("resource").asString().ifPresent(this::resource);
+ return super.config(metaConfig);
}
@Override
protected Path target() {
+ if (null == resource) {
+ throw new IllegalArgumentException("Resource name must be defined.");
+ }
+
try {
Path resourcePath = ClasspathSourceHelper.resourcePath(resource);
if (resourcePath != null) {
@@ -136,8 +149,8 @@ public class ClasspathOverrideSource extends AbstractOverrideSource {
* @return new instance of Classpath OverrideSource.
*/
@Override
- public OverrideSource build() {
- return new ClasspathOverrideSource(this, resource);
+ public ClasspathOverrideSource build() {
+ return new ClasspathOverrideSource(this);
}
PollingStrategy pollingStrategyInternal() { //just for testing purposes
diff --git a/config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java b/config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java
similarity index 66%
rename from config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java
rename to config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java
index b250d86f7..a89745f1b 100644
--- a/config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java
+++ b/config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java
@@ -14,22 +14,27 @@
* limitations under the License.
*/
-package io.helidon.config.internal;
+package io.helidon.config;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
+import java.util.Optional;
+import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utilities for file-related source classes.
*
- * @see ClasspathConfigSource
+ * @see io.helidon.config.ClasspathConfigSource
* @see ClasspathOverrideSource
*/
class ClasspathSourceHelper {
@@ -84,4 +89,29 @@ class ClasspathSourceHelper {
}
return Instant.EPOCH;
}
+
+ static T content(String resource,
+ String description,
+ BiFunction, T> processor) throws ConfigException {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ InputStream inputStream = classLoader.getResourceAsStream(resource);
+
+ if (inputStream == null) {
+ LOGGER.log(Level.FINE,
+ String.format("Error to get %s using %s CONTEXT ClassLoader.", description, classLoader));
+ throw new ConfigException(description + " does not exist. Used ClassLoader: " + classLoader);
+ }
+ Optional resourceTimestamp = Optional.ofNullable(resourceTimestamp(resource));
+ try {
+ LOGGER.log(Level.FINE,
+ String.format("Getting content from '%s'. Last modified at %s. Used ClassLoader: %s",
+ resourcePath(resource), resourceTimestamp, classLoader));
+ } catch (Exception ex) {
+ LOGGER.log(Level.FINE, "Error to get resource '" + resource + "' path. Used ClassLoader: " + classLoader, ex);
+ }
+
+ return processor.apply(new InputStreamReader(inputStream, StandardCharsets.UTF_8), resourceTimestamp);
+ }
+
}
diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java
index c3748e465..df7fe4dd2 100644
--- a/config/config/src/main/java/io/helidon/config/Config.java
+++ b/config/config/src/main/java/io/helidon/config/Config.java
@@ -269,30 +269,6 @@ public interface Config {
return BuilderImpl.EmptyConfigHolder.EMPTY;
}
- /**
- * Create an empty configuration with mappers copied from another config.
- *
- * @param config config to get mappers from
- * @return an empty config instance (empty Object)
- */
- static Config empty(Config config) {
-
- return new BuilderImpl()
- .sources(ConfigSources.empty())
- .overrides(OverrideSources.empty())
- .disableEnvironmentVariablesSource()
- .disableSystemPropertiesSource()
- .disableMapperServices()
- .disableParserServices()
- .disableFilterServices()
- .mappersFrom(config)
- .build();
- }
-
- //
- // tree (config nodes) method
- //
-
/**
* Returns a new default {@link Config} loaded using one of the
* configuration files available on the classpath and/or using the runtime
@@ -353,10 +329,9 @@ public interface Config {
* {@code Config} instance as desired.
*
* @return new instance of {@link Config}
- * @see #loadSourcesFrom(Supplier[])
*/
static Config create() {
- return builder().build();
+ return builder().metaConfig().build();
}
/**
@@ -375,9 +350,7 @@ public interface Config {
*
* @param configSources ordered list of configuration sources
* @return new instance of {@link Config}
- * @see #loadSourcesFrom(Supplier[])
* @see #builder(Supplier[])
- * @see #builderLoadSourcesFrom(Supplier[])
* @see Builder#sources(List)
* @see Builder#disableEnvironmentVariablesSource()
* @see Builder#disableSystemPropertiesSource()
@@ -386,29 +359,10 @@ public interface Config {
* @see ConfigSources.MergingStrategy
*/
@SafeVarargs
- static Config create(Supplier... configSources) {
+ static Config create(Supplier extends ConfigSource>... configSources) {
return builder(configSources).build();
}
- /**
- * Creates a new {@link Config} loaded from the specified
- * {@link ConfigSource}s representing meta-configurations.
- *
- * See {@link ConfigSource#create(Config)} for more information about the
- * format of meta-configuration.
- *
- * @param metaSources ordered list of meta sources
- * @return new instance of {@link Config}
- * @see #create(Supplier[])
- * @see #builder(Supplier[])
- * @see #builderLoadSourcesFrom(Supplier[])
- * @see ConfigSources#load(Supplier[])
- */
- @SafeVarargs
- static Config loadSourcesFrom(Supplier... metaSources) {
- return builderLoadSourcesFrom(metaSources).build();
- }
-
/**
* Provides a {@link Builder} for creating a {@link Config}
* based on the specified {@link ConfigSource} instances.
@@ -427,8 +381,6 @@ public interface Config {
* @return new initialized Builder instance
* @see #builder()
* @see #create(Supplier[])
- * @see #loadSourcesFrom(Supplier[])
- * @see #builderLoadSourcesFrom(Supplier[])
* @see Builder#sources(List)
* @see Builder#disableEnvironmentVariablesSource()
* @see Builder#disableSystemPropertiesSource()
@@ -437,32 +389,10 @@ public interface Config {
* @see ConfigSources.MergingStrategy
*/
@SafeVarargs
- static Builder builder(Supplier... configSources) {
+ static Builder builder(Supplier extends ConfigSource>... configSources) {
return builder().sources(CollectionsHelper.listOf(configSources));
}
- /**
- * Provides a {@link Builder} for creating a {@link Config} based on the
- * specified {@link ConfigSource}s representing meta-configurations.
- *
- * Each meta-configuration source should set the {@code sources} property to
- * be an array of config sources. See {@link ConfigSource#create(Config)} for
- * more information about the format of meta-configuration.
- *
- * @param metaSources ordered list of meta sources
- * @return new initialized Builder instance
- * @see #builder()
- * @see #builder(Supplier[])
- * @see ConfigSources#load(Supplier[])
- * @see #loadSourcesFrom(Supplier[])
- */
- @SafeVarargs
- static Builder builderLoadSourcesFrom(Supplier... metaSources) {
- return builder(ConfigSources.load(metaSources))
- .disableSystemPropertiesSource()
- .disableEnvironmentVariablesSource();
- }
-
/**
* Provides a {@link Builder} for creating a {@link Config} instance.
*
@@ -1281,6 +1211,13 @@ public interface Config {
* @see ConfigFilter
*/
interface Builder {
+ /**
+ * Disable loading of config sources from Java service loader.
+ * This disables loading of MicroProfile Config sources.
+ *
+ * @return updated builder instance
+ */
+ Builder disableSourceServices();
/**
* Sets ordered list of {@link ConfigSource} instance to be used as single source of configuration
@@ -1317,7 +1254,19 @@ public interface Config {
* @see ConfigSources.CompositeBuilder
* @see ConfigSources.MergingStrategy
*/
- Builder sources(List> configSources);
+ Builder sources(List> configSources);
+
+ /**
+ * Add a config source to the list of sources.
+ *
+ * @param source to add
+ * @return updated builder instance
+ */
+ Builder addSource(ConfigSource source);
+
+ default Builder addSource(Supplier extends ConfigSource> source) {
+ return addSource(source.get());
+ }
/**
* Sets a {@link ConfigSource} instance to be used as a source of configuration to be wrapped into {@link Config} API.
@@ -1338,7 +1287,7 @@ public interface Config {
* @see #disableEnvironmentVariablesSource()
* @see #disableSystemPropertiesSource()
*/
- default Builder sources(Supplier configSource) {
+ default Builder sources(Supplier extends ConfigSource> configSource) {
sources(CollectionsHelper.listOf(configSource));
return this;
}
@@ -1364,8 +1313,8 @@ public interface Config {
* @see #disableEnvironmentVariablesSource()
* @see #disableSystemPropertiesSource()
*/
- default Builder sources(Supplier configSource,
- Supplier configSource2) {
+ default Builder sources(Supplier extends ConfigSource> configSource,
+ Supplier extends ConfigSource> configSource2) {
sources(CollectionsHelper.listOf(configSource, configSource2));
return this;
}
@@ -1392,9 +1341,9 @@ public interface Config {
* @see #disableEnvironmentVariablesSource()
* @see #disableSystemPropertiesSource()
*/
- default Builder sources(Supplier configSource,
- Supplier configSource2,
- Supplier configSource3) {
+ default Builder sources(Supplier extends ConfigSource> configSource,
+ Supplier extends ConfigSource> configSource2,
+ Supplier extends ConfigSource> configSource3) {
sources(CollectionsHelper.listOf(configSource, configSource2, configSource3));
return this;
}
@@ -1697,12 +1646,143 @@ public interface Config {
Config build();
/**
- * Add mappers from another config instance.
- * This may be useful if we need the same conversion behavior.
+ * Check if meta configuration is present and if so, update this builder using
+ * the meta configuration.
*
- * @param config config to extract mappers from
* @return updated builder instance
+ * @see #config(Config)
*/
- Builder mappersFrom(Config config);
+ default Builder metaConfig() {
+ MetaConfig.metaConfig()
+ .ifPresent(this::config);
+
+ return this;
+ }
+
+ /**
+ * Configure this config builder from meta configuration.
+ *
+ * The following configuration options are supported in a meta configuration file:
+ *
+ *
+ *
Meta configuration
+ *
+ *
key
+ *
default value
+ *
description
+ *
reference
+ *
+ *
+ *
caching.enabled
+ *
{@code true}
+ *
Enable or disable caching of results of filters.
+ *
{@link #disableCaching()}
+ *
+ *
+ *
key-resolving.enabled
+ *
{@code true}
+ *
Enable or disable resolving of placeholders in keys.
+ *
{@link #disableKeyResolving()}
+ *
+ *
+ *
value-resolving.enabled
+ *
{@code true}
+ *
Enable or disable resolving of placeholders in values.
+ *
{@link #disableValueResolving()}
+ *
+ *
+ *
parsers.enabled
+ *
{@code true}
+ *
Enable or disable parser services.
+ *
{@link #disableParserServices()}
+ *
+ *
+ *
mappers.enabled
+ *
{@code true}
+ *
Enable or disable mapper services.
+ *
{@link #disableMapperServices()}
+ *
+ *
+ *
override-source
+ *
none
+ *
Configure an override source. Same as config source configuration (see below)
+ *
{@link #overrides(java.util.function.Supplier)}
+ *
+ *
+ *
sources
+ *
Default config sources are prefixed {@code application}, and suffix is the first available of
+ * {@code yaml, conf, json, properties}
+ *
Configure config sources to be used by the application. This node contains the array of objects defining
+ * config sources