mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
Config for Helidon 2.0 (#1102)
* Meta configuration refactoring. * Using meta configuration in MicroProfile Server * Refactored config sources to use proper builder pattern. * Native image substitution removed as no longer needed. * Documentation update for Meta configuration. * MP config in SE * Order system properties and environment variables as in MP
This commit is contained in:
@@ -421,11 +421,6 @@
|
||||
<artifactId>helidon-microprofile-config</artifactId>
|
||||
<version>${helidon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.config</groupId>
|
||||
<artifactId>helidon-microprofile-config-cdi</artifactId>
|
||||
<version>${helidon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile</groupId>
|
||||
<artifactId>helidon-microprofile-fault-tolerance</artifactId>
|
||||
|
||||
@@ -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<T> implements Type {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> implements Iterable<T> {
|
||||
}
|
||||
|
||||
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<T> implements Iterable<T> {
|
||||
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();
|
||||
|
||||
@@ -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.
|
||||
* <ul>
|
||||
* <li>If element implements {@link io.helidon.common.Prioritized}, uses its priority.</li>
|
||||
* <li>If element is a class and has annotation {@link javax.annotation.Priority}, uses its priority</li>
|
||||
* <li>If element is any object and its class has annotation {@link javax.annotation.Priority}, uses its priority</li>
|
||||
* </ul>
|
||||
* @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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,18 @@
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-media-type</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
@@ -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<ConfigDiff> subscriber;
|
||||
private final ReentrantReadWriteLock subscriberLock = new ReentrantReadWriteLock();
|
||||
private final AtomicReference<Config> latestConfig = new AtomicReference<>();
|
||||
private boolean useSystemProperties;
|
||||
private boolean useEnvironmentVariables;
|
||||
|
||||
/**
|
||||
* Initializes Config implementation.
|
||||
@@ -121,7 +132,7 @@ abstract class AbstractConfigImpl implements Config {
|
||||
|
||||
@Override
|
||||
public <T> T convert(Class<T> 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> T getValue(String propertyName, Class<T> 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 <T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType) {
|
||||
try {
|
||||
return Optional.of(getValue(propertyName, propertyType));
|
||||
} catch (NoSuchElementException e) {
|
||||
return Optional.empty();
|
||||
} catch (ConfigMappingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getPropertyNames() {
|
||||
Set<String> keys = new HashSet<>(latestConfig.get()
|
||||
.asMap()
|
||||
.orElseGet(Collections::emptyMap)
|
||||
.keySet());
|
||||
|
||||
if (useSystemProperties) {
|
||||
keys.addAll(System.getProperties().stringPropertyNames());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigSource> 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<ConfigSource> mpConfigSources() {
|
||||
return new LinkedList<>(factory.mpConfigSources());
|
||||
}
|
||||
|
||||
private <T> T mpFindValue(Config config, String propertyName, Class<T> 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<T> 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> T mpFindEnvVar(Config config, String propertyName, Class<T> 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<Config> changes() {
|
||||
return changesPublisher;
|
||||
}
|
||||
|
||||
void initMp() {
|
||||
this.latestConfig.set(this);
|
||||
|
||||
List<io.helidon.config.spi.ConfigSource> 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.
|
||||
|
||||
@@ -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<String> DEFAULT_FILE_EXTENSIONS = Arrays.asList("yaml", "conf", "json", "properties");
|
||||
|
||||
private List<ConfigSource> sources;
|
||||
/*
|
||||
* Config sources
|
||||
*/
|
||||
// sources to be sorted by priority
|
||||
private final List<PrioritizedConfigSource> prioritizedSources = new ArrayList<>();
|
||||
// sources "pre-sorted" - all user defined sources without priority will be ordered
|
||||
// as added
|
||||
private final List<ConfigSource> sources = new LinkedList<>();
|
||||
private boolean configSourceServicesEnabled;
|
||||
/*
|
||||
* Config mapper providers
|
||||
*/
|
||||
private final List<PrioritizedMapperProvider> prioritizedMappers = new ArrayList<>();
|
||||
private final MapperProviders mapperProviders;
|
||||
private boolean mapperServicesEnabled;
|
||||
private boolean mpMapperServicesEnabled;
|
||||
/*
|
||||
* Config parsers
|
||||
*/
|
||||
private final List<ConfigParser> parsers;
|
||||
private boolean parserServicesEnabled;
|
||||
/*
|
||||
* Config filters
|
||||
*/
|
||||
private final List<Function<Config, ConfigFilter>> 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<Supplier<ConfigSource>> 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<Supplier<? extends ConfigSource>> 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 <T> Config.Builder addStringMapper(Class<T> type, Function<String, T> 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<Class<?>, Function<Config, ?>> 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 <T> Optional<BiFunction<Config, ConfigMapper, T>> mapper(GenericType<T> type) {
|
||||
Optional<? extends BiFunction<Config, ConfigMapper, T>> 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<ConfigSource> 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);
|
||||
}
|
||||
}
|
||||
|
||||
<T> void mpWithConverter(Class<T> type, int ordinal, Converter<T> 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 <T> void addMpConverter(Converter<T> converter) {
|
||||
Class<T> type = (Class<T>) 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<org.eclipse.microprofile.config.spi.ConfigSource> 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<String, List<String>> 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<ConfigSource> 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<Function<Config, ConfigFilter>> filterProviders,
|
||||
boolean cachingEnabled,
|
||||
@@ -375,39 +604,6 @@ class BuilderImpl implements Config.Builder {
|
||||
//
|
||||
// utils
|
||||
//
|
||||
|
||||
private static ConfigSource defaultConfigSource() {
|
||||
final List<ConfigSource> sources = new ArrayList<>();
|
||||
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
final List<ConfigSource> 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<ConfigSource> defaultConfigSources(final ClassLoader classLoader, final String baseResourceName) {
|
||||
final List<ConfigSource> 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<ConfigSource> sources) {
|
||||
if (sources.isEmpty()) {
|
||||
return ConfigSources.empty();
|
||||
} else if (sources.size() == 1) {
|
||||
return sources.get(0);
|
||||
} else {
|
||||
return new UseFirstAvailableConfigSource(sources);
|
||||
}
|
||||
}
|
||||
|
||||
static List<ConfigParser> buildParsers(boolean servicesEnabled, List<ConfigParser> userDefinedParsers) {
|
||||
List<ConfigParser> 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<Class<?>, Function<Config, ?>> 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<Class<?>, Function<Config, ?>> 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<Class<?>, Function<Config, ?>> mappers() {
|
||||
return delegate.mappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GenericType<?>, BiFunction<Config, ConfigMapper, ?>> genericTypeMappers() {
|
||||
return delegate.genericTypeMappers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<Function<Config, T>> mapper(Class<T> type) {
|
||||
return delegate.mapper(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<BiFunction<Config, ConfigMapper, T>> mapper(GenericType<T> 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<ConfigNode.ObjectNode> load() throws ConfigException {
|
||||
return Optional.of(ConfigUtils.mapToObjectNode(getProperties(), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> 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<String, String> getProperties() {
|
||||
return delegate.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
return delegate.getValue(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyNames() {
|
||||
return delegate.getPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return delegate.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConfigNode.ObjectNode> 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<Optional<ConfigNode.ObjectNode>> 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<ConfigSource> allSources;
|
||||
|
||||
private ConfigSourceConfiguration(ConfigSource compositeSource, List<ConfigSource> allSources) {
|
||||
this.compositeSource = compositeSource;
|
||||
this.allSources = allSources;
|
||||
}
|
||||
|
||||
static ConfigSourceConfiguration empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
ConfigSource compositeSource() {
|
||||
return compositeSource;
|
||||
}
|
||||
|
||||
List<ConfigSource> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Instant> {
|
||||
|
||||
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<Instant>
|
||||
* <ul>
|
||||
* <li>{@code resource} - type {@code String}</li>
|
||||
* </ul>
|
||||
* 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<Instant>
|
||||
* @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<Instant>
|
||||
|
||||
@Override
|
||||
protected ConfigParser.Content<Instant> 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<Instant> 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<Instant>
|
||||
* <p>
|
||||
* If {@code media-type} not set it tries to guess it from resource extension.
|
||||
*/
|
||||
public static final class ClasspathBuilder extends Builder<ClasspathBuilder, Path> {
|
||||
public static final class ClasspathBuilder extends Builder<ClasspathBuilder, Path, ClasspathConfigSource> {
|
||||
|
||||
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}
|
||||
* <ul>
|
||||
* <li>{@code resource} - the classpath resource to load</li>
|
||||
* </ul>
|
||||
* @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<Instant>
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -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<Instant> {
|
||||
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<Instant> {
|
||||
|
||||
@Override
|
||||
public Data<OverrideData, Instant> 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<Instant> {
|
||||
|
||||
/**
|
||||
* 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<Instant> {
|
||||
* @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
|
||||
@@ -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> T content(String resource,
|
||||
String description,
|
||||
BiFunction<InputStreamReader, Optional<Instant>, 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<Instant> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ConfigSource>... 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.
|
||||
* <p>
|
||||
* 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<ConfigSource>... 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<ConfigSource>... 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.
|
||||
* <p>
|
||||
* 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<ConfigSource>... 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<Supplier<ConfigSource>> configSources);
|
||||
Builder sources(List<Supplier<? extends ConfigSource>> 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> 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> configSource,
|
||||
Supplier<ConfigSource> 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> configSource,
|
||||
Supplier<ConfigSource> configSource2,
|
||||
Supplier<ConfigSource> 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.
|
||||
* <p>
|
||||
* The following configuration options are supported in a meta configuration file:
|
||||
*
|
||||
* <table class="config">
|
||||
* <caption>Meta configuration</caption>
|
||||
* <tr>
|
||||
* <th>key</th>
|
||||
* <th>default value</th>
|
||||
* <th>description</th>
|
||||
* <th>reference</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>caching.enabled</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Enable or disable caching of results of filters.</td>
|
||||
* <td>{@link #disableCaching()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>key-resolving.enabled</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Enable or disable resolving of placeholders in keys.</td>
|
||||
* <td>{@link #disableKeyResolving()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>value-resolving.enabled</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Enable or disable resolving of placeholders in values.</td>
|
||||
* <td>{@link #disableValueResolving()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>parsers.enabled</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Enable or disable parser services.</td>
|
||||
* <td>{@link #disableParserServices()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>mappers.enabled</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Enable or disable mapper services.</td>
|
||||
* <td>{@link #disableMapperServices()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>override-source</td>
|
||||
* <td>none</td>
|
||||
* <td>Configure an override source. Same as config source configuration (see below)</td>
|
||||
* <td>{@link #overrides(java.util.function.Supplier)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>sources</td>
|
||||
* <td>Default config sources are prefixed {@code application}, and suffix is the first available of
|
||||
* {@code yaml, conf, json, properties}</td>
|
||||
* <td>Configure config sources to be used by the application. This node contains the array of objects defining
|
||||
* config sources</td>
|
||||
* <td>{@link #addSource(io.helidon.config.spi.ConfigSource)}</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* Config source configuration options:
|
||||
* <table class="config">
|
||||
* <caption>Config source</caption>
|
||||
* <tr>
|
||||
* <th>key</th>
|
||||
* <th>default value</th>
|
||||
* <th>description</th>
|
||||
* <th>reference</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>type</td>
|
||||
* <td> </td>
|
||||
* <td>Type of a config source - a string supported by a provider.</td>
|
||||
* <td>{@link io.helidon.config.spi.ConfigSourceProvider#create(String, Config)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>properties</td>
|
||||
* <td> </td>
|
||||
* <td>Configuration options to configure the config source (meta configuration of a source)</td>
|
||||
* <td>{@link io.helidon.config.spi.ConfigSourceProvider#create(String, Config)},
|
||||
* {@link MetaConfig#configSource(Config)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>properties.optional</td>
|
||||
* <td>false</td>
|
||||
* <td>Most config sources can be configured to be optional</td>
|
||||
* <td>{@link io.helidon.config.spi.AbstractSource.Builder#optional(boolean)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>properties.polling-strategy</td>
|
||||
* <td> </td>
|
||||
* <td>Some config sources can have a polling strategy defined</td>
|
||||
* <td>{@link io.helidon.config.spi.AbstractSource.Builder#pollingStrategy(java.util.function.Function)},
|
||||
* {@link MetaConfig#pollingStrategy(Config)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>properties.retry-policy</td>
|
||||
* <td> </td>
|
||||
* <td>Some config sources can have a retry policy defined</td>
|
||||
* <td>{@link io.helidon.config.spi.AbstractSource.Builder#retryPolicy(io.helidon.config.spi.RetryPolicy)},
|
||||
* {@link MetaConfig#retryPolicy(Config)}</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* Full meta configuration example:
|
||||
* <pre>
|
||||
* sources:
|
||||
* - type: "system-properties"
|
||||
* - type: "environment-variables"
|
||||
* - type: "file"
|
||||
* properties:
|
||||
* optional: true
|
||||
* path: "conf/dev-application.yaml"
|
||||
* polling-strategy:
|
||||
* type: "watch"
|
||||
* retry-policy:
|
||||
* type: "repeat"
|
||||
* properties:
|
||||
* retries: 5
|
||||
* - type: "classpath"
|
||||
* properties:
|
||||
* optional: true
|
||||
* resource: "application.yaml"
|
||||
* </pre>
|
||||
*/
|
||||
Builder config(Config metaConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,22 @@ import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.time.Instant;
|
||||
import java.util.AbstractMap;
|
||||
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.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.internal.ConfigKeyImpl;
|
||||
import io.helidon.config.spi.ConfigFilter;
|
||||
@@ -49,9 +55,11 @@ final class ConfigFactory {
|
||||
private final ConfigFilter filter;
|
||||
private final ProviderImpl provider;
|
||||
private final Function<String, List<String>> aliasGenerator;
|
||||
private final ConcurrentMap<PrefixedKey, Reference<Config>> configCache;
|
||||
private final ConcurrentMap<PrefixedKey, Reference<AbstractConfigImpl>> configCache;
|
||||
private final Flow.Publisher<ConfigDiff> changesPublisher;
|
||||
private final Instant timestamp;
|
||||
private final List<ConfigSource> configSources;
|
||||
private final List<org.eclipse.microprofile.config.spi.ConfigSource> mpConfigSources;
|
||||
|
||||
/**
|
||||
* Create new instance of the factory operating on specified {@link ConfigSource}.
|
||||
@@ -66,7 +74,9 @@ final class ConfigFactory {
|
||||
ObjectNode node,
|
||||
ConfigFilter filter,
|
||||
ProviderImpl provider,
|
||||
Function<String, List<String>> aliasGenerator) {
|
||||
Function<String, List<String>> aliasGenerator,
|
||||
List<ConfigSource> configSources) {
|
||||
|
||||
Objects.requireNonNull(mapperManager, "mapperManager argument is null.");
|
||||
Objects.requireNonNull(node, "node argument is null.");
|
||||
Objects.requireNonNull(filter, "filter argument is null.");
|
||||
@@ -77,10 +87,15 @@ final class ConfigFactory {
|
||||
this.filter = filter;
|
||||
this.provider = provider;
|
||||
this.aliasGenerator = aliasGenerator;
|
||||
this.configSources = configSources;
|
||||
|
||||
configCache = new ConcurrentHashMap<>();
|
||||
changesPublisher = new FilteringConfigChangeEventPublisher(provider.changes());
|
||||
timestamp = Instant.now();
|
||||
|
||||
this.mpConfigSources = configSources.stream()
|
||||
.map(ConfigFactory::toMpSource)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Map<ConfigKeyImpl, ConfigNode> createFullKeyToNodeMap(ObjectNode objectNode) {
|
||||
@@ -123,7 +138,7 @@ final class ConfigFactory {
|
||||
*
|
||||
* @return root {@link Config}
|
||||
*/
|
||||
public Config config() {
|
||||
public AbstractConfigImpl config() {
|
||||
return config(ConfigKeyImpl.of(), ConfigKeyImpl.of());
|
||||
}
|
||||
|
||||
@@ -134,9 +149,9 @@ final class ConfigFactory {
|
||||
* @param key config key
|
||||
* @return {@code key} specific instance of {@link Config}
|
||||
*/
|
||||
public Config config(ConfigKeyImpl prefix, ConfigKeyImpl key) {
|
||||
public AbstractConfigImpl config(ConfigKeyImpl prefix, ConfigKeyImpl key) {
|
||||
PrefixedKey prefixedKey = new PrefixedKey(prefix, key);
|
||||
Reference<Config> reference = configCache.compute(prefixedKey, (k, v) -> {
|
||||
Reference<AbstractConfigImpl> reference = configCache.compute(prefixedKey, (k, v) -> {
|
||||
if (v == null || v.get() == null) {
|
||||
return new SoftReference<>(createConfig(prefix, key));
|
||||
} else {
|
||||
@@ -152,7 +167,7 @@ final class ConfigFactory {
|
||||
* @param key config key
|
||||
* @return new instance of {@link Config} for specified {@code key}
|
||||
*/
|
||||
private Config createConfig(ConfigKeyImpl prefix, ConfigKeyImpl key) {
|
||||
private AbstractConfigImpl createConfig(ConfigKeyImpl prefix, ConfigKeyImpl key) {
|
||||
ConfigNode value = findNode(prefix, key);
|
||||
|
||||
if (null == value) {
|
||||
@@ -202,6 +217,152 @@ final class ConfigFactory {
|
||||
return provider;
|
||||
}
|
||||
|
||||
List<ConfigSource> configSources() {
|
||||
return configSources;
|
||||
}
|
||||
|
||||
List<org.eclipse.microprofile.config.spi.ConfigSource> mpConfigSources() {
|
||||
return mpConfigSources;
|
||||
}
|
||||
|
||||
private static org.eclipse.microprofile.config.spi.ConfigSource toMpSource(ConfigSource helidonCs) {
|
||||
if (helidonCs instanceof org.eclipse.microprofile.config.spi.ConfigSource) {
|
||||
return (org.eclipse.microprofile.config.spi.ConfigSource) helidonCs;
|
||||
} else {
|
||||
// this is a non-Helidon ConfigSource
|
||||
return new MpConfigSource(helidonCs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class MpConfigSource implements org.eclipse.microprofile.config.spi.ConfigSource {
|
||||
private final AtomicReference<Map<String, String>> currentValues = new AtomicReference<>();
|
||||
private final Object lock = new Object();
|
||||
private final ConfigSource delegate;
|
||||
|
||||
private MpConfigSource(ConfigSource helidonCs) {
|
||||
this.delegate = helidonCs;
|
||||
delegate.changes()
|
||||
.subscribe(new Flow.Subscriber<Optional<ObjectNode>>() {
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
subscription.request(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(Optional<ObjectNode> item) {
|
||||
synchronized (lock) {
|
||||
currentValues.set(loadMap(item));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
ensureValue();
|
||||
return currentValues.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
ensureValue();
|
||||
return currentValues.get().get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.description();
|
||||
}
|
||||
|
||||
private void ensureValue() {
|
||||
synchronized (lock) {
|
||||
if (null == currentValues.get()) {
|
||||
currentValues.set(loadMap(delegate.load()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> loadMap(Optional<ObjectNode> item) {
|
||||
if (item.isPresent()) {
|
||||
ConfigNode.ObjectNode node = item.get();
|
||||
Map<String, String> values = new TreeMap<>();
|
||||
processNode(values, "", node);
|
||||
return values;
|
||||
} else {
|
||||
return CollectionsHelper.mapOf();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processNode(Map<String, String> values, String keyPrefix, ConfigNode.ObjectNode node) {
|
||||
node.forEach((key, configNode) -> {
|
||||
switch (configNode.nodeType()) {
|
||||
case OBJECT:
|
||||
processNode(values, key(keyPrefix, key), (ConfigNode.ObjectNode) configNode);
|
||||
break;
|
||||
case LIST:
|
||||
processNode(values, key(keyPrefix, key), ((ConfigNode.ListNode) configNode));
|
||||
break;
|
||||
case VALUE:
|
||||
values.put(key(keyPrefix, key), configNode.get());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Config node of type: " + configNode.nodeType() + " not supported");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static void processNode(Map<String, String> values, String keyPrefix, ConfigNode.ListNode node) {
|
||||
List<String> directValue = new LinkedList<>();
|
||||
Map<String, String> thisListValues = new HashMap<>();
|
||||
boolean hasDirectValue = true;
|
||||
|
||||
for (int i = 0; i < node.size(); i++) {
|
||||
ConfigNode configNode = node.get(i);
|
||||
String nextKey = key(keyPrefix, String.valueOf(i));
|
||||
switch (configNode.nodeType()) {
|
||||
case OBJECT:
|
||||
processNode(thisListValues, nextKey, (ConfigNode.ObjectNode) configNode);
|
||||
hasDirectValue = false;
|
||||
break;
|
||||
case LIST:
|
||||
processNode(thisListValues, nextKey, (ConfigNode.ListNode) configNode);
|
||||
hasDirectValue = false;
|
||||
break;
|
||||
case VALUE:
|
||||
String value = configNode.get();
|
||||
directValue.add(value);
|
||||
thisListValues.put(nextKey, value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Config node of type: " + configNode.nodeType() + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDirectValue) {
|
||||
values.put(keyPrefix, String.join(",", directValue));
|
||||
} else {
|
||||
values.putAll(thisListValues);
|
||||
}
|
||||
}
|
||||
|
||||
private static String key(String keyPrefix, String key) {
|
||||
if (keyPrefix.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
return keyPrefix + "." + key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Flow.Publisher} implementation that filters original {@link ProviderImpl#changes()} events to be wrapped by
|
||||
* {@link FilteringConfigChangeEventSubscriber} to ignore events about current Config instance.
|
||||
|
||||
@@ -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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -61,9 +62,24 @@ class ConfigMapperManager implements ConfigMapper {
|
||||
|
||||
@Override
|
||||
public <T> T map(Config config, Class<T> type) throws MissingValueException, ConfigMappingException {
|
||||
if (type.isArray()) {
|
||||
return mapArray(config, type);
|
||||
}
|
||||
return map(config, GenericType.create(supportedType(type)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T mapArray(Config config, Class<T> type) {
|
||||
Class<?> componentType = type.getComponentType();
|
||||
List<?> listValue = config.asList(componentType).get();
|
||||
Object result = Array.newInstance(componentType, listValue.size());
|
||||
for (int i = 0; i < listValue.size(); i++) {
|
||||
Object component = listValue.get(i);
|
||||
Array.set(result, i, component);
|
||||
}
|
||||
return (T) result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T map(Config config, GenericType<T> type) throws MissingValueException, ConfigMappingException {
|
||||
Mapper<?> mapper = mappers.computeIfAbsent(type, theType -> findMapper(theType, config.key()));
|
||||
@@ -149,38 +165,7 @@ class ConfigMapperManager implements ConfigMapper {
|
||||
}
|
||||
|
||||
void add(ConfigMapperProvider provider) {
|
||||
add(genericType -> {
|
||||
// first try to get it from generic type mappers map
|
||||
BiFunction<Config, ConfigMapper, ?> converter = provider.genericTypeMappers().get(genericType);
|
||||
|
||||
if (null != converter) {
|
||||
return Optional.of(converter);
|
||||
}
|
||||
|
||||
// second try to get it from generic type method
|
||||
Optional<? extends BiFunction<Config, ConfigMapper, ?>> mapper1 = provider.mapper(genericType);
|
||||
|
||||
if (mapper1.isPresent()) {
|
||||
return mapper1;
|
||||
}
|
||||
|
||||
if (!genericType.isClass()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// third try the specific class map
|
||||
Class<?> rawType = genericType.rawType();
|
||||
|
||||
Function<Config, ?> configConverter = provider.mappers().get(rawType);
|
||||
|
||||
if (null != configConverter) {
|
||||
return Optional.of((config, mapper) -> configConverter.apply(config));
|
||||
}
|
||||
|
||||
// and last, the specific class method
|
||||
return provider.mapper(rawType)
|
||||
.map(funct -> (config, mapper) -> funct.apply(config));
|
||||
});
|
||||
add(new ProviderWrapper(provider));
|
||||
}
|
||||
|
||||
void addAll(MapperProviders other) {
|
||||
@@ -247,7 +232,7 @@ class ConfigMapperManager implements ConfigMapper {
|
||||
|
||||
@Override
|
||||
public ConfigValue<String> asString() {
|
||||
return ConfigValues.create(this, () -> Optional.of(value), Config::asString);
|
||||
return ConfigValues.create(this, () -> Optional.ofNullable(value), Config::asString);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -351,4 +336,51 @@ class ConfigMapperManager implements ConfigMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProviderWrapper
|
||||
implements Function<GenericType<?>, Optional<? extends BiFunction<Config, ConfigMapper, ?>>> {
|
||||
private final ConfigMapperProvider provider;
|
||||
|
||||
private ProviderWrapper(ConfigMapperProvider wrapped) {
|
||||
this.provider = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<? extends BiFunction<Config, ConfigMapper, ?>> apply(GenericType<?> genericType) {
|
||||
// first try to get it from generic type mappers map
|
||||
BiFunction<Config, ConfigMapper, ?> converter = provider.genericTypeMappers().get(genericType);
|
||||
|
||||
if (null != converter) {
|
||||
return Optional.of(converter);
|
||||
}
|
||||
|
||||
// second try to get it from generic type method
|
||||
Optional<? extends BiFunction<Config, ConfigMapper, ?>> mapper1 = provider.mapper(genericType);
|
||||
|
||||
if (mapper1.isPresent()) {
|
||||
return mapper1;
|
||||
}
|
||||
|
||||
if (!genericType.isClass()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// third try the specific class map
|
||||
Class<?> rawType = genericType.rawType();
|
||||
|
||||
Function<Config, ?> configConverter = provider.mappers().get(rawType);
|
||||
|
||||
if (null != configConverter) {
|
||||
return Optional.of((config, mapper) -> configConverter.apply(config));
|
||||
}
|
||||
|
||||
// and last, the specific class method
|
||||
return provider.mapper(rawType)
|
||||
.map(funct -> (config, mapper) -> funct.apply(config));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return provider.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -34,6 +34,7 @@ import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -148,6 +149,7 @@ public final class ConfigMappers {
|
||||
builtIns.put(Instant.class, wrap(ConfigMappers::toInstant));
|
||||
builtIns.put(OffsetTime.class, wrap(ConfigMappers::toOffsetTime));
|
||||
builtIns.put(OffsetDateTime.class, wrap(ConfigMappers::toOffsetDateTime));
|
||||
builtIns.put(YearMonth.class, wrap(YearMonth::parse));
|
||||
//java.io
|
||||
builtIns.put(File.class, wrap(ConfigMappers::toFile));
|
||||
//java.nio
|
||||
@@ -252,7 +254,18 @@ public final class ConfigMappers {
|
||||
* @return mapped {@code stringValue} to {@code boolean}
|
||||
*/
|
||||
public static Boolean toBoolean(String stringValue) {
|
||||
return Boolean.parseBoolean(stringValue);
|
||||
final String lower = stringValue.toLowerCase();
|
||||
// according to microprofile config specification (section Built-in Converters)
|
||||
switch (lower) {
|
||||
case "true":
|
||||
case "1":
|
||||
case "yes":
|
||||
case "y":
|
||||
case "on":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,34 +16,35 @@
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.Builder;
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.internal.ClasspathConfigSource;
|
||||
import io.helidon.config.internal.ConfigUtils;
|
||||
import io.helidon.config.internal.DirectoryConfigSource;
|
||||
import io.helidon.config.internal.FileConfigSource;
|
||||
import io.helidon.config.internal.MapConfigSource;
|
||||
import io.helidon.config.internal.PrefixedConfigSource;
|
||||
import io.helidon.config.internal.UrlConfigSource;
|
||||
import io.helidon.config.spi.AbstractConfigSource;
|
||||
import io.helidon.config.spi.AbstractParsableConfigSource;
|
||||
import io.helidon.config.spi.AbstractMpSource;
|
||||
import io.helidon.config.spi.AbstractSource;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
@@ -209,7 +210,7 @@ public final class ConfigSources {
|
||||
* @return new @{code ConfigSource} for the newly-prefixed content
|
||||
*/
|
||||
public static ConfigSource prefixed(String key, Supplier<ConfigSource> sourceSupplier) {
|
||||
return new PrefixedConfigSource(key, sourceSupplier.get());
|
||||
return PrefixedConfigSource.create(key, sourceSupplier.get());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +219,7 @@ public final class ConfigSources {
|
||||
*
|
||||
* @return {@code ConfigSource} for config derived from system properties
|
||||
*/
|
||||
public static ConfigSource systemProperties() {
|
||||
public static AbstractMpSource<Instant> systemProperties() {
|
||||
return new SystemPropertiesConfigSource();
|
||||
}
|
||||
|
||||
@@ -228,7 +229,7 @@ public final class ConfigSources {
|
||||
*
|
||||
* @return {@code ConfigSource} for config derived from environment variables
|
||||
*/
|
||||
public static ConfigSource environmentVariables() {
|
||||
public static AbstractMpSource<Instant> environmentVariables() {
|
||||
return new EnvironmentVariablesConfigSource();
|
||||
}
|
||||
|
||||
@@ -244,9 +245,29 @@ public final class ConfigSources {
|
||||
* @param resource a name of the resource
|
||||
* @return builder for a {@code ConfigSource} for the classpath-based resource
|
||||
*/
|
||||
public static AbstractParsableConfigSource.Builder
|
||||
<? extends AbstractParsableConfigSource.Builder<?, Path>, Path> classpath(String resource) {
|
||||
return new ClasspathConfigSource.ClasspathBuilder(resource);
|
||||
public static ClasspathConfigSource.ClasspathBuilder classpath(String resource) {
|
||||
return ClasspathConfigSource.builder().resource(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create builders for each instance of the resource on the current classpath.
|
||||
* @param resource resource to look for
|
||||
* @return a list of classpath config source builders
|
||||
*/
|
||||
public static List<UrlConfigSource.UrlBuilder> classpathAll(String resource) {
|
||||
|
||||
List<UrlConfigSource.UrlBuilder> result = new LinkedList<>();
|
||||
try {
|
||||
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
result.add(url(url));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ConfigException("Failed to read " + resource + " from classpath", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,9 +277,19 @@ public final class ConfigSources {
|
||||
* @param path a file path
|
||||
* @return builder for the file-based {@code ConfigSource}
|
||||
*/
|
||||
public static AbstractParsableConfigSource.Builder
|
||||
<? extends AbstractParsableConfigSource.Builder<?, Path>, Path> file(String path) {
|
||||
return new FileConfigSource.FileBuilder(Paths.get(path));
|
||||
public static FileConfigSource.FileBuilder file(String path) {
|
||||
return FileConfigSource.builder().path(Paths.get(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@code Builder} for creating a {@code ConfigSource} from the specified
|
||||
* file path.
|
||||
*
|
||||
* @param path a file path
|
||||
* @return builder for the file-based {@code ConfigSource}
|
||||
*/
|
||||
public static FileConfigSource.FileBuilder file(Path path) {
|
||||
return FileConfigSource.builder().path(path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,9 +299,8 @@ public final class ConfigSources {
|
||||
* @param path a directory path
|
||||
* @return new Builder instance
|
||||
*/
|
||||
public static AbstractConfigSource.Builder
|
||||
<? extends AbstractConfigSource.Builder<?, Path>, Path> directory(String path) {
|
||||
return new DirectoryConfigSource.DirectoryBuilder(Paths.get(path));
|
||||
public static DirectoryConfigSource.DirectoryBuilder directory(String path) {
|
||||
return DirectoryConfigSource.builder().path(Paths.get(path));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,9 +311,8 @@ public final class ConfigSources {
|
||||
* @return new Builder instance
|
||||
* @see #url(URL)
|
||||
*/
|
||||
public static AbstractParsableConfigSource.Builder
|
||||
<? extends AbstractParsableConfigSource.Builder<?, URL>, URL> url(URL url) {
|
||||
return new UrlConfigSource.UrlBuilder(url);
|
||||
public static UrlConfigSource.UrlBuilder url(URL url) {
|
||||
return UrlConfigSource.builder().url(url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,13 +332,11 @@ public final class ConfigSources {
|
||||
* @see MergingStrategy
|
||||
* @see #create(Supplier[])
|
||||
* @see #create(List)
|
||||
* @see #load(Supplier[])
|
||||
* @see #load(Config)
|
||||
* @see Config#create(Supplier[])
|
||||
* @see Config#builder(Supplier[])
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static CompositeBuilder create(Supplier<ConfigSource>... configSources) {
|
||||
public static CompositeBuilder create(Supplier<? extends ConfigSource>... configSources) {
|
||||
return create(CollectionsHelper.listOf(configSources));
|
||||
}
|
||||
|
||||
@@ -330,89 +357,13 @@ public final class ConfigSources {
|
||||
* @see MergingStrategy
|
||||
* @see #create(Supplier[])
|
||||
* @see #create(List)
|
||||
* @see #load(Supplier[])
|
||||
* @see #load(Config)
|
||||
* @see Config#create(Supplier[])
|
||||
* @see Config#builder(Supplier[])
|
||||
*/
|
||||
public static CompositeBuilder create(List<Supplier<ConfigSource>> configSources) {
|
||||
public static CompositeBuilder create(List<Supplier<? extends ConfigSource>> configSources) {
|
||||
return new CompositeBuilder(configSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link CompositeBuilder} for creating a composite
|
||||
* {@code ConfigSource} based on the {@code ConfigSource}s returned by the
|
||||
* provided meta-sources.
|
||||
* <p>
|
||||
* Each meta-source must contain the {@code sources} property which is an
|
||||
* array of config sources. See {@link ConfigSource#create(Config)} for more
|
||||
* information about the format of meta-configuration.
|
||||
* <p>
|
||||
* The returned builder is a {@code CompositeBuilder} that combines the
|
||||
* config from all config sources derived from the meta-configuration in the
|
||||
* meta-sources. By default the composite builder uses the
|
||||
* {@link MergingStrategy#fallback() fallback merging strategy}.
|
||||
*
|
||||
* @param metaSources ordered list of meta-sources from which the builder
|
||||
* will read meta-configuration indicating the config sources
|
||||
* @return new composite config source builder initialized from the
|
||||
* specified meta-sources.
|
||||
* @see CompositeBuilder
|
||||
* @see MergingStrategy
|
||||
* @see #create(Supplier[])
|
||||
* @see #create(List)
|
||||
* @see #load(Supplier[])
|
||||
* @see #load(Config)
|
||||
* @see Config#builderLoadSourcesFrom(Supplier[])
|
||||
* @see Config#loadSourcesFrom(Supplier[])
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static CompositeBuilder load(Supplier<ConfigSource>... metaSources) {
|
||||
return load(Config.builder(metaSources)
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link CompositeBuilder} for creating a composite
|
||||
* {@code ConfigSource} based on the {@link ConfigSource}s returned by the
|
||||
* provided meta-configuration.
|
||||
* <p>
|
||||
* The meta-configuration must contain the {@code sources} property which is
|
||||
* an array of config sources. See {@link ConfigSource#create(Config)} for
|
||||
* more information about the format of meta-configuration.
|
||||
* <p>
|
||||
* The returned builder is a {@code CompositeBuilder} that combines the
|
||||
* config from all config sources derived from the meta-configuration. By
|
||||
* default the composite builder uses the
|
||||
* {@link MergingStrategy#fallback() fallback merging strategy}.
|
||||
*
|
||||
* @param metaConfig meta-configuration from which the builder will derive
|
||||
* config sources
|
||||
* @return new composite config source builder initialized from the
|
||||
* specified meta-config
|
||||
* @see CompositeBuilder
|
||||
* @see MergingStrategy
|
||||
* @see #create(Supplier[])
|
||||
* @see #create(List)
|
||||
* @see #load(Supplier[])
|
||||
* @see #load(Config)
|
||||
* @see Config#builderLoadSourcesFrom(Supplier[])
|
||||
* @see Config#loadSourcesFrom(Supplier[])
|
||||
*/
|
||||
public static CompositeBuilder load(Config metaConfig) {
|
||||
List<Supplier<ConfigSource>> sources = metaConfig.get(SOURCES_KEY)
|
||||
.asNodeList()
|
||||
.orElse(CollectionsHelper.listOf())
|
||||
.stream()
|
||||
.map(node -> node.as(ConfigSource::create))
|
||||
.map(ConfigValue::get)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ConfigSources.create(sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of a {@code ConfigSource} based on a {@code Map} containing
|
||||
* config entries.
|
||||
@@ -448,7 +399,6 @@ public final class ConfigSources {
|
||||
private Map<String, String> map;
|
||||
private boolean strict;
|
||||
private String mapSourceName;
|
||||
private volatile ConfigSource configSource;
|
||||
|
||||
private MapBuilder(final Map<String, String> map, final String name) {
|
||||
requireNonNull(name, "name cannot be null");
|
||||
@@ -490,15 +440,7 @@ public final class ConfigSources {
|
||||
*/
|
||||
@Override
|
||||
public ConfigSource build() {
|
||||
return new MapConfigSource(map, strict, mapSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource get() {
|
||||
if (configSource == null) {
|
||||
configSource = build();
|
||||
}
|
||||
return configSource;
|
||||
return MapConfigSource.create(map, strict, mapSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +502,7 @@ public final class ConfigSources {
|
||||
private Duration debounceTimeout;
|
||||
private volatile ConfigSource configSource;
|
||||
|
||||
private CompositeBuilder(List<Supplier<ConfigSource>> configSources) {
|
||||
private CompositeBuilder(List<Supplier<? extends ConfigSource>> configSources) {
|
||||
this.configSources = initConfigSources(configSources);
|
||||
|
||||
changesExecutor = CompositeConfigSource.DEFAULT_CHANGES_EXECUTOR_SERVICE;
|
||||
@@ -568,9 +510,9 @@ public final class ConfigSources {
|
||||
changesMaxBuffer = Flow.defaultBufferSize();
|
||||
}
|
||||
|
||||
private static List<ConfigSource> initConfigSources(List<Supplier<ConfigSource>> sourceSuppliers) {
|
||||
private static List<ConfigSource> initConfigSources(List<Supplier<? extends ConfigSource>> sourceSuppliers) {
|
||||
List<ConfigSource> configSources = new LinkedList<>();
|
||||
for (Supplier<ConfigSource> configSupplier : sourceSuppliers) {
|
||||
for (Supplier<? extends ConfigSource> configSupplier : sourceSuppliers) {
|
||||
configSources.add(configSupplier.get());
|
||||
}
|
||||
return configSources;
|
||||
@@ -582,7 +524,7 @@ public final class ConfigSources {
|
||||
* @param source config source
|
||||
* @return updated builder
|
||||
*/
|
||||
public CompositeBuilder add(Supplier<ConfigSource> source) {
|
||||
public CompositeBuilder add(Supplier<? extends ConfigSource> source) {
|
||||
requireNonNull(source, "source cannot be null");
|
||||
|
||||
configSources.add(source.get());
|
||||
@@ -742,7 +684,7 @@ public final class ConfigSources {
|
||||
*
|
||||
* @see ConfigSources#empty()
|
||||
*/
|
||||
private static final class EmptyConfigSourceHolder {
|
||||
static final class EmptyConfigSourceHolder {
|
||||
|
||||
private EmptyConfigSourceHolder() {
|
||||
throw new AssertionError("Instantiation not allowed.");
|
||||
@@ -751,7 +693,7 @@ public final class ConfigSources {
|
||||
/**
|
||||
* EMPTY singleton instance.
|
||||
*/
|
||||
private static final ConfigSource EMPTY = new ConfigSource() {
|
||||
static final ConfigSource EMPTY = new ConfigSource() {
|
||||
@Override
|
||||
public String description() {
|
||||
return "Empty";
|
||||
@@ -779,12 +721,58 @@ public final class ConfigSources {
|
||||
/**
|
||||
* System properties config source.
|
||||
*/
|
||||
static final class SystemPropertiesConfigSource extends MapConfigSource {
|
||||
public static final class SystemPropertiesConfigSource extends AbstractMpSource<Instant> {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
SystemPropertiesConfigSource() {
|
||||
super(ConfigUtils.propertiesToMap(System.getProperties()), false, "");
|
||||
// need builder to be able to customize polling strategy
|
||||
super(new Builder().pollingStrategy(PollingStrategies.regular(Duration.of(5, ChronoUnit.SECONDS))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Data<ConfigNode.ObjectNode, Instant> loadData() throws ConfigException {
|
||||
return new Data<>(Optional.of(ConfigUtils
|
||||
.mapToObjectNode(ConfigUtils.propertiesToMap(System.getProperties()), false)),
|
||||
Optional.of(Instant.now()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Instant> dataStamp() {
|
||||
// each polling event will trigger a load and comparison of config tree
|
||||
return Optional.of(Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyNames() {
|
||||
return System.getProperties().stringPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ConfigContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
System.getProperties().stringPropertyNames()
|
||||
.forEach(it -> {
|
||||
result.put(it, System.getProperty(it));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final class Builder extends AbstractSource.Builder<Builder, Instant, SystemPropertiesConfigSource> {
|
||||
private Builder() {
|
||||
super(Instant.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemPropertiesConfigSource build() {
|
||||
return new SystemPropertiesConfigSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -259,8 +259,7 @@ public final class ConfigValues {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// we do not use value in toString, as that may throw a MappingException
|
||||
return "ConfigValue for key \"" + key() + "\"";
|
||||
return key() + ": " + asOptional();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,18 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.internal.FileSourceHelper;
|
||||
import io.helidon.config.spi.AbstractConfigSource;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
@@ -57,7 +53,7 @@ public class DirectoryConfigSource extends AbstractConfigSource<Instant> {
|
||||
* <li>{@code path} - type {@link Path}</li>
|
||||
* </ul>
|
||||
* Optional {@code properties}: see
|
||||
* {@link io.helidon.config.spi.AbstractParsableConfigSource.Builder#init(Config)}.
|
||||
* {@link io.helidon.config.spi.AbstractParsableConfigSource.Builder#config(Config)}.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned config source instance from.
|
||||
* @return new instance of config source described by {@code metaConfig}
|
||||
@@ -66,12 +62,19 @@ public class DirectoryConfigSource extends AbstractConfigSource<Instant> {
|
||||
* @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#directory(String)
|
||||
* @see io.helidon.config.spi.AbstractParsableConfigSource.Builder#init(Config)
|
||||
* @see io.helidon.config.spi.AbstractParsableConfigSource.Builder#config(Config)
|
||||
*/
|
||||
public static DirectoryConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return (DirectoryConfigSource) new DirectoryBuilder(metaConfig.get(PATH_KEY).as(Path.class).get())
|
||||
.init(metaConfig)
|
||||
.build();
|
||||
return builder().config(metaConfig).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fluent API builder to construct a directory config source.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static DirectoryBuilder builder() {
|
||||
return new DirectoryBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,25 +113,39 @@ public class DirectoryConfigSource extends AbstractConfigSource<Instant> {
|
||||
* If the Directory ConfigSource is {@code mandatory} and a {@code directory} does not exist
|
||||
* then {@link ConfigSource#load} throws {@link ConfigException}.
|
||||
*/
|
||||
public static final class DirectoryBuilder extends Builder<DirectoryBuilder, Path> {
|
||||
public static final class DirectoryBuilder extends Builder<DirectoryBuilder, Path, DirectoryConfigSource> {
|
||||
private Path path;
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
*
|
||||
* @param path configuration directory path
|
||||
*/
|
||||
public DirectoryBuilder(Path path) {
|
||||
private DirectoryBuilder() {
|
||||
super(Path.class);
|
||||
|
||||
Objects.requireNonNull(path, "directory path cannot be null");
|
||||
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration directory path.
|
||||
*
|
||||
* @param path directory
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public DirectoryBuilder path(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <ul>
|
||||
* <li>{@code path} - directory path</li>
|
||||
* </ul>
|
||||
* @param metaConfig configuration properties used to configure a builder instance.
|
||||
* @return updated builder instance
|
||||
*/
|
||||
@Override
|
||||
protected DirectoryBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public DirectoryBuilder config(Config metaConfig) {
|
||||
metaConfig.get(PATH_KEY).as(Path.class).ifPresent(this::path);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,7 +158,11 @@ public class DirectoryConfigSource extends AbstractConfigSource<Instant> {
|
||||
*
|
||||
* @return new instance of File ConfigSource.
|
||||
*/
|
||||
public ConfigSource build() {
|
||||
@Override
|
||||
public DirectoryConfigSource build() {
|
||||
if (null == path) {
|
||||
throw new IllegalArgumentException("path must be defined");
|
||||
}
|
||||
return new DirectoryConfigSource(this, path);
|
||||
}
|
||||
}
|
||||
@@ -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,21 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Path;
|
||||
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.internal.FileSourceHelper;
|
||||
import io.helidon.config.spi.AbstractParsableConfigSource;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigParser.Content;
|
||||
@@ -47,10 +42,15 @@ public class FileConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
|
||||
private final Path filePath;
|
||||
|
||||
FileConfigSource(FileBuilder builder, Path filePath) {
|
||||
/**
|
||||
* Create a new file config source.
|
||||
*
|
||||
* @param builder builder with configured path and other options of this source
|
||||
*/
|
||||
protected FileConfigSource(FileBuilder builder) {
|
||||
super(builder);
|
||||
|
||||
this.filePath = filePath;
|
||||
this.filePath = builder.path;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,7 @@ public class FileConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
* <ul>
|
||||
* <li>{@code path} - type {@link Path}</li>
|
||||
* </ul>
|
||||
* Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#init(Config)}.
|
||||
* Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#config(Config)}.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned config source instance from.
|
||||
* @return new instance of config source described by {@code metaConfig}
|
||||
@@ -69,14 +69,22 @@ public class FileConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
* @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#file(String)
|
||||
* @see AbstractParsableConfigSource.Builder#init(Config)
|
||||
* @see AbstractParsableConfigSource.Builder#config(Config)
|
||||
*/
|
||||
public static FileConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return (FileConfigSource) new FileBuilder(metaConfig.get(PATH_KEY).as(Path.class).get())
|
||||
.init(metaConfig)
|
||||
return FileConfigSource.builder()
|
||||
.config(metaConfig)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a builder instance to create a new config source.
|
||||
* @return a fluent API builder
|
||||
*/
|
||||
public static FileBuilder builder() {
|
||||
return new FileBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String uid() {
|
||||
return filePath.toString();
|
||||
@@ -125,25 +133,37 @@ public class FileConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
* <p>
|
||||
* If {@code media-type} not set it tries to guess it from file extension.
|
||||
*/
|
||||
public static final class FileBuilder extends Builder<FileBuilder, Path> {
|
||||
public static final class FileBuilder extends Builder<FileBuilder, Path, FileConfigSource> {
|
||||
private Path path;
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
*
|
||||
* @param path configuration file path
|
||||
*/
|
||||
public FileBuilder(Path path) {
|
||||
private FileBuilder() {
|
||||
super(Path.class);
|
||||
|
||||
Objects.requireNonNull(path, "file path cannot be null");
|
||||
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the path to read configuration from (mandatory).
|
||||
*
|
||||
* @param path path of a file to use
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public FileBuilder path(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <ul>
|
||||
* <li>{@code path} - path to the file containing the configuration</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param metaConfig configuration properties used to configure a builder instance.
|
||||
* @return modified builder instance
|
||||
*/
|
||||
@Override
|
||||
protected FileBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public FileBuilder config(Config metaConfig) {
|
||||
metaConfig.get(PATH_KEY).as(Path.class).ifPresent(this::path);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,8 +178,11 @@ public class FileConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
*
|
||||
* @return new instance of File ConfigSource.
|
||||
*/
|
||||
public ConfigSource build() {
|
||||
return new FileConfigSource(this, path);
|
||||
public FileConfigSource build() {
|
||||
if (null == path) {
|
||||
throw new IllegalArgumentException("File path cannot be null");
|
||||
}
|
||||
return new FileConfigSource(this);
|
||||
}
|
||||
|
||||
PollingStrategy pollingStrategyInternal() { //just for testing purposes
|
||||
@@ -56,7 +56,8 @@ class InMemoryConfigSource extends AbstractParsableConfigSource<Object> {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static final class Builder extends AbstractParsableConfigSource.Builder<InMemoryConfigSource.Builder, Void> {
|
||||
static final class Builder
|
||||
extends AbstractParsableConfigSource.Builder<InMemoryConfigSource.Builder, Void, InMemoryConfigSource> {
|
||||
|
||||
private String uri;
|
||||
private ConfigParser.Content<Object> content;
|
||||
|
||||
174
config/config/src/main/java/io/helidon/config/MetaConfig.java
Normal file
174
config/config/src/main/java/io/helidon/config/MetaConfig.java
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.OverrideSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
import io.helidon.config.spi.RetryPolicy;
|
||||
|
||||
/**
|
||||
* Meta configuration.
|
||||
*
|
||||
* TODO document meta configuration
|
||||
* - files loaded as part of meta config lookup
|
||||
* - options to specify in meta configuration
|
||||
*/
|
||||
public final class MetaConfig {
|
||||
private static final Logger LOGGER = Logger.getLogger(MetaConfig.class.getName());
|
||||
|
||||
private MetaConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configuration from meta configuration (files or classpath resources), or create a default config instance
|
||||
* if meta configuration is not present.
|
||||
*
|
||||
* @return a config instance
|
||||
*/
|
||||
public static Config config() {
|
||||
return metaConfig().map(MetaConfig::config)
|
||||
// if not found, create a default instance
|
||||
.orElseGet(MetaConfig::createDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create configuration from provided meta configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration
|
||||
* @return a new config instance built from meta configuration
|
||||
*/
|
||||
public static Config config(Config metaConfig) {
|
||||
return Config.builder()
|
||||
.config(metaConfig)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find meta configuration (files or classpath resources) and create a meta configuration instance from it.
|
||||
*
|
||||
* @return meta configuration if present, or empty
|
||||
*/
|
||||
public static Optional<Config> metaConfig() {
|
||||
return MetaConfigFinder.findMetaConfig(supportedMediaTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a polling strategy based on its meta configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration of a polling strategy
|
||||
* @return a function that creates a polling strategy instance for an instance of target type
|
||||
*/
|
||||
public static Function<Object, PollingStrategy> pollingStrategy(Config metaConfig) {
|
||||
return MetaProviders.pollingStrategy(metaConfig.get("type").asString().get(),
|
||||
metaConfig.get("properties"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a retry policy based on its meta configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration of retry policy
|
||||
* @return retry policy instance
|
||||
*/
|
||||
public static RetryPolicy retryPolicy(Config metaConfig) {
|
||||
String type = metaConfig.get("type").asString().get();
|
||||
RetryPolicy retryPolicy = MetaProviders.retryPolicy(type,
|
||||
metaConfig.get("properties"));
|
||||
|
||||
LOGGER.fine(() -> "Loaded retry policy of type \"" + type + "\", class: " + retryPolicy.getClass().getName());
|
||||
|
||||
return retryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a config source based on its meta configuration.
|
||||
* The metaConfig must contain a key {@code type} that defines the type of the source to be found via providers, and
|
||||
* a key {@code properties} with configuration of the config sources
|
||||
* @param sourceMetaConfig meta configuration of a config source
|
||||
* @return config source instance
|
||||
* @see Config.Builder#config(Config)
|
||||
*/
|
||||
public static ConfigSource configSource(Config sourceMetaConfig) {
|
||||
String type = sourceMetaConfig.get("type").asString().get();
|
||||
ConfigSource source = MetaProviders.configSource(type,
|
||||
sourceMetaConfig.get("properties"));
|
||||
|
||||
LOGGER.fine(() -> "Loaded source of type \"" + type + "\", class: " + source.getClass().getName());
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
// override config source
|
||||
static OverrideSource overrideSource(Config sourceMetaConfig) {
|
||||
String type = sourceMetaConfig.get("type").asString().get();
|
||||
OverrideSource source = MetaProviders.overrideSource(type,
|
||||
sourceMetaConfig.get("properties"));
|
||||
|
||||
LOGGER.fine(() -> "Loaded override source of type \"" + type + "\", class: " + source.getClass().getName());
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
static List<ConfigSource> configSources(Config metaConfig) {
|
||||
List<ConfigSource> configSources = new LinkedList<>();
|
||||
|
||||
metaConfig.get("sources")
|
||||
.asNodeList()
|
||||
.ifPresent(list -> list.forEach(it -> configSources.add(MetaConfig.configSource(it))));
|
||||
|
||||
return configSources;
|
||||
}
|
||||
|
||||
// only interested in config source
|
||||
static List<ConfigSource> configSources(Function<String, Boolean> supportedMediaType) {
|
||||
Optional<Config> metaConfigOpt = metaConfig();
|
||||
|
||||
return metaConfigOpt
|
||||
.map(MetaConfig::configSources)
|
||||
.orElseGet(() -> MetaConfigFinder.findConfigSource(supportedMediaType)
|
||||
.map(CollectionsHelper::listOf)
|
||||
.orElseGet(CollectionsHelper::listOf));
|
||||
|
||||
}
|
||||
|
||||
private static Function<String, Boolean> supportedMediaTypes() {
|
||||
Set<String> supportedMediaTypes = new HashSet<>();
|
||||
|
||||
HelidonServiceLoader.create(ServiceLoader.load(ConfigParser.class))
|
||||
.forEach(parser -> supportedMediaTypes.addAll(parser.supportedMediaTypes()));
|
||||
|
||||
return supportedMediaTypes::contains;
|
||||
}
|
||||
|
||||
private static Config createDefault() {
|
||||
// use defaults
|
||||
Config.Builder builder = Config.builder();
|
||||
MetaConfigFinder.findConfigSource(supportedMediaTypes()).ifPresent(builder::addSource);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.media.type.MediaTypes;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
|
||||
import static io.helidon.common.CollectionsHelper.listOf;
|
||||
|
||||
/**
|
||||
* Utility class that locates the meta configuration source.
|
||||
*/
|
||||
final class MetaConfigFinder {
|
||||
/**
|
||||
* System property used to set a file with meta configuration.
|
||||
*/
|
||||
public static final String META_CONFIG_SYSTEM_PROPERTY = "io.helidon.config.meta-config";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(MetaConfigFinder.class.getName());
|
||||
private static final List<String> CONFIG_SUFFIXES = listOf("yaml", "conf", "json", "properties");
|
||||
private static final String META_CONFIG_PREFIX = "meta-config.";
|
||||
private static final String CONFIG_PREFIX = "application.";
|
||||
|
||||
private MetaConfigFinder() {
|
||||
}
|
||||
|
||||
static Optional<Config> findMetaConfig(Function<String, Boolean> supportedMediaType) {
|
||||
return findMetaConfigSource(supportedMediaType)
|
||||
.map(source -> Config.builder(source).build());
|
||||
}
|
||||
|
||||
static Optional<ConfigSource> findConfigSource(Function<String, Boolean> supportedMediaType) {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
return findSource(supportedMediaType, cl, CONFIG_PREFIX);
|
||||
}
|
||||
|
||||
private static Optional<ConfigSource> findMetaConfigSource(Function<String, Boolean> supportedMediaType) {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
Optional<ConfigSource> source;
|
||||
|
||||
// check if meta configuration is configured using system property
|
||||
String property = System.getProperty(META_CONFIG_SYSTEM_PROPERTY);
|
||||
if (null != property) {
|
||||
// is it a file
|
||||
source = findFile(property);
|
||||
if (source.isPresent()) {
|
||||
return source;
|
||||
}
|
||||
// so it is a classpath resource?
|
||||
source = findClasspath(cl, property);
|
||||
if (source.isPresent()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
LOGGER.info("Meta configuration file not found: " + property);
|
||||
}
|
||||
|
||||
return findSource(supportedMediaType, cl, META_CONFIG_PREFIX);
|
||||
}
|
||||
|
||||
private static Optional<ConfigSource> findSource(Function<String, Boolean> supportedMediaType,
|
||||
ClassLoader cl,
|
||||
String configPrefix) {
|
||||
Optional<ConfigSource> source;
|
||||
|
||||
List<String> validSuffixes = CONFIG_SUFFIXES.stream()
|
||||
.filter(suffix -> supportedMediaType.apply(MediaTypes.detectExtensionType(suffix).orElse("unknown/unknown")))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// look into the file system - in current user directory
|
||||
source = validSuffixes.stream()
|
||||
.map(suf -> configPrefix + suf)
|
||||
.map(MetaConfigFinder::findFile)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
|
||||
if (source.isPresent()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// and finally try to find meta configuration on classpath
|
||||
return validSuffixes.stream()
|
||||
.map(suf -> configPrefix + suf)
|
||||
.map(resource -> MetaConfigFinder.findClasspath(cl, resource))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Optional<ConfigSource> findFile(String name) {
|
||||
Path path = Paths.get(name);
|
||||
if (Files.exists(path) && Files.isReadable(path) && !Files.isDirectory(path)) {
|
||||
LOGGER.info("Found config source file: " + path.toAbsolutePath());
|
||||
return Optional.of(ConfigSources.file(path).build());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<ConfigSource> findClasspath(ClassLoader cl, String name) {
|
||||
// so it is a classpath resource?
|
||||
URL resource = cl.getResource(name);
|
||||
if (null != resource) {
|
||||
LOGGER.info("Found config source resource: " + resource.getPath());
|
||||
return Optional.of(ConfigSources.classpath(name).build());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
268
config/config/src/main/java/io/helidon/config/MetaProviders.java
Normal file
268
config/config/src/main/java/io/helidon/config/MetaProviders.java
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.config.internal.FileOverrideSource;
|
||||
import io.helidon.config.internal.PrefixedConfigSource;
|
||||
import io.helidon.config.internal.UrlOverrideSource;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.ConfigSourceProvider;
|
||||
import io.helidon.config.spi.OverrideSource;
|
||||
import io.helidon.config.spi.OverrideSourceProvider;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
import io.helidon.config.spi.PollingStrategyProvider;
|
||||
import io.helidon.config.spi.RetryPolicy;
|
||||
import io.helidon.config.spi.RetryPolicyProvider;
|
||||
|
||||
/**
|
||||
* Access to Java service loaders for config sources, retry policies and polling strategies.
|
||||
*/
|
||||
final class MetaProviders {
|
||||
private static final List<ConfigSourceProvider> CONFIG_SOURCE_PROVIDERS;
|
||||
private static final List<RetryPolicyProvider> RETRY_POLICY_PROVIDERS;
|
||||
private static final List<PollingStrategyProvider> POLLING_STRATEGY_PROVIDERS;
|
||||
private static final List<OverrideSourceProvider> OVERRIDE_SOURCE_PROVIDERS;
|
||||
|
||||
private static final Set<String> SUPPORTED_CONFIG_SOURCES = new HashSet<>();
|
||||
private static final Set<String> SUPPORTED_RETRY_POLICIES = new HashSet<>();
|
||||
private static final Set<String> SUPPORTED_POLLING_STRATEGIES = new HashSet<>();
|
||||
private static final Set<String> SUPPORTED_OVERRIDE_SOURCES = new HashSet<>();
|
||||
|
||||
static {
|
||||
CONFIG_SOURCE_PROVIDERS = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(ConfigSourceProvider.class))
|
||||
.addService(new BuiltInConfigSourcesProvider())
|
||||
.build()
|
||||
.asList();
|
||||
|
||||
CONFIG_SOURCE_PROVIDERS.stream()
|
||||
.map(ConfigSourceProvider::supported)
|
||||
.forEach(SUPPORTED_CONFIG_SOURCES::addAll);
|
||||
|
||||
RETRY_POLICY_PROVIDERS = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(RetryPolicyProvider.class))
|
||||
.addService(new BuiltInRetryPolicyProvider())
|
||||
.build()
|
||||
.asList();
|
||||
|
||||
RETRY_POLICY_PROVIDERS.stream()
|
||||
.map(RetryPolicyProvider::supported)
|
||||
.forEach(SUPPORTED_RETRY_POLICIES::addAll);
|
||||
|
||||
POLLING_STRATEGY_PROVIDERS = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(PollingStrategyProvider.class))
|
||||
.addService(new BuiltInPollingStrategyProvider())
|
||||
.build()
|
||||
.asList();
|
||||
|
||||
POLLING_STRATEGY_PROVIDERS.stream()
|
||||
.map(PollingStrategyProvider::supported)
|
||||
.forEach(SUPPORTED_POLLING_STRATEGIES::addAll);
|
||||
|
||||
OVERRIDE_SOURCE_PROVIDERS = HelidonServiceLoader
|
||||
.builder(ServiceLoader.load(OverrideSourceProvider.class))
|
||||
.addService(new BuiltinOverrideSourceProvider())
|
||||
.build()
|
||||
.asList();
|
||||
|
||||
OVERRIDE_SOURCE_PROVIDERS.stream()
|
||||
.map(OverrideSourceProvider::supported)
|
||||
.forEach(SUPPORTED_OVERRIDE_SOURCES::addAll);
|
||||
}
|
||||
|
||||
private MetaProviders() {
|
||||
}
|
||||
|
||||
public static ConfigSource configSource(String type, Config config) {
|
||||
return CONFIG_SOURCE_PROVIDERS.stream()
|
||||
.filter(provider -> provider.supports(type))
|
||||
.findFirst()
|
||||
.map(provider -> provider.create(type, config))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Config source of type " + type + " is not supported."
|
||||
+ " Supported types: " + SUPPORTED_CONFIG_SOURCES));
|
||||
}
|
||||
|
||||
public static OverrideSource overrideSource(String type, Config config) {
|
||||
return OVERRIDE_SOURCE_PROVIDERS.stream()
|
||||
.filter(provider -> provider.supports(type))
|
||||
.findFirst()
|
||||
.map(provider -> provider.create(type, config))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Config source of type " + type + " is not supported."
|
||||
+ " Supported types: " + SUPPORTED_OVERRIDE_SOURCES));
|
||||
}
|
||||
|
||||
public static Function<Object, PollingStrategy> pollingStrategy(String type, Config config) {
|
||||
return POLLING_STRATEGY_PROVIDERS.stream()
|
||||
.filter(provider -> provider.supports(type))
|
||||
.findFirst()
|
||||
.map(provider -> provider.create(type, config))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Polling strategy of type " + type + " is not supported."
|
||||
+ " Supported types: " + SUPPORTED_POLLING_STRATEGIES));
|
||||
}
|
||||
|
||||
public static RetryPolicy retryPolicy(String type, Config config) {
|
||||
return RETRY_POLICY_PROVIDERS.stream()
|
||||
.filter(provider -> provider.supports(type))
|
||||
.findFirst()
|
||||
.map(provider -> provider.create(type, config))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Retry policy of type " + type + " is not supported."
|
||||
+ " Supported types: " + SUPPORTED_RETRY_POLICIES));
|
||||
}
|
||||
|
||||
@Priority(Integer.MAX_VALUE)
|
||||
private static final class BuiltInPollingStrategyProvider implements PollingStrategyProvider {
|
||||
private static final String REGULAR_TYPE = "regular";
|
||||
private static final String WATCH_TYPE = "watch";
|
||||
|
||||
private static final Map<String, Function<Config, Function<Object, PollingStrategy>>> BUILT_IN =
|
||||
CollectionsHelper.mapOf(
|
||||
REGULAR_TYPE, config -> target -> PollingStrategies.ScheduledBuilder.create(config).build(),
|
||||
WATCH_TYPE, config -> BuiltInPollingStrategyProvider::watchStrategy
|
||||
);
|
||||
|
||||
private static PollingStrategy watchStrategy(Object target) {
|
||||
if (target instanceof Path) {
|
||||
Path path = (Path) target;
|
||||
return PollingStrategies.watch(path).build();
|
||||
}
|
||||
|
||||
throw new ConfigException("Incorrect target type ('" + target.getClass().getName()
|
||||
+ "') for WATCH polling strategy. Expected '" + Path.class.getName() + "'.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return BUILT_IN.containsKey(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Object, PollingStrategy> create(String type, Config metaConfig) {
|
||||
return BUILT_IN.get(type).apply(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return BUILT_IN.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
@Priority(Integer.MAX_VALUE)
|
||||
private static final class BuiltInRetryPolicyProvider implements RetryPolicyProvider {
|
||||
private static final String REPEAT_TYPE = "repeat";
|
||||
|
||||
private BuiltInRetryPolicyProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return REPEAT_TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryPolicy create(String type, Config metaConfig) {
|
||||
// This method is actually dedicated to repeat type and does no reflection at all
|
||||
// TODO refactor to proper factory methods and a builder
|
||||
// (e.g. RetryPolicies.repeatBuilder().config(metaConfig).build())
|
||||
return RetryPolicies.Builder.create(metaConfig).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(REPEAT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Priority(Integer.MAX_VALUE)
|
||||
private static final class BuiltinOverrideSourceProvider implements OverrideSourceProvider {
|
||||
private static final String FILE_TYPE = "file";
|
||||
private static final String CLASSPATH_TYPE = "classpath";
|
||||
private static final String URL_TYPE = "url";
|
||||
|
||||
private static final Map<String, Function<Config, OverrideSource>> BUILT_INS = new HashMap<>();
|
||||
|
||||
static {
|
||||
BUILT_INS.put(CLASSPATH_TYPE, ClasspathOverrideSource::create);
|
||||
BUILT_INS.put(FILE_TYPE, FileOverrideSource::create);
|
||||
BUILT_INS.put(URL_TYPE, UrlOverrideSource::create);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return BUILT_INS.containsKey(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OverrideSource create(String type, Config metaConfig) {
|
||||
return BUILT_INS.get(type).apply(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return BUILT_INS.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
@Priority(Integer.MAX_VALUE)
|
||||
private static final class BuiltInConfigSourcesProvider implements ConfigSourceProvider {
|
||||
private static final String SYSTEM_PROPERTIES_TYPE = "system-properties";
|
||||
private static final String ENVIRONMENT_VARIABLES_TYPE = "environment-variables";
|
||||
private static final String CLASSPATH_TYPE = "classpath";
|
||||
private static final String FILE_TYPE = "file";
|
||||
private static final String DIRECTORY_TYPE = "directory";
|
||||
private static final String URL_TYPE = "url";
|
||||
private static final String PREFIXED_TYPE = "prefixed";
|
||||
|
||||
private static final Map<String, Function<Config, ConfigSource>> BUILT_INS = new HashMap<>();
|
||||
|
||||
static {
|
||||
BUILT_INS.put(SYSTEM_PROPERTIES_TYPE, config -> ConfigSources.systemProperties());
|
||||
BUILT_INS.put(ENVIRONMENT_VARIABLES_TYPE, config -> ConfigSources.environmentVariables());
|
||||
BUILT_INS.put(CLASSPATH_TYPE, ClasspathConfigSource::create);
|
||||
BUILT_INS.put(FILE_TYPE, FileConfigSource::create);
|
||||
BUILT_INS.put(DIRECTORY_TYPE, DirectoryConfigSource::create);
|
||||
BUILT_INS.put(URL_TYPE, UrlConfigSource::create);
|
||||
BUILT_INS.put(PREFIXED_TYPE, PrefixedConfigSource::create);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return BUILT_INS.containsKey(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource create(String type, Config metaConfig) {
|
||||
return BUILT_INS.get(type).apply(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return BUILT_INS.keySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.config;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigBuilder;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
/**
|
||||
* Configuration builder.
|
||||
*/
|
||||
public class MpConfigBuilder implements ConfigBuilder {
|
||||
private final BuilderImpl delegate = new BuilderImpl();
|
||||
|
||||
MpConfigBuilder() {
|
||||
delegate.disableSystemPropertiesSource();
|
||||
delegate.disableEnvironmentVariablesSource();
|
||||
delegate.disableSourceServices();
|
||||
delegate.disableMpMapperServices();
|
||||
delegate.metaConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDefaultSources() {
|
||||
delegate.mpAddDefaultSources();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredSources() {
|
||||
delegate.mpAddDiscoveredSources();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredConverters() {
|
||||
delegate.mpAddDiscoveredConverters();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder forClassLoader(ClassLoader loader) {
|
||||
delegate.mpForClassLoader(loader);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withSources(ConfigSource... sources) {
|
||||
delegate.mpWithSources(sources);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigBuilder withConverter(Class<T> aClass, int ordinal, Converter<T> converter) {
|
||||
delegate.mpWithConverter(aClass, ordinal, converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withConverters(Converter<?>... converters) {
|
||||
delegate.mpWithConverters(converters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config build() {
|
||||
return delegate.build();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.microprofile.config;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
@@ -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.
|
||||
@@ -22,7 +22,6 @@ import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.internal.ClasspathOverrideSource;
|
||||
import io.helidon.config.internal.FileOverrideSource;
|
||||
import io.helidon.config.internal.UrlOverrideSource;
|
||||
import io.helidon.config.spi.AbstractOverrideSource;
|
||||
@@ -75,7 +74,7 @@ public final class OverrideSources {
|
||||
*/
|
||||
public static AbstractOverrideSource.Builder
|
||||
<? extends AbstractOverrideSource.Builder<?, Path>, Path> classpath(String resourceName) {
|
||||
return new ClasspathOverrideSource.ClasspathBuilder(resourceName);
|
||||
return ClasspathOverrideSource.builder().resource(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +85,7 @@ public final class OverrideSources {
|
||||
*/
|
||||
public static AbstractOverrideSource.Builder
|
||||
<? extends AbstractOverrideSource.Builder<?, Path>, Path> file(String file) {
|
||||
return new FileOverrideSource.FileBuilder(Paths.get(file));
|
||||
return FileOverrideSource.builder().path(Paths.get(file));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +96,7 @@ public final class OverrideSources {
|
||||
*/
|
||||
public static AbstractOverrideSource.Builder
|
||||
<? extends AbstractOverrideSource.Builder<?, URL>, URL> url(URL url) {
|
||||
return new UrlOverrideSource.UrlBuilder(url);
|
||||
return UrlOverrideSource.builder().url(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,7 @@ class ProviderImpl implements Config.Context {
|
||||
private static final Logger LOGGER = Logger.getLogger(ConfigFactory.class.getName());
|
||||
|
||||
private final ConfigMapperManager configMapperManager;
|
||||
private final ConfigSource configSource;
|
||||
private final BuilderImpl.ConfigSourceConfiguration configSource;
|
||||
private final OverrideSource overrideSource;
|
||||
private final List<Function<Config, ConfigFilter>> filterProviders;
|
||||
private final boolean cachingEnabled;
|
||||
@@ -66,14 +66,14 @@ class ProviderImpl implements Config.Context {
|
||||
private ConfigSourceChangeEventSubscriber configSourceChangeEventSubscriber;
|
||||
|
||||
private ConfigDiff lastConfigsDiff;
|
||||
private Config lastConfig;
|
||||
private AbstractConfigImpl lastConfig;
|
||||
private OverrideSourceChangeEventSubscriber overrideSourceChangeEventSubscriber;
|
||||
private volatile boolean overrideChangeComplete;
|
||||
private volatile boolean configChangeComplete;
|
||||
|
||||
@SuppressWarnings("ParameterNumber")
|
||||
ProviderImpl(ConfigMapperManager configMapperManager,
|
||||
ConfigSource configSource,
|
||||
BuilderImpl.ConfigSourceConfiguration configSource,
|
||||
OverrideSource overrideSource,
|
||||
List<Function<Config, ConfigFilter>> filterProviders,
|
||||
boolean cachingEnabled,
|
||||
@@ -89,7 +89,7 @@ class ProviderImpl implements Config.Context {
|
||||
this.changesExecutor = changesExecutor;
|
||||
|
||||
this.lastConfigsDiff = null;
|
||||
this.lastConfig = Config.empty();
|
||||
this.lastConfig = (AbstractConfigImpl) Config.empty();
|
||||
|
||||
this.keyResolving = keyResolving;
|
||||
this.aliasGenerator = aliasGenerator;
|
||||
@@ -102,14 +102,14 @@ class ProviderImpl implements Config.Context {
|
||||
configSourceChangeEventSubscriber = null;
|
||||
}
|
||||
|
||||
public Config newConfig() {
|
||||
lastConfig = build(configSource.load());
|
||||
public AbstractConfigImpl newConfig() {
|
||||
lastConfig = build(configSource.compositeSource().load());
|
||||
return lastConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config reload() {
|
||||
rebuild(configSource.load(), true);
|
||||
rebuild(configSource.compositeSource().load(), true);
|
||||
return lastConfig;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ProviderImpl implements Config.Context {
|
||||
return lastConfig;
|
||||
}
|
||||
|
||||
private synchronized Config build(Optional<ObjectNode> rootNode) {
|
||||
private synchronized AbstractConfigImpl build(Optional<ObjectNode> rootNode) {
|
||||
|
||||
// resolve tokens
|
||||
rootNode = rootNode.map(this::resolveKeys);
|
||||
@@ -140,14 +140,16 @@ class ProviderImpl implements Config.Context {
|
||||
rootNode.orElseGet(ObjectNode::empty),
|
||||
targetFilter,
|
||||
this,
|
||||
aliasGenerator);
|
||||
Config config = factory.config();
|
||||
aliasGenerator,
|
||||
configSource.allSources());
|
||||
AbstractConfigImpl config = factory.config();
|
||||
// initialize filters
|
||||
initializeFilters(config, targetFilter);
|
||||
// caching
|
||||
if (cachingEnabled) {
|
||||
targetFilter.enableCaching();
|
||||
}
|
||||
config.initMp();
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -218,7 +220,7 @@ class ProviderImpl implements Config.Context {
|
||||
|
||||
private synchronized void rebuild(Optional<ObjectNode> objectNode, boolean force) {
|
||||
// 1. build new Config
|
||||
Config newConfig = build(objectNode);
|
||||
AbstractConfigImpl newConfig = build(objectNode);
|
||||
// 2. for each subscriber fire event on specific node/key - see AbstractConfigImpl.FilteringConfigChangeEventSubscriber
|
||||
// 3. fire event
|
||||
ConfigDiff configsDiff = ConfigDiff.from(lastConfig, newConfig);
|
||||
@@ -252,7 +254,7 @@ class ProviderImpl implements Config.Context {
|
||||
subscribeConfigSource();
|
||||
subscribeOverrideSource();
|
||||
//check if source has changed - reload
|
||||
rebuild(configSource.load(), false);
|
||||
rebuild(configSource.compositeSource().load(), false);
|
||||
}
|
||||
|
||||
private void cancelSourcesSubscriptions() {
|
||||
@@ -262,7 +264,7 @@ class ProviderImpl implements Config.Context {
|
||||
|
||||
private void subscribeConfigSource() {
|
||||
configSourceChangeEventSubscriber = new ConfigSourceChangeEventSubscriber();
|
||||
configSource.changes().subscribe(configSourceChangeEventSubscriber);
|
||||
configSource.compositeSource().changes().subscribe(configSourceChangeEventSubscriber);
|
||||
}
|
||||
|
||||
private void cancelConfigSource() {
|
||||
@@ -401,7 +403,7 @@ class ProviderImpl implements Config.Context {
|
||||
ProviderImpl.this.changesSubmitter
|
||||
.closeExceptionally(new ConfigException(
|
||||
String.format("'%s' config source changes support has failed. %s",
|
||||
ProviderImpl.this.configSource.description(),
|
||||
ProviderImpl.this.configSource.compositeSource().description(),
|
||||
throwable.getLocalizedMessage()),
|
||||
throwable));
|
||||
}
|
||||
@@ -409,7 +411,7 @@ class ProviderImpl implements Config.Context {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
LOGGER.fine(String.format("'%s' config source changes support has completed.",
|
||||
ProviderImpl.this.configSource.description()));
|
||||
ProviderImpl.this.configSource.compositeSource().description()));
|
||||
|
||||
ProviderImpl.this.configChangeComplete = true;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -28,16 +28,11 @@ 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.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.internal.ConfigUtils;
|
||||
import io.helidon.config.spi.AbstractParsableConfigSource;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
@@ -71,7 +66,7 @@ public class UrlConfigSource extends AbstractParsableConfigSource<Instant> {
|
||||
* <ul>
|
||||
* <li>{@code url} - type {@link URL}</li>
|
||||
* </ul>
|
||||
* Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#init(Config)}.
|
||||
* Optional {@code properties}: see {@link AbstractParsableConfigSource.Builder#config(Config)}.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned config source instance from.
|
||||
* @return new instance of config source described by {@code metaConfig}
|
||||
@@ -80,14 +75,23 @@ public class UrlConfigSource extends AbstractParsableConfigSource<Instant> {
|
||||
* @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#url(URL)
|
||||
* @see AbstractParsableConfigSource.Builder#init(Config)
|
||||
* @see AbstractParsableConfigSource.Builder#config(Config)
|
||||
*/
|
||||
public static UrlConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return (UrlConfigSource) new UrlBuilder(metaConfig.get(URL_KEY).as(URL.class).get())
|
||||
.init(metaConfig)
|
||||
return builder()
|
||||
.config(metaConfig)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A new fluent API builder.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static UrlBuilder builder() {
|
||||
return new UrlBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String uid() {
|
||||
return url.toString();
|
||||
@@ -223,25 +227,39 @@ public class UrlConfigSource extends AbstractParsableConfigSource<Instant> {
|
||||
* If {@code media-type} not set it uses HTTP response header {@code content-type}.
|
||||
* If {@code media-type} not returned it tries to guess it from url suffix.
|
||||
*/
|
||||
public static final class UrlBuilder extends Builder<UrlBuilder, URL> {
|
||||
public static final class UrlBuilder extends Builder<UrlBuilder, URL, UrlConfigSource> {
|
||||
private URL url;
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
*
|
||||
* @param url configuration url
|
||||
*/
|
||||
public UrlBuilder(URL url) {
|
||||
private UrlBuilder() {
|
||||
super(URL.class);
|
||||
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL of the configuration.
|
||||
*
|
||||
* @param url of configuration source
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public UrlBuilder url(URL url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <ul>
|
||||
* <li>{@code url} - URL of the configuration source</li>
|
||||
* </ul>
|
||||
* @param metaConfig configuration properties used to configure a builder instance.
|
||||
* @return updated builder instance
|
||||
*/
|
||||
@Override
|
||||
protected UrlBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public UrlBuilder config(Config metaConfig) {
|
||||
metaConfig.get(URL_KEY).as(URL.class).ifPresent(this::url);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,7 +274,10 @@ public class UrlConfigSource extends AbstractParsableConfigSource<Instant> {
|
||||
*
|
||||
* @return new instance of Url ConfigSource.
|
||||
*/
|
||||
public ConfigSource build() {
|
||||
public UrlConfigSource build() {
|
||||
if (null == url) {
|
||||
throw new IllegalArgumentException("url must be provided");
|
||||
}
|
||||
return new UrlConfigSource(this, url);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -179,7 +179,7 @@ public final class ConfigUtils {
|
||||
* or {@code UTF-8} in case a {@code contentEncoding} is {@code null}
|
||||
* @throws ConfigException in case of unsupported charset name
|
||||
*/
|
||||
static Charset getContentCharset(String contentEncoding) throws ConfigException {
|
||||
public static Charset getContentCharset(String contentEncoding) throws ConfigException {
|
||||
try {
|
||||
return Optional.ofNullable(contentEncoding)
|
||||
.map(Charset::forName)
|
||||
|
||||
@@ -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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package io.helidon.config.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
@@ -24,6 +23,7 @@ import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.spi.AbstractOverrideSource;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
@@ -42,10 +42,10 @@ public class FileOverrideSource extends AbstractOverrideSource<byte[]> {
|
||||
|
||||
private final Path filePath;
|
||||
|
||||
FileOverrideSource(FileBuilder builder, Path filePath) {
|
||||
FileOverrideSource(FileBuilder builder) {
|
||||
super(builder);
|
||||
|
||||
this.filePath = filePath;
|
||||
this.filePath = builder.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,13 +63,29 @@ public class FileOverrideSource extends AbstractOverrideSource<byte[]> {
|
||||
Optional<byte[]> digest = dataStamp();
|
||||
LOGGER.log(Level.FINE, String.format("Getting content from '%s'.", filePath));
|
||||
|
||||
try {
|
||||
OverrideData overrideData = OverrideSource.OverrideData
|
||||
.create(new StringReader(FileSourceHelper.safeReadContent(filePath)));
|
||||
return new Data<>(Optional.of(overrideData), digest);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigException("Cannot load data from source.", e);
|
||||
}
|
||||
OverrideData overrideData = OverrideSource.OverrideData
|
||||
.create(new StringReader(FileSourceHelper.safeReadContent(filePath)));
|
||||
return new Data<>(Optional.of(overrideData), digest);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file override source from meta configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration containing the {@code path} and other configuration options
|
||||
* @return a new file override source
|
||||
*/
|
||||
public static FileOverrideSource create(Config metaConfig) {
|
||||
return builder().config(metaConfig).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder.
|
||||
*
|
||||
* @return builder to create new instances of file override source
|
||||
*/
|
||||
public static FileBuilder builder() {
|
||||
return new FileBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,15 +109,27 @@ public class FileOverrideSource extends AbstractOverrideSource<byte[]> {
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
*
|
||||
* @param path configuration file path
|
||||
*/
|
||||
public FileBuilder(Path path) {
|
||||
private FileBuilder() {
|
||||
super(Path.class);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(path, "file path cannot be null");
|
||||
|
||||
/**
|
||||
* Configure path to look for the source.
|
||||
*
|
||||
* @param path file path
|
||||
* @return updated builder
|
||||
*/
|
||||
public FileBuilder path(Path path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileBuilder config(Config metaConfig) {
|
||||
metaConfig.get("path").as(Path.class).ifPresent(this::path);
|
||||
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,8 +144,10 @@ public class FileOverrideSource extends AbstractOverrideSource<byte[]> {
|
||||
*
|
||||
* @return new instance of File ConfigSource.
|
||||
*/
|
||||
public OverrideSource build() {
|
||||
return new FileOverrideSource(this, path);
|
||||
@Override
|
||||
public FileOverrideSource build() {
|
||||
Objects.requireNonNull(path, "file path cannot be null");
|
||||
return new FileOverrideSource(this);
|
||||
}
|
||||
|
||||
PollingStrategy pollingStrategyInternal() { //just for testing purposes
|
||||
|
||||
@@ -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.
|
||||
@@ -39,9 +39,9 @@ import io.helidon.config.ConfigException;
|
||||
/**
|
||||
* Utilities for file-related source classes.
|
||||
*
|
||||
* @see FileConfigSource
|
||||
* @see io.helidon.config.FileConfigSource
|
||||
* @see FileOverrideSource
|
||||
* @see DirectoryConfigSource
|
||||
* @see io.helidon.config.DirectoryConfigSource
|
||||
*/
|
||||
public class FileSourceHelper {
|
||||
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
|
||||
package io.helidon.config.internal;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.spi.AbstractMpSource;
|
||||
import io.helidon.config.spi.AbstractSource;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
|
||||
@@ -31,7 +36,7 @@ import io.helidon.config.spi.ConfigSource;
|
||||
*
|
||||
* @see io.helidon.config.ConfigSources.MapBuilder
|
||||
*/
|
||||
public class MapConfigSource implements ConfigSource {
|
||||
public class MapConfigSource extends AbstractMpSource<Instant> {
|
||||
|
||||
private final Map<String, String> map;
|
||||
private final String mapSourceName;
|
||||
@@ -44,7 +49,8 @@ public class MapConfigSource implements ConfigSource {
|
||||
* @param strict strict mode flag
|
||||
* @param mapSourceName name of map source
|
||||
*/
|
||||
public MapConfigSource(Map<String, String> map, boolean strict, String mapSourceName) {
|
||||
protected MapConfigSource(Map<String, String> map, boolean strict, String mapSourceName) {
|
||||
super(new Builder());
|
||||
Objects.requireNonNull(map, "map cannot be null");
|
||||
Objects.requireNonNull(mapSourceName, "mapSourceName cannot be null");
|
||||
|
||||
@@ -53,13 +59,71 @@ public class MapConfigSource implements ConfigSource {
|
||||
this.mapSourceName = mapSourceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return ConfigSource.super.description() + (mapSourceName.isEmpty() ? "" : "[" + mapSourceName + "]");
|
||||
private MapConfigSource(Builder builder) {
|
||||
super(builder);
|
||||
this.map = new HashMap<>();
|
||||
this.mapSourceName = "empty";
|
||||
this.strict = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new config source from the provided map, with strict mode set to {@code false}.
|
||||
*
|
||||
* @param map config properties
|
||||
* @return a new map config source
|
||||
*/
|
||||
public static MapConfigSource create(Map<String, String> map) {
|
||||
return create(map, false, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new config source from the provided map.
|
||||
*
|
||||
* @param map config properties
|
||||
* @param strict strict mode flag, if set to {@code true}, parsing would fail if a tree node and a leaf node conflict,
|
||||
* such as for {@code http.ssl=true} and {@code http.ssl.port=1024}.
|
||||
* @param mapSourceName name of map source (for debugging purposes)
|
||||
* @return a new map config source
|
||||
*/
|
||||
public static MapConfigSource create(Map<String, String> map, boolean strict, String mapSourceName) {
|
||||
return new MapConfigSource(map, strict, mapSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ObjectNode> load() {
|
||||
return Optional.of(ConfigUtils.mapToObjectNode(map, strict));
|
||||
protected String uid() {
|
||||
return mapSourceName.isEmpty() ? "" : mapSourceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Instant> dataStamp() {
|
||||
return Optional.of(Instant.EPOCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Data<ObjectNode, Instant> loadData() throws ConfigException {
|
||||
return new Data<>(Optional.of(ConfigUtils.mapToObjectNode(map, strict)), Optional.of(Instant.EPOCH));
|
||||
}
|
||||
|
||||
/**
|
||||
* A fluent API builder for {@link io.helidon.config.internal.MapConfigSource}.
|
||||
*/
|
||||
public static final class Builder extends AbstractSource.Builder<Builder, String, MapConfigSource> {
|
||||
private Builder() {
|
||||
super(String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapConfigSource build() {
|
||||
return new MapConfigSource(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -20,7 +20,9 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.OptionalHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.MetaConfig;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
@@ -31,17 +33,12 @@ import io.helidon.config.spi.ConfigSource;
|
||||
* @see io.helidon.config.ConfigSources#prefixed(String, java.util.function.Supplier)
|
||||
*/
|
||||
public class PrefixedConfigSource implements ConfigSource {
|
||||
private static final String KEY_KEY = "key";
|
||||
|
||||
private final String key;
|
||||
private final ConfigSource source;
|
||||
|
||||
/**
|
||||
* Initialize prefixed config source.
|
||||
*
|
||||
* @param key prefix key
|
||||
* @param source wrapped source
|
||||
*/
|
||||
public PrefixedConfigSource(String key, ConfigSource source) {
|
||||
private PrefixedConfigSource(String key, ConfigSource source) {
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(source, "source cannot be null");
|
||||
|
||||
@@ -49,6 +46,32 @@ public class PrefixedConfigSource implements ConfigSource {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a prefixed config source from meta configuration.
|
||||
* The meta configuration must contain the configuration key {@value #KEY_KEY}
|
||||
* and meta configuration of another config source to be prefixed with the key.
|
||||
*
|
||||
* @param metaConfig meta configuration
|
||||
* @return a new prefixed config source
|
||||
*/
|
||||
public static PrefixedConfigSource create(Config metaConfig) {
|
||||
String prefix = metaConfig.get(KEY_KEY).asString().orElse("");
|
||||
ConfigSource configSource = MetaConfig.configSource(metaConfig);
|
||||
|
||||
return create(prefix, configSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new prefixed config source.
|
||||
*
|
||||
* @param key prefix key
|
||||
* @param source wrapped source
|
||||
* @return a new prefixed config source
|
||||
*/
|
||||
public static PrefixedConfigSource create(String key, ConfigSource source) {
|
||||
return new PrefixedConfigSource(key, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return String.format("prefixed[%s]:%s", key, source.description());
|
||||
@@ -57,7 +80,7 @@ public class PrefixedConfigSource implements ConfigSource {
|
||||
@Override
|
||||
public Optional<ConfigNode.ObjectNode> load() throws ConfigException {
|
||||
return OptionalHelper.from(source.load()
|
||||
.map(originRoot -> new ObjectNodeBuilderImpl().addObject(key, originRoot).build()))
|
||||
.map(originRoot -> new ObjectNodeBuilderImpl().addObject(key, originRoot).build()))
|
||||
.or(Optional::empty)
|
||||
.asOptional();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -27,12 +27,12 @@ import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.OverrideSources;
|
||||
import io.helidon.config.spi.AbstractOverrideSource;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.OverrideSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
|
||||
/**
|
||||
@@ -47,12 +47,33 @@ public class UrlOverrideSource extends AbstractOverrideSource<Instant> {
|
||||
|
||||
private static final String GET_METHOD = "GET";
|
||||
private static final String HEAD_METHOD = "HEAD";
|
||||
private static final String URL_KEY = "url";
|
||||
|
||||
private final URL url;
|
||||
|
||||
UrlOverrideSource(UrlBuilder builder, URL url) {
|
||||
UrlOverrideSource(UrlBuilder builder) {
|
||||
super(builder);
|
||||
this.url = url;
|
||||
|
||||
this.url = builder.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new URL override source from meta configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration containing at least the {@key url} key
|
||||
* @return a new URL override source
|
||||
*/
|
||||
public static UrlOverrideSource create(Config metaConfig) {
|
||||
return builder().config(metaConfig).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder to create URL override source.
|
||||
*
|
||||
* @return a new builder
|
||||
*/
|
||||
public static UrlBuilder builder() {
|
||||
return new UrlBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,15 +148,26 @@ public class UrlOverrideSource extends AbstractOverrideSource<Instant> {
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
*
|
||||
* @param url configuration url
|
||||
*/
|
||||
public UrlBuilder(URL url) {
|
||||
private UrlBuilder() {
|
||||
super(URL.class);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
|
||||
/**
|
||||
* Configure the URL that is source of this overrides.
|
||||
*
|
||||
* @param url url of the resource to load
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public UrlBuilder url(URL url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UrlBuilder config(Config metaConfig) {
|
||||
metaConfig.get(URL_KEY).as(URL.class).ifPresent(this::url);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,8 +182,10 @@ public class UrlOverrideSource extends AbstractOverrideSource<Instant> {
|
||||
*
|
||||
* @return new instance of Url ConfigSource.
|
||||
*/
|
||||
public OverrideSource build() {
|
||||
return new UrlOverrideSource(this, url);
|
||||
public UrlOverrideSource build() {
|
||||
Objects.requireNonNull(url, "url cannot be null");
|
||||
|
||||
return new UrlOverrideSource(this);
|
||||
}
|
||||
|
||||
PollingStrategy pollingStrategyInternal() { //just for testing purposes
|
||||
|
||||
@@ -42,7 +42,7 @@ import io.helidon.config.spi.ConfigParser.Content;
|
||||
* @param <S> a type of data stamp
|
||||
* @see Builder
|
||||
*/
|
||||
public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode, S> implements ConfigSource {
|
||||
public abstract class AbstractConfigSource<S> extends AbstractMpSource<S> implements ConfigSource {
|
||||
|
||||
private final Function<Config.Key, String> mediaTypeMapping;
|
||||
private final Function<Config.Key, ConfigParser> parserMapping;
|
||||
@@ -54,7 +54,7 @@ public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode,
|
||||
*
|
||||
* @param builder builder to be initialized from
|
||||
*/
|
||||
protected AbstractConfigSource(Builder<?, ?> builder) {
|
||||
protected AbstractConfigSource(Builder<?, ?, ?> builder) {
|
||||
super(builder);
|
||||
|
||||
mediaTypeMapping = builder.mediaTypeMapping();
|
||||
@@ -80,7 +80,12 @@ public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode,
|
||||
|| (mediaTypeMapping == null && parserMapping == null)) {
|
||||
return data;
|
||||
}
|
||||
return new Data<>(Optional.of(processObject(data.stamp(), ConfigKeyImpl.of(), data.data().get())), data.stamp());
|
||||
Data<ObjectNode, S> result = new Data<>(Optional.of(processObject(data.stamp(), ConfigKeyImpl.of(), data.data().get())),
|
||||
data.stamp());
|
||||
|
||||
super.processLoadedData(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ConfigNode processNode(Optional<S> datastamp, ConfigKeyImpl key, ConfigNode node) {
|
||||
@@ -151,15 +156,16 @@ public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode,
|
||||
*
|
||||
* @param <B> type of Builder implementation
|
||||
* @param <T> type of key source attributes (target) used to construct polling strategy from
|
||||
* @param <S> Type of the source to be built
|
||||
*/
|
||||
public abstract static class Builder<B extends Builder<B, T>, T>
|
||||
extends AbstractSource.Builder<B, T, ConfigSource>
|
||||
implements io.helidon.common.Builder<ConfigSource> {
|
||||
public abstract static class Builder<B extends Builder<B, T, S>, T, S extends AbstractMpSource<?>>
|
||||
extends AbstractSource.Builder<B, T, S>
|
||||
implements io.helidon.common.Builder<S> {
|
||||
|
||||
private static final String MEDIA_TYPE_MAPPING_KEY = "media-type-mapping";
|
||||
private Function<Config.Key, String> mediaTypeMapping;
|
||||
private Function<Config.Key, ConfigParser> parserMapping;
|
||||
private volatile ConfigSource configSource;
|
||||
private volatile S configSource;
|
||||
|
||||
/**
|
||||
* Initialize builder.
|
||||
@@ -174,7 +180,7 @@ public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode,
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource get() {
|
||||
public S get() {
|
||||
if (configSource == null) {
|
||||
configSource = build();
|
||||
}
|
||||
@@ -187,16 +193,16 @@ public abstract class AbstractConfigSource<S> extends AbstractSource<ObjectNode,
|
||||
* <li>{@code media-type-mapping} - type {@code Map} - key to media type, see {@link #mediaTypeMapping(Function)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param metaConfig configuration properties used to initialize a builder instance.
|
||||
* @param metaConfig configuration properties used to configure a builder instance.
|
||||
* @return modified builder instance
|
||||
*/
|
||||
@Override
|
||||
protected B init(Config metaConfig) {
|
||||
public B config(Config metaConfig) {
|
||||
//media-type-mapping
|
||||
metaConfig.get(MEDIA_TYPE_MAPPING_KEY).detach().asMap()
|
||||
.ifPresent(this::initMediaTypeMapping);
|
||||
|
||||
return super.init(metaConfig);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
private void initMediaTypeMapping(Map<String, String> mediaTypeMapping) {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.config.spi;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
/**
|
||||
* MP Config source basis. Just extend this class to be both Helidon config source and an MP config source.
|
||||
* @param <S> Type of the stamp of this config source
|
||||
*/
|
||||
public abstract class AbstractMpSource<S> extends AbstractSource<ConfigNode.ObjectNode, S> implements ConfigSource, io.helidon.config.spi.ConfigSource {
|
||||
private final AtomicReference<Map<String, String>> currentValues = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* Initializes config source from builder.
|
||||
*
|
||||
* @param builder builder to be initialized from
|
||||
*/
|
||||
protected AbstractMpSource(Builder<?, ?, ?> builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Data<ConfigNode.ObjectNode, S> processLoadedData(Data<ConfigNode.ObjectNode, S> data) {
|
||||
currentValues.set(loadMap(data.data()));
|
||||
return super.processLoadedData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ConfigContext context) {
|
||||
this.changes().subscribe(new Flow.Subscriber<Optional<ConfigNode.ObjectNode>>() {
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
subscription.request(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(Optional<ConfigNode.ObjectNode> item) {
|
||||
currentValues.set(loadMap(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
if (null == currentValues.get()) {
|
||||
currentValues.set(loadMap(load()));
|
||||
}
|
||||
|
||||
return currentValues.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
if (null == currentValues.get()) {
|
||||
currentValues.set(loadMap(load()));
|
||||
}
|
||||
return currentValues.get().get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return description();
|
||||
}
|
||||
|
||||
private static Map<String, String> loadMap(Optional<ConfigNode.ObjectNode> item) {
|
||||
if (item.isPresent()) {
|
||||
ConfigNode.ObjectNode node = item.get();
|
||||
Map<String, String> values = new TreeMap<>();
|
||||
processNode(values, "", node);
|
||||
return values;
|
||||
} else {
|
||||
return CollectionsHelper.mapOf();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processNode(Map<String, String> values, String keyPrefix, ConfigNode.ObjectNode node) {
|
||||
node.forEach((key, configNode) -> {
|
||||
switch (configNode.nodeType()) {
|
||||
case OBJECT:
|
||||
processNode(values, key(keyPrefix, key), (ConfigNode.ObjectNode) configNode);
|
||||
break;
|
||||
case LIST:
|
||||
processNode(values, key(keyPrefix, key), ((ConfigNode.ListNode) configNode));
|
||||
break;
|
||||
case VALUE:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Config node of type: " + configNode.nodeType() + " not supported");
|
||||
}
|
||||
|
||||
String directValue = configNode.get();
|
||||
if (null != directValue) {
|
||||
values.put(key(keyPrefix, key), directValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void processNode(Map<String, String> values, String keyPrefix, ConfigNode.ListNode node) {
|
||||
List<String> directValue = new LinkedList<>();
|
||||
Map<String, String> thisListValues = new HashMap<>();
|
||||
boolean hasDirectValue = true;
|
||||
|
||||
for (int i = 0; i < node.size(); i++) {
|
||||
ConfigNode configNode = node.get(i);
|
||||
String nextKey = key(keyPrefix, String.valueOf(i));
|
||||
switch (configNode.nodeType()) {
|
||||
case OBJECT:
|
||||
processNode(thisListValues, nextKey, (ConfigNode.ObjectNode) configNode);
|
||||
hasDirectValue = false;
|
||||
break;
|
||||
case LIST:
|
||||
processNode(thisListValues, nextKey, (ConfigNode.ListNode) configNode);
|
||||
hasDirectValue = false;
|
||||
break;
|
||||
case VALUE:
|
||||
String value = configNode.get();
|
||||
directValue.add(value);
|
||||
thisListValues.put(nextKey, value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Config node of type: " + configNode.nodeType() + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDirectValue) {
|
||||
values.put(keyPrefix, String.join(",", directValue));
|
||||
} else {
|
||||
values.putAll(thisListValues);
|
||||
}
|
||||
}
|
||||
|
||||
private static String key(String keyPrefix, String key) {
|
||||
if (keyPrefix.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
return keyPrefix + "." + key;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -34,7 +34,7 @@ public abstract class AbstractOverrideSource<S> extends AbstractSource<OverrideS
|
||||
*
|
||||
* @param builder builder to be initialized from
|
||||
*/
|
||||
protected AbstractOverrideSource(Builder builder) {
|
||||
protected AbstractOverrideSource(Builder<?, ?> builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,10 @@ public abstract class AbstractParsableConfigSource<S> extends AbstractConfigSour
|
||||
*
|
||||
* @param <B> type of Builder implementation
|
||||
* @param <T> type of key source attributes (target) used to construct polling strategy from
|
||||
* @param <S> type of the config source to be built
|
||||
*/
|
||||
public abstract static class Builder<B extends Builder<B, T>, T> extends AbstractConfigSource.Builder<B, T> {
|
||||
public abstract static class Builder<B extends Builder<B, T, S>, T, S extends AbstractMpSource<?>>
|
||||
extends AbstractConfigSource.Builder<B, T, S> {
|
||||
private static final String MEDIA_TYPE_KEY = "media-type";
|
||||
private String mediaType;
|
||||
private ConfigParser parser;
|
||||
@@ -148,16 +150,17 @@ public abstract class AbstractParsableConfigSource<S> extends AbstractConfigSour
|
||||
* <li>{@code media-type} - type {@code String}, see {@link #mediaType(String)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param metaConfig configuration properties used to initialize a builder instance.
|
||||
* @param metaConfig configuration properties used to configure a builder instance.
|
||||
* @return modified builder instance
|
||||
*/
|
||||
@Override
|
||||
protected B init(Config metaConfig) {
|
||||
public B config(Config metaConfig) {
|
||||
//media-type
|
||||
metaConfig.get(MEDIA_TYPE_KEY).asString()
|
||||
metaConfig.get(MEDIA_TYPE_KEY)
|
||||
.asString()
|
||||
.ifPresent(this::mediaType);
|
||||
|
||||
return super.init(metaConfig);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.helidon.common.reactive.SubmissionPublisher;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigHelper;
|
||||
import io.helidon.config.MetaConfig;
|
||||
import io.helidon.config.PollingStrategies;
|
||||
import io.helidon.config.RetryPolicies;
|
||||
import io.helidon.config.internal.ConfigThreadFactory;
|
||||
@@ -315,7 +316,9 @@ public abstract class AbstractSource<T, S> implements Source<T> {
|
||||
* polling strategy from
|
||||
* @param <S> type of source that should be built
|
||||
*/
|
||||
public abstract static class Builder<B extends Builder<B, T, S>, T, S> {
|
||||
public abstract static class Builder<B extends Builder<B, T, S>, T, S extends Source<?>>
|
||||
implements io.helidon.common.Builder<S> {
|
||||
|
||||
/**
|
||||
* Default executor where the changes threads are run.
|
||||
*/
|
||||
@@ -358,31 +361,36 @@ public abstract class AbstractSource<T, S> implements Source<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize builder from specified configuration properties.
|
||||
* Configure this builder from an existing configuration (we use the term meta configuration for this
|
||||
* type of configuration, as it is a configuration that builds configuration).
|
||||
* <p>
|
||||
* Supported configuration {@code properties}:
|
||||
* <ul>
|
||||
* <li>{@code optional} - type {@code boolean}, see {@link #optional()}</li>
|
||||
* <li>{@code polling-strategy} - see {@link PollingStrategy} for details about configuration format,
|
||||
* see {@link #pollingStrategy(Supplier)} or {@link #pollingStrategy(Function)}</li>
|
||||
* <li>{@code optional} - type {@code boolean}, see {@link #optional()}</li>
|
||||
* <li>{@code polling-strategy} - see {@link PollingStrategy} for details about configuration format,
|
||||
* see {@link #pollingStrategy(Supplier)} or {@link #pollingStrategy(Function)}</li>
|
||||
* <li>{@code retry-policy} - see {@link io.helidon.config.spi.RetryPolicy} for details about
|
||||
* configuration format</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param metaConfig configuration properties used to initialize a builder instance.
|
||||
*
|
||||
* @param metaConfig configuration to configure this source
|
||||
* @return modified builder instance
|
||||
*/
|
||||
protected B init(Config metaConfig) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public B config(Config metaConfig) {
|
||||
//optional / mandatory
|
||||
metaConfig.get(OPTIONAL_KEY)
|
||||
.asBoolean()
|
||||
.filter(value -> value) //filter `true` only
|
||||
.ifPresent(value -> optional());
|
||||
.ifPresent(this::optional);
|
||||
|
||||
//polling-strategy
|
||||
metaConfig.get(POLLING_STRATEGY_KEY)
|
||||
.ifExists(cfg -> pollingStrategy(PollingStrategyConfigMapper.instance().apply(cfg, targetType)));
|
||||
.ifExists(cfg -> pollingStrategy((t -> MetaConfig.pollingStrategy(cfg).apply(t))));
|
||||
|
||||
//retry-policy
|
||||
metaConfig.get(RETRY_POLICY_KEY)
|
||||
.as(RetryPolicy::create)
|
||||
.ifPresent(this::retryPolicy);
|
||||
.ifExists(cfg -> retryPolicy(MetaConfig.retryPolicy(cfg)));
|
||||
|
||||
return thisBuilder;
|
||||
}
|
||||
@@ -433,6 +441,15 @@ public abstract class AbstractSource<T, S> implements Source<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of target used by this builder.
|
||||
*
|
||||
* @return target type, used by {@link #pollingStrategy(java.util.function.Function)}
|
||||
*/
|
||||
public Class<T> targetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built {@link ConfigSource} will not be mandatory, i.e. it is ignored if configuration target does not exists.
|
||||
*
|
||||
@@ -444,6 +461,18 @@ public abstract class AbstractSource<T, S> implements Source<T> {
|
||||
return thisBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built {@link ConfigSource} will be optional ({@code true}) or mandatory ({@code false}).
|
||||
*
|
||||
* @param optional set to {@code true} to mark this source optional.
|
||||
* @return a modified builder instance
|
||||
*/
|
||||
public B optional(boolean optional) {
|
||||
this.mandatory = !optional;
|
||||
|
||||
return thisBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies "observe-on" {@link Executor} to be used to deliver
|
||||
* {@link ConfigSource#changes() config source changes}. The same
|
||||
@@ -500,6 +529,20 @@ public abstract class AbstractSource<T, S> implements Source<T> {
|
||||
return thisBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link RetryPolicy} that will be responsible for invocation of {@link AbstractSource#load()}.
|
||||
* <p>
|
||||
* The default reply policy is {@link RetryPolicies#justCall()}.
|
||||
* <p>
|
||||
* Create a custom policy or use the built-in policy constructed with a {@link RetryPolicies#repeat(int) builder}.
|
||||
*
|
||||
* @param retryPolicy retry policy
|
||||
* @return a modified builder instance
|
||||
*/
|
||||
public B retryPolicy(RetryPolicy retryPolicy) {
|
||||
return retryPolicy(() -> retryPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds new instance of {@code S}.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
@@ -21,12 +21,12 @@ import java.util.Optional;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
|
||||
/**
|
||||
* Interface used to mark changeable source.
|
||||
* A changeable component is a component that may identify a change
|
||||
* at an arbitrary point in time and that can notify listeners of such a change.
|
||||
*
|
||||
* @param <T> a type of source
|
||||
*/
|
||||
public interface Changeable<T> { //TODO later to be extended just by selected sources
|
||||
|
||||
/**
|
||||
* Returns a {@code Flow.Publisher} to which the caller can subscribe in
|
||||
* order to receive change notifications.
|
||||
|
||||
@@ -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.
|
||||
@@ -20,7 +20,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.common.Builder;
|
||||
import io.helidon.config.internal.ConfigUtils;
|
||||
import io.helidon.config.internal.ListNodeBuilderImpl;
|
||||
import io.helidon.config.internal.ObjectNodeBuilderImpl;
|
||||
@@ -175,6 +174,19 @@ public interface ConfigNode extends Supplier<String> {
|
||||
return ConfigUtils.EmptyObjectNodeHolder.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object node containing a single simple value.
|
||||
*
|
||||
* @param key key of the value
|
||||
* @param value value
|
||||
* @return a new object node
|
||||
*/
|
||||
static ObjectNode simple(String key, String value) {
|
||||
return ObjectNode.builder()
|
||||
.addValue(key, value)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link Builder}.
|
||||
*
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
|
||||
package io.helidon.config.spi;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
|
||||
/**
|
||||
@@ -38,211 +35,6 @@ import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ConfigSource extends Source<ObjectNode>, Supplier<ConfigSource> {
|
||||
|
||||
/**
|
||||
* Initializes a {@link ConfigSource} from meta-configuration.
|
||||
* <p>
|
||||
* Meta-config can contain the following top level properties to define one
|
||||
* or more {@code ConfigSource}s:
|
||||
* <ul>
|
||||
* <li>{@code type} - specifies the built-in configuration type
|
||||
* (environment-variables, system-properties, directory, file, url,
|
||||
* prefixed, classpath) or the <a href="#customSourcesAndTypes">custom
|
||||
* config types</a> of the {@code ConfigSource} being defined.
|
||||
* </li>
|
||||
* <li>{@code class} - fully-qualified class name of one of:
|
||||
* <ul>
|
||||
* <li>a {@link ConfigSource} implementation,</li>
|
||||
* <li>a {@link Config.Builder} implementation with a {@code build()} method
|
||||
* that returns a {@code ConfigSource} instance.</li>
|
||||
* </ul>
|
||||
* See the section below on <a href="#customSourcesAndTypes">custom source
|
||||
* types</a>.
|
||||
* </ul>
|
||||
* The meta-config for a source should specify either {@code type} or
|
||||
* {@code class} but not both. If the meta-config specifies both the config
|
||||
* system ignores the {@code class} information.
|
||||
* <p>
|
||||
* As the config system loads configuration it uses mappers to convert the
|
||||
* raw data into Java types. See {@link ConfigMapperProvider} for
|
||||
* details about mapping.
|
||||
* <p>
|
||||
* The meta-config can modify a {@code type} or {@code class} source
|
||||
* declaration using {@code properties}. See
|
||||
* {@link AbstractParsableConfigSource.Builder#init(Config)} for the
|
||||
* available properties for types other than {@code system-properties} and
|
||||
* {@code environment-variables} (which do not support {@code properties}
|
||||
* settings).
|
||||
* <table class="config">
|
||||
* <caption><b>Predefined Configuration Source Types</b></caption>
|
||||
* <tr>
|
||||
* <th>Source Type</th>
|
||||
* <th>Further Information</th>
|
||||
* <th>Mandatory Properties</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code system-properties}</td>
|
||||
* <td>{@link ConfigSources#systemProperties()}</td>
|
||||
* <td>none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code environment-variables}</td>
|
||||
* <td>{@link ConfigSources#environmentVariables()}</td>
|
||||
* <td>none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code classpath}</td>
|
||||
* <td>{@link ConfigSources#classpath(String)}</td>
|
||||
* <td>{@code resource}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code file}</td>
|
||||
* <td>{@link ConfigSources#file(String)}</td>
|
||||
* <td>{@code path}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code directory}</td>
|
||||
* <td>{@link ConfigSources#directory(String)}</td>
|
||||
* <td>{@code path}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code url}</td>
|
||||
* <td>{@link ConfigSources#url(URL)}</td>
|
||||
* <td>{@code url}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code prefixed}</td>
|
||||
* <td>{@link ConfigSources#prefixed(String, Supplier)}</td>
|
||||
* <td> {@code key}<br> {@code type} or {@code class}<br> {@code properties}
|
||||
* </td>
|
||||
* <td></td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* Example configuration in HOCON format:
|
||||
* <pre>
|
||||
* sources = [
|
||||
* {
|
||||
* type = "environment-variables"
|
||||
* }
|
||||
* {
|
||||
* type = "system-properties"
|
||||
* }
|
||||
* {
|
||||
* type = "directory"
|
||||
* properties {
|
||||
* path = "conf/secrets"
|
||||
* media-type-mapping {
|
||||
* yaml = "application/x-yaml"
|
||||
* password = "application/base64"
|
||||
* }
|
||||
* polling-strategy {
|
||||
* type = "regular"
|
||||
* properties {
|
||||
* interval = "PT15S"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* type = "url"
|
||||
* properties {
|
||||
* url = "http://config-service/my-config"
|
||||
* media-type = "application/hocon"
|
||||
* optional = true
|
||||
* retry-policy {
|
||||
* type = "repeat"
|
||||
* properties {
|
||||
* retries = 3
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* type = "file"
|
||||
* properties {
|
||||
* path = "conf/config.yaml"
|
||||
* polling-strategy {
|
||||
* type = "watch"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* type = "prefixed"
|
||||
* properties {
|
||||
* key = "app"
|
||||
* type = "classpath"
|
||||
* properties {
|
||||
* resource = "app.yaml"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* type = "classpath"
|
||||
* properties {
|
||||
* resource = "default.yaml"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* </pre> The example refers to the built-in {@code polling-strategy} types
|
||||
* {@code regular} and {@code watch}. See {@link PollingStrategy} for
|
||||
* details about all supported properties and custom implementation support.
|
||||
* It also shows the built-in {@code retry-policy} type {@code repeat}. See
|
||||
* {@link RetryPolicy#create(Config) RetryPolicy} for more information.
|
||||
*
|
||||
* <h2><a id="customSourcesAndTypes">Custom Sources and Source
|
||||
* Types</a></h2>
|
||||
* <h3>Custom Configuration Sources</h3>
|
||||
* The application can define a custom config source using {@code class}
|
||||
* instead of {@code type} in the meta-configuration. The referenced class
|
||||
* must implement either {@link ConfigSource} or {@link Config.Builder}. If
|
||||
* the custom implementation extends
|
||||
* {@link AbstractParsableConfigSource.Builder} then the config system will
|
||||
* invoke its {@code init} method passing a {@code Config} object
|
||||
* representing the information from the meta-configuration for that custom
|
||||
* source. The implementation can then use the relevant properties to load
|
||||
* and manage the configuration from the origin.
|
||||
* <h3>Custom Configuration Source Types</h3>
|
||||
* The application can also add a custom source type to the list of built-in
|
||||
* source types. The config system looks for the resource
|
||||
* {@code META-INF/resources/meta-config-sources.properties} on the
|
||||
* classpath and uses its contents to define custom source types. For each
|
||||
* property the name is a new custom source type and the value is the
|
||||
* fully-qualified class name of the custom {@code ConfigSource} or a
|
||||
* builder for a custom {@code ConfigSource}.
|
||||
* <p>
|
||||
* For example, the module {@code helidon-config-git} includes the resource
|
||||
* {@code META-INF/resources/meta-config-sources.properties} containing
|
||||
* <pre>
|
||||
* git = io.helidon.config.git.GitConfigSourceBuilder
|
||||
* </pre> This defines the new source type {@code git} which can then be
|
||||
* referenced from meta-configuration this way:
|
||||
* <pre>
|
||||
* {
|
||||
* type = "git"
|
||||
* properties {
|
||||
* path = "application.conf"
|
||||
* directory = "/app-config/"
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize the
|
||||
* {@code ConfigSource}
|
||||
* @return {@code ConfigSource} described by {@code metaConfig}
|
||||
* @throws MissingValueException if the configuration tree does not contain
|
||||
* all expected sub-nodes required by the mapper implementation to provide
|
||||
* an instance of the corresponding Java type.
|
||||
* @throws ConfigMappingException if the mapper fails to map the (existing)
|
||||
* configuration tree represented by the supplied configuration node to an
|
||||
* instance of the given Java type
|
||||
* @see ConfigSources#load(Supplier[])
|
||||
* @see ConfigSources#load(Config)
|
||||
*/
|
||||
static ConfigSource create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return ConfigSourceConfigMapper.instance().apply(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
default ConfigSource get() {
|
||||
return this;
|
||||
@@ -257,5 +49,4 @@ public interface ConfigSource extends Source<ObjectNode>, Supplier<ConfigSource>
|
||||
*/
|
||||
default void init(ConfigContext context) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 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.config.spi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.OptionalHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigMappers;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.internal.ClasspathConfigSource;
|
||||
import io.helidon.config.internal.DirectoryConfigSource;
|
||||
import io.helidon.config.internal.FileConfigSource;
|
||||
import io.helidon.config.internal.UrlConfigSource;
|
||||
|
||||
/**
|
||||
* Mapper to convert meta-configuration to a {@link ConfigSource} instance.
|
||||
*/
|
||||
class ConfigSourceConfigMapper implements Function<Config, ConfigSource> {
|
||||
|
||||
private static final String META_CONFIG_SOURCES_PROPERTIES = "META-INF/resources/meta-config-sources.properties";
|
||||
|
||||
private static final String PROPERTIES_KEY = "properties";
|
||||
private static final String TYPE_KEY = "type";
|
||||
private static final String CLASS_KEY = "class";
|
||||
private static final String KEY_KEY = "key";
|
||||
|
||||
private static final String SYSTEM_PROPERTIES_TYPE = "system-properties";
|
||||
private static final String ENVIRONMENT_VARIABLES_TYPE = "environment-variables";
|
||||
private static final String PREFIXED_TYPE = "prefixed";
|
||||
private static final String CLASSPATH_TYPE = "classpath";
|
||||
private static final String FILE_TYPE = "file";
|
||||
private static final String DIRECTORY_TYPE = "directory";
|
||||
private static final String URL_TYPE = "url";
|
||||
|
||||
/**
|
||||
* Maps custom source `type` to custom `class`.
|
||||
*/
|
||||
private final Map<String, String> customSources;
|
||||
|
||||
ConfigSourceConfigMapper() {
|
||||
customSources = initCustomSources();
|
||||
}
|
||||
|
||||
private static Map<String, String> initCustomSources() {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
Enumeration<URL> e = Thread.currentThread().getContextClassLoader()
|
||||
.getResources(META_CONFIG_SOURCES_PROPERTIES);
|
||||
|
||||
while (e.hasMoreElements()) {
|
||||
URL resource = e.nextElement();
|
||||
try (InputStream is = resource.openStream()) {
|
||||
properties.load(is);
|
||||
}
|
||||
}
|
||||
Map<String, String> providers = new HashMap<>();
|
||||
for (String type : properties.stringPropertyNames()) {
|
||||
providers.put(type, properties.getProperty(type));
|
||||
}
|
||||
return providers;
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigException("Loading of " + META_CONFIG_SOURCES_PROPERTIES
|
||||
+ " resources has failed with exception.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
static ConfigSourceConfigMapper instance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource apply(Config config) throws ConfigMappingException, MissingValueException {
|
||||
Config properties = config.get(PROPERTIES_KEY) // use properties config node
|
||||
.asNode()
|
||||
.orElse(Config.empty(config)); // or empty config node
|
||||
|
||||
return OptionalHelper.from(config.get(TYPE_KEY)
|
||||
.asString() // `type` is specified
|
||||
.flatMap(type -> OptionalHelper
|
||||
.from(builtin(type, properties)) // return built-in source
|
||||
.or(() -> providers(type, properties))
|
||||
.asOptional())) // or use sources - custom type to class mapping
|
||||
.or(() -> config.get(CLASS_KEY)
|
||||
.as(Class.class) // `class` is specified
|
||||
.flatMap(clazz -> custom(clazz, properties))) // return custom source
|
||||
.asOptional()
|
||||
.orElseThrow(() -> new ConfigMappingException(config.key(), "Uncompleted source configuration."));
|
||||
}
|
||||
|
||||
private Optional<ConfigSource> builtin(String type, Config properties) {
|
||||
final ConfigSource configSource;
|
||||
|
||||
switch (type) {
|
||||
case SYSTEM_PROPERTIES_TYPE:
|
||||
configSource = ConfigSources.systemProperties();
|
||||
break;
|
||||
case ENVIRONMENT_VARIABLES_TYPE:
|
||||
configSource = ConfigSources.environmentVariables();
|
||||
break;
|
||||
case PREFIXED_TYPE:
|
||||
configSource = ConfigSources.prefixed(properties.get(KEY_KEY).asString().orElse(""),
|
||||
properties.as(ConfigSource::create).get());
|
||||
break;
|
||||
case CLASSPATH_TYPE:
|
||||
configSource = properties.as(ClasspathConfigSource::create).get();
|
||||
break;
|
||||
case FILE_TYPE:
|
||||
configSource = properties.as(FileConfigSource::create).get();
|
||||
break;
|
||||
case DIRECTORY_TYPE:
|
||||
configSource = properties.as(DirectoryConfigSource::create).get();
|
||||
break;
|
||||
case URL_TYPE:
|
||||
configSource = properties.as(UrlConfigSource::create).get();
|
||||
break;
|
||||
default:
|
||||
configSource = null;
|
||||
}
|
||||
return Optional.ofNullable(configSource);
|
||||
}
|
||||
|
||||
private Optional<ConfigSource> providers(String type, Config properties) {
|
||||
return Optional.ofNullable(customSources.get(type))
|
||||
.map(ConfigMappers::toClass)
|
||||
.flatMap(clazz -> custom(clazz, properties));
|
||||
}
|
||||
|
||||
private Optional<ConfigSource> custom(Class<?> clazz, Config properties) {
|
||||
Object source = properties.as(clazz).get();
|
||||
|
||||
if (source instanceof ConfigSource) {
|
||||
return Optional.of((ConfigSource) source);
|
||||
}
|
||||
|
||||
throw new ConfigException("Failed to process configuration metadata, configured class " + clazz.getName() + " does "
|
||||
+ "not implement ConfigSource");
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton holder for {@link ConfigSourceConfigMapper}.
|
||||
*/
|
||||
static final class SingletonHolder {
|
||||
private static final ConfigSourceConfigMapper INSTANCE = new ConfigSourceConfigMapper();
|
||||
|
||||
private SingletonHolder() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.config.spi;
|
||||
|
||||
/**
|
||||
* Java service loader service to provide a config source based on meta configuration.
|
||||
*/
|
||||
public interface ConfigSourceProvider extends MetaConfigurableProvider<ConfigSource> {
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.config.spi;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
|
||||
/**
|
||||
* Configurable object of config that can be loaded using a Java service loader.
|
||||
* This is used by {@link io.helidon.config.MetaConfig}.
|
||||
*/
|
||||
interface MetaConfigurableProvider<T> {
|
||||
/**
|
||||
* Return true if this provider supports the type of meta-configurable object.
|
||||
* @param type type that is supported (such as {@code file} for {@link io.helidon.config.spi.ConfigSource} meta configurable)
|
||||
* @return {@code true} if this provider can create instances of the type
|
||||
*/
|
||||
boolean supports(String type);
|
||||
|
||||
/**
|
||||
* Create an instance of the meta configurable using the provided meta configuration.
|
||||
*
|
||||
* @param type type of the meta configurable
|
||||
* @param metaConfig meta configuration
|
||||
* @return meta configurable configured from the metaConfig
|
||||
*/
|
||||
T create(String type, Config metaConfig);
|
||||
|
||||
/**
|
||||
* Return a set of supported types. Used for error handling.
|
||||
*
|
||||
* @return a set of types supported by this provider
|
||||
*/
|
||||
Set<String> supported();
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
|
||||
/**
|
||||
* Source of config override settings.
|
||||
@@ -132,13 +133,15 @@ public interface OverrideSource extends Source<OverrideSource.OverrideData>, Sup
|
||||
*
|
||||
* @param reader a source
|
||||
* @return a new instance
|
||||
* @throws IOException when an error occurred when reading from the
|
||||
* @throws io.helidon.config.ConfigException when an error occurred when reading from the
|
||||
* reader
|
||||
*/
|
||||
public static OverrideData create(Reader reader) throws IOException {
|
||||
public static OverrideData create(Reader reader) {
|
||||
OrderedProperties properties = new OrderedProperties();
|
||||
try (Reader autocloseableReader = reader) {
|
||||
properties.load(autocloseableReader);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigException("Cannot load data from reader.", e);
|
||||
}
|
||||
List<Map.Entry<Predicate<Config.Key>, String>> data = properties.orderedMap().entrySet()
|
||||
.stream()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.config.spi;
|
||||
|
||||
/**
|
||||
* Java service loader service to provide a override source based on meta configuration.
|
||||
*/
|
||||
public interface OverrideSourceProvider extends MetaConfigurableProvider<OverrideSource> {
|
||||
}
|
||||
@@ -55,11 +55,11 @@ import io.helidon.config.PollingStrategies;
|
||||
* {@link AbstractParsableConfigSource} can use a different
|
||||
* {@code PollingStrategy}.
|
||||
* <p>
|
||||
* As described with {@link ConfigSource#create(Config)}, the config system can
|
||||
* As described with {@link io.helidon.config.MetaConfig#configSource(io.helidon.config.Config)}, the config system can
|
||||
* load {@code ConfigSource}s using meta-configuration, which supports
|
||||
* specifying polling strategies. All {@link PollingStrategies built-in polling
|
||||
* strategies} and custom ones are supported. (The support is tightly connected
|
||||
* with {@link AbstractSource.Builder#init(Config) AbstractSource extensions}
|
||||
* with {@link AbstractSource.Builder#config(Config) AbstractSource extensions}
|
||||
* and will not be automatically provided by any another config source
|
||||
* implementations.)
|
||||
* <p>
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 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.config.spi;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
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.ConfigMappingException;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.PollingStrategies;
|
||||
|
||||
/**
|
||||
* Mapper to convert meta-configuration to a {@link PollingStrategy} instance.
|
||||
*/
|
||||
class PollingStrategyConfigMapper {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PollingStrategyConfigMapper.class.getName());
|
||||
|
||||
private static final String PROPERTIES_KEY = "properties";
|
||||
private static final String TYPE_KEY = "type";
|
||||
private static final String CLASS_KEY = "class";
|
||||
|
||||
private static final String REGULAR_TYPE = "regular";
|
||||
private static final String WATCH_TYPE = "watch";
|
||||
|
||||
static PollingStrategyConfigMapper instance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public <T> Function<T, Supplier<PollingStrategy>> apply(Config config, Class<T> targetType)
|
||||
throws ConfigMappingException, MissingValueException {
|
||||
Config properties = config.get(PROPERTIES_KEY) // use properties config node
|
||||
.asNode()
|
||||
.orElse(Config.empty(config)); // or empty config node
|
||||
|
||||
return OptionalHelper.from(config.get(TYPE_KEY).asString() // `type` is specified
|
||||
.flatMap(type -> this
|
||||
.builtin(type, properties, targetType))) // return built-in polling strategy
|
||||
.or(() -> config.get(CLASS_KEY)
|
||||
.as(Class.class) // `class` is specified
|
||||
.flatMap(clazz -> custom(clazz, properties, targetType))) // return custom polling strategy
|
||||
.asOptional()
|
||||
.orElseThrow(() -> new ConfigMappingException(config.key(), "Uncompleted polling-strategy configuration."));
|
||||
}
|
||||
|
||||
private <T> Optional<Function<T, Supplier<PollingStrategy>>> builtin(String type,
|
||||
Config properties,
|
||||
Class<T> targetType) {
|
||||
final Function<T, Supplier<PollingStrategy>> pollingStrategy;
|
||||
switch (type) {
|
||||
case REGULAR_TYPE:
|
||||
pollingStrategy = target -> properties.as(PollingStrategies.ScheduledBuilder.class).get();
|
||||
break;
|
||||
case WATCH_TYPE:
|
||||
pollingStrategy = PollingStrategyConfigMapper::watchSupplier;
|
||||
break;
|
||||
default:
|
||||
pollingStrategy = null;
|
||||
}
|
||||
return Optional.ofNullable(pollingStrategy);
|
||||
}
|
||||
|
||||
private static <T> Supplier<PollingStrategy> watchSupplier(T target) {
|
||||
if (target instanceof Path) {
|
||||
Path path = (Path) target;
|
||||
return PollingStrategies.watch(path);
|
||||
}
|
||||
throw new ConfigException("Incorrect target type ('" + target.getClass().getName()
|
||||
+ "') for WATCH polling strategy. Expected 'Path'.");
|
||||
}
|
||||
|
||||
private <T> Optional<Function<T, Supplier<PollingStrategy>>> custom(Class<?> clazz,
|
||||
Config properties,
|
||||
Class<T> targetType) {
|
||||
Function<T, Supplier<PollingStrategy>> pollingStrategyFunction;
|
||||
if (PollingStrategy.class.isAssignableFrom(clazz)) {
|
||||
// set class is PollingStrategy implementation
|
||||
try {
|
||||
// use public constructor with target parameter
|
||||
Constructor<?> constructor = clazz.getConstructor(targetType);
|
||||
MethodHandle constructorHandle = MethodHandles.publicLookup().unreflectConstructor(constructor);
|
||||
|
||||
pollingStrategyFunction = customSupplier(constructorHandle);
|
||||
} catch (NoSuchMethodException | IllegalAccessException ex) {
|
||||
LOGGER.log(Level.FINE, ex, () -> clazz.getName() + " does not have public constructor with single parameter ("
|
||||
+ targetType.getName() + "). Generic instance from Config will be used.");
|
||||
// use generic mapping as a fallback
|
||||
pollingStrategyFunction = target -> (Supplier<PollingStrategy>) properties.as(clazz).get();
|
||||
}
|
||||
} else {
|
||||
// use builder pattern as a fallback
|
||||
throw new ConfigException("Configured polling strategy class "
|
||||
+ clazz.getName()
|
||||
+ " does not implement PollingStrategy");
|
||||
}
|
||||
return Optional.ofNullable(pollingStrategyFunction);
|
||||
}
|
||||
|
||||
private static <T> Function<T, Supplier<PollingStrategy>> customSupplier(MethodHandle constructorHandle) {
|
||||
return target -> {
|
||||
try {
|
||||
return (Supplier<PollingStrategy>) constructorHandle.invoke(target);
|
||||
} catch (Throwable ex) {
|
||||
throw new ConfigException("Creating new polling strategy instance has failed with exception.", ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton holder for {@link PollingStrategyConfigMapper}.
|
||||
*/
|
||||
static class SingletonHolder {
|
||||
private static final PollingStrategyConfigMapper INSTANCE = new PollingStrategyConfigMapper();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.config.spi;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Java service loader service to create a polling strategy factory based on meta configuration.
|
||||
*/
|
||||
public interface PollingStrategyProvider extends MetaConfigurableProvider<Function<Object, PollingStrategy>> {
|
||||
}
|
||||
@@ -16,15 +16,8 @@
|
||||
|
||||
package io.helidon.config.spi;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.RetryPolicies;
|
||||
|
||||
/**
|
||||
* Mechanism for controlling retry of attempts to load data by an
|
||||
* {@link AbstractSource}.
|
||||
@@ -38,89 +31,7 @@ import io.helidon.config.RetryPolicies;
|
||||
* chooses not to retry in case of errors.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RetryPolicy extends Supplier<RetryPolicy> {
|
||||
|
||||
/**
|
||||
* Constructs a {@code RetryPolicy} from meta-configuration.
|
||||
* <p>
|
||||
* As described with {@link ConfigSource#create(Config)}, the config system
|
||||
* can load {@code ConfigSource}s using meta-configuration, which supports
|
||||
* specifying retry policies. The
|
||||
* {@link RetryPolicies built-in retry policies} and custom ones are
|
||||
* supported. (The support is tightly connected with
|
||||
* {@link AbstractSource.Builder#init(Config) AbstractSource extensions} and
|
||||
* will not be automatically provided by any another config source
|
||||
* implementations.)
|
||||
* <p>
|
||||
* The meta-configuration for a config source can set the property
|
||||
* {@code retry-policy} using the following nested {@code properties}:
|
||||
* <ul>
|
||||
* <li>{@code type} - name of the retry policy implementation.
|
||||
* <table class="config">
|
||||
* <caption>Built-in Retry Policies</caption>
|
||||
* <tr>
|
||||
* <th>Name</th>
|
||||
* <th>Policy</th>
|
||||
* <th>Properties</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code repeat}</td>
|
||||
* <td>Tries to load at regular intervals until the retry count reaches
|
||||
* {@code retries}. See {@link RetryPolicies#repeat(int)}, and
|
||||
* {@link RetryPolicies.Builder} for details on the {@code properties}.</td>
|
||||
* <td>
|
||||
* <ul>
|
||||
* <li>{@code retries} (required) in {@code int} format</li>
|
||||
* <li>{@code delay} in {@link Duration} format</li>
|
||||
* <li>{@code delay-factor} - in {@code double} format</li>
|
||||
* <li>{@code call-timeout} - in {@link Duration} format</li>
|
||||
* <li>{@code overall-timeout} - in {@link Duration} format</li>
|
||||
* </ul>
|
||||
* </td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* </li>
|
||||
* <li>{@code class} - fully qualified class name of custom retry policy
|
||||
* implementation or a builder class that implements a {@code build()}
|
||||
* method that returns a {@code RetryPolicy}.
|
||||
* </li>
|
||||
* </ul>
|
||||
* For a given config source use either {@code type} or {@code class} to
|
||||
* indicate a retry policy but not both. If both appear the config system
|
||||
* ignores the {@code class} setting.
|
||||
* <p>
|
||||
* See {@link ConfigSource#create(Config)} for example of using built-in retry
|
||||
* policies.
|
||||
* <h3>Meta-configuration Support for Custom Retry Policies</h3>
|
||||
* To support settings in meta-configuration, a custom retry policy must
|
||||
* follow this pattern.
|
||||
* <p>
|
||||
* The implementation class should define a Java bean property for each
|
||||
* meta-configuration property it needs to support. The config system uses
|
||||
* mapping functions to convert the text in the
|
||||
* meta-configuration into the correct Java type and then assigns the value
|
||||
* to the correspondingly-named Java bean property defined on the custom
|
||||
* policy instance. See the built-in mappers defined in
|
||||
* {@link io.helidon.config.ConfigMappers} to see what Java types are
|
||||
* automatically supported.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned retry
|
||||
* policy instance from.
|
||||
* @return new instance of retry policy described by {@code metaConfig}
|
||||
* @throws MissingValueException in case the configuration tree does not
|
||||
* contain all expected sub-nodes required by the mapper implementation to
|
||||
* provide instance of Java type.
|
||||
* @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 ConfigSources#load(Supplier[])
|
||||
* @see ConfigSources#load(Config)
|
||||
*/
|
||||
static RetryPolicy create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return RetryPolicyConfigMapper.instance().apply(metaConfig);
|
||||
}
|
||||
|
||||
public interface RetryPolicy {
|
||||
/**
|
||||
* Invokes the provided {@code Supplier} to read the source data and returns
|
||||
* that data.
|
||||
@@ -155,9 +66,4 @@ public interface RetryPolicy extends Supplier<RetryPolicy> {
|
||||
default boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default RetryPolicy get() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 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.config.spi;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.OptionalHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.RetryPolicies;
|
||||
|
||||
/**
|
||||
* Mapper to convert meta-configuration to {@link RetryPolicy} instance.
|
||||
*/
|
||||
class RetryPolicyConfigMapper implements Function<Config, RetryPolicy> {
|
||||
|
||||
private static final String PROPERTIES_KEY = "properties";
|
||||
private static final String TYPE_KEY = "type";
|
||||
private static final String CLASS_KEY = "class";
|
||||
|
||||
private static final String REPEAT_TYPE = "repeat";
|
||||
|
||||
static RetryPolicyConfigMapper instance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryPolicy apply(Config config) throws ConfigMappingException, MissingValueException {
|
||||
Config properties = config.get(PROPERTIES_KEY) // use properties config node
|
||||
.asNode()
|
||||
.orElse(Config.empty(config)); // or empty config node
|
||||
|
||||
return OptionalHelper.from(config.get(TYPE_KEY).asString() // `type` is specified
|
||||
.flatMap(type -> this.builtin(type, properties))) // return built-in retry policy
|
||||
.or(() -> config.get(CLASS_KEY)
|
||||
.as(Class.class) // `class` is specified
|
||||
.flatMap(clazz -> custom(clazz, properties))) // return custom retry policy
|
||||
.asOptional()
|
||||
.orElseThrow(() -> new ConfigMappingException(config.key(), "Uncompleted retry-policy configuration."));
|
||||
}
|
||||
|
||||
private Optional<RetryPolicy> builtin(String type, Config properties) {
|
||||
final RetryPolicy retryPolicy;
|
||||
switch (type) {
|
||||
case REPEAT_TYPE:
|
||||
retryPolicy = properties.as(RetryPolicies.Builder.class).get().get();
|
||||
break;
|
||||
default:
|
||||
retryPolicy = null;
|
||||
}
|
||||
return Optional.ofNullable(retryPolicy);
|
||||
}
|
||||
|
||||
private Optional<RetryPolicy> custom(Class<?> clazz, Config properties) {
|
||||
final RetryPolicy retryPolicy;
|
||||
if (RetryPolicy.class.isAssignableFrom(clazz)) {
|
||||
retryPolicy = properties.as((Class<RetryPolicy>) clazz).get();
|
||||
} else {
|
||||
throw new ConfigException("Configured retry policy class " + clazz.getName() + " does not implement RetryPolicy");
|
||||
}
|
||||
return Optional.of(retryPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton holder for {@link RetryPolicyConfigMapper}.
|
||||
*/
|
||||
static class SingletonHolder {
|
||||
private static final RetryPolicyConfigMapper INSTANCE = new RetryPolicyConfigMapper();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -13,8 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.helidon.config.spi;
|
||||
|
||||
/**
|
||||
* CDI extension for Helidon implementation of microprofile config.
|
||||
* Java service loader service to create retry policy based on meta configuration.
|
||||
*/
|
||||
package io.helidon.microprofile.config;
|
||||
public interface RetryPolicyProvider extends MetaConfigurableProvider<RetryPolicy> {
|
||||
}
|
||||
@@ -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.
|
||||
@@ -25,6 +25,9 @@ module io.helidon.config {
|
||||
|
||||
requires transitive io.helidon.common;
|
||||
requires transitive io.helidon.common.reactive;
|
||||
requires io.helidon.common.serviceloader;
|
||||
requires io.helidon.common.media.type;
|
||||
requires transitive microprofile.config.api;
|
||||
|
||||
exports io.helidon.config;
|
||||
exports io.helidon.config.spi;
|
||||
@@ -34,9 +37,15 @@ module io.helidon.config {
|
||||
uses io.helidon.config.spi.ConfigMapperProvider;
|
||||
uses io.helidon.config.spi.ConfigParser;
|
||||
uses io.helidon.config.spi.ConfigFilter;
|
||||
uses io.helidon.config.spi.ConfigSourceProvider;
|
||||
uses io.helidon.config.spi.OverrideSourceProvider;
|
||||
uses io.helidon.config.spi.RetryPolicyProvider;
|
||||
uses io.helidon.config.spi.PollingStrategyProvider;
|
||||
|
||||
uses java.nio.file.spi.FileTypeDetector;
|
||||
|
||||
provides io.helidon.config.spi.ConfigParser with io.helidon.config.internal.PropertiesConfigParser;
|
||||
provides java.nio.file.spi.FileTypeDetector with io.helidon.config.internal.ConfigFileTypeDetector;
|
||||
provides org.eclipse.microprofile.config.spi.ConfigProviderResolver with io.helidon.config.MpConfigProviderResolver;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
# 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.
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
io.helidon.microprofile.config.MpConfigProviderResolver
|
||||
io.helidon.config.MpConfigProviderResolver
|
||||
@@ -64,7 +64,8 @@ public class AbstractConfigImplTest {
|
||||
mock(ConfigNode.ObjectNode.class),
|
||||
mock(ConfigFilter.class),
|
||||
providerMock,
|
||||
key -> Collections.emptyList()),
|
||||
key -> Collections.emptyList(),
|
||||
Collections.emptyList()),
|
||||
mock(ConfigMapperManager.class));
|
||||
|
||||
// WHEN we reproduce the issue https://github.com/oracle/helidon/issues/299
|
||||
|
||||
@@ -58,7 +58,7 @@ public class BuilderImplTest {
|
||||
.build();
|
||||
|
||||
verify(spyBuilder).createProvider(notNull(), //ConfigMapperManager
|
||||
eq(ConfigSources.empty()), //ConfigSource
|
||||
eq(BuilderImpl.ConfigSourceConfiguration.empty()), //ConfigSource
|
||||
eq(OverrideSources.empty()), //OverrideSource
|
||||
eq(CollectionsHelper.listOf()), //filterProviders
|
||||
eq(true), //cachingEnabled
|
||||
@@ -83,7 +83,7 @@ public class BuilderImplTest {
|
||||
spyBuilder.build();
|
||||
|
||||
verify(spyBuilder).createProvider(notNull(), //ConfigMapperManager
|
||||
eq(ConfigSources.empty()), //ConfigSource
|
||||
eq(BuilderImpl.ConfigSourceConfiguration.empty()), //ConfigSource
|
||||
eq(OverrideSources.empty()), //OverrideSource
|
||||
eq(CollectionsHelper.listOf()), //filterProviders
|
||||
eq(true), //cachingEnabled
|
||||
@@ -110,7 +110,7 @@ public class BuilderImplTest {
|
||||
spyBuilder.build();
|
||||
|
||||
verify(spyBuilder).createProvider(notNull(), //ConfigMapperManager
|
||||
eq(ConfigSources.empty()), //ConfigSource
|
||||
eq(BuilderImpl.ConfigSourceConfiguration.empty()), //ConfigSource
|
||||
eq(OverrideSources.empty()), //OverrideSource
|
||||
eq(CollectionsHelper.listOf()), //filterProviders
|
||||
eq(true), //cachingEnabled
|
||||
@@ -123,8 +123,10 @@ public class BuilderImplTest {
|
||||
|
||||
@Test
|
||||
public void testBuildWithDefaultStrategy() {
|
||||
String expected = "This value should override the environment variable";
|
||||
System.setProperty(TEST_SYS_PROP_NAME, TEST_SYS_PROP_VALUE);
|
||||
System.setProperty(TEST_ENV_VAR_NAME, "This value is not used, but from Env Vars, see pom.xml!");
|
||||
// system properties now have priority over environment variables
|
||||
System.setProperty(TEST_ENV_VAR_NAME, expected);
|
||||
|
||||
Config config = Config.builder()
|
||||
.sources(CompositeConfigSourceTest.initBuilder().build())
|
||||
@@ -134,8 +136,9 @@ public class BuilderImplTest {
|
||||
assertThat(config.get("prop2").asString().get(), is("source-2"));
|
||||
assertThat(config.get("prop3").asString().get(), is("source-3"));
|
||||
assertThat(config.get(TEST_SYS_PROP_NAME).asString().get(), is(TEST_SYS_PROP_VALUE));
|
||||
assertThat(config.get(TEST_ENV_VAR_NAME).asString().get(), is(TEST_ENV_VAR_VALUE));
|
||||
assertThat(config.get(TEST_ENV_VAR_NAME).asString().get(), is(expected));
|
||||
|
||||
// once we do the replacement, we hit the environment variable only (as there is no replacement for other sources)
|
||||
String envVarName = TEST_ENV_VAR_NAME.toLowerCase().replace("_", ".");
|
||||
assertThat(config.get(envVarName).asString().get(), is(TEST_ENV_VAR_VALUE));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -24,11 +24,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigHelper;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.PollingStrategies;
|
||||
import io.helidon.config.internal.ClasspathConfigSource.ClasspathBuilder;
|
||||
import io.helidon.config.ClasspathConfigSource.ClasspathBuilder;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
@@ -50,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link ClasspathConfigSource}.
|
||||
* Tests {@link io.helidon.config.ClasspathConfigSource}.
|
||||
*/
|
||||
public class ClasspathConfigSourceTest {
|
||||
|
||||
@@ -141,7 +137,7 @@ public class ClasspathConfigSourceTest {
|
||||
try {
|
||||
assertThat((char) ConfigHelper.createReader(content.asReadable()).read(), is('#'));
|
||||
} catch (IOException e) {
|
||||
fail("Cannot read from source's reader");
|
||||
fail("Cannot read from source's reader", e);
|
||||
}
|
||||
return ObjectNode.empty();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -14,17 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.OverrideSources;
|
||||
import io.helidon.config.PollingStrategies;
|
||||
import io.helidon.config.internal.ClasspathOverrideSource.ClasspathBuilder;
|
||||
import io.helidon.config.ClasspathOverrideSource.ClasspathBuilder;
|
||||
import io.helidon.config.spi.OverrideSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
|
||||
@@ -39,7 +36,7 @@ import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
/**
|
||||
* Tests {@link ClasspathOverrideSource}.
|
||||
* Tests {@link io.helidon.config.ClasspathOverrideSource}.
|
||||
*/
|
||||
public class ClasspathOverrideSourceTest {
|
||||
|
||||
@@ -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.
|
||||
@@ -23,6 +23,7 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
@@ -34,15 +35,14 @@ import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.TestingParsableConfigSource;
|
||||
|
||||
import static io.helidon.config.ValueNodeMatcher.valueNode;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.helidon.config.ValueNodeMatcher.valueNode;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -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.
|
||||
@@ -160,8 +160,7 @@ public class ConfigChangesTest {
|
||||
|
||||
// change config source
|
||||
TimeUnit.MILLISECONDS.sleep(TEST_DELAY_MS); // Make sure timestamp changes.
|
||||
configSource.changeLoadedObjectNode(
|
||||
ObjectNode.builder().addValue("key-1-1.key-2-1", "NEW item 1").build());
|
||||
configSource.changeLoadedObjectNode(ObjectNode.simple("key-1-1.key-2-1", "NEW item 1"));
|
||||
|
||||
// wait for event1
|
||||
Config last1 = subscriber1.getLastOnNext(200, true);
|
||||
@@ -404,7 +403,10 @@ public class ConfigChangesTest {
|
||||
.build()).build();
|
||||
|
||||
// config
|
||||
Config config = Config.builder().sources(configSource).build();
|
||||
Config config = Config.builder(configSource)
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.build();
|
||||
|
||||
// key exists
|
||||
assertThat(config.get("key1").exists(), is(true));
|
||||
@@ -424,7 +426,7 @@ public class ConfigChangesTest {
|
||||
Config newConfig = subscriber.getLastOnNext(1000, true);
|
||||
|
||||
// new: key does not exist
|
||||
assertThat(newConfig.exists(), is(false));
|
||||
assertThat("New config should not exist", newConfig.exists(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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.
|
||||
@@ -49,12 +49,10 @@ import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
|
||||
@@ -62,6 +60,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@@ -78,17 +77,27 @@ public class ConfigMappersTest {
|
||||
*/
|
||||
@Test
|
||||
public void testAllToTypeStaticMethodsAreRegistered() {
|
||||
Set<Class<?>> methods = Arrays.asList(ConfigMappers.class.getMethods()).stream()
|
||||
|
||||
Class[] methodArray = Arrays.stream(ConfigMappers.class.getMethods())
|
||||
.filter(method -> Modifier.isPublic(method.getModifiers())) //public
|
||||
.filter(method -> Modifier.isStatic(method.getModifiers())) //static
|
||||
.filter(method -> method.getName().startsWith("to")) //to*
|
||||
.filter(method -> method.getParameterCount() == 1) //single parameter
|
||||
.filter(method -> String.class.equals(method.getParameterTypes()[0]) || //String or Config parameter
|
||||
Config.class.equals(method.getParameterTypes()[0]))
|
||||
.filter(this::expectedMapperParamType)
|
||||
.map(Method::getReturnType)
|
||||
.collect(Collectors.toSet());
|
||||
.distinct()
|
||||
.toArray(Class[]::new);
|
||||
|
||||
assertThat(ConfigMappers.BUILT_IN_MAPPERS.keySet(), is(methods));
|
||||
assertThat(ConfigMappers.BUILT_IN_MAPPERS.keySet(), hasItems(methodArray));
|
||||
}
|
||||
|
||||
private boolean expectedMapperParamType(Method method) {
|
||||
Class<?> type = method.getParameterTypes()[0];
|
||||
|
||||
//String, CharSequence or Config parameter
|
||||
return String.class.equals(type)
|
||||
|| Config.class.equals(type)
|
||||
|| CharSequence.class.equals(type);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
@@ -37,6 +38,7 @@ import static io.helidon.config.ValueNodeMatcher.valueNode;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
@@ -102,14 +104,6 @@ public class ConfigSourcesTest {
|
||||
assertThat(prefixed("security", source).description(), is("prefixed[security]:" + source.description()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapBuilderSupplierGetOnce() {
|
||||
ConfigSources.MapBuilder builder = ConfigSources.create(mapOf());
|
||||
|
||||
ConfigSource configSource = builder.get();
|
||||
assertThat(configSource, sameInstance(builder.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompositeBuilderSupplierGetOnce() {
|
||||
ConfigSources.CompositeBuilder builder = ConfigSources.create();
|
||||
@@ -120,7 +114,7 @@ public class ConfigSourcesTest {
|
||||
|
||||
@Test
|
||||
public void testLoadNoSource() {
|
||||
ConfigSource source = ConfigSources.load().build();
|
||||
ConfigSource source = ConfigSources.empty();
|
||||
source.init(mock(ConfigContext.class));
|
||||
|
||||
assertThat(source.load(), is(Optional.empty()));
|
||||
@@ -139,7 +133,10 @@ public class ConfigSourcesTest {
|
||||
.build())
|
||||
.build());
|
||||
|
||||
ConfigSource source = ConfigSources.load(meta1).build();
|
||||
List<ConfigSource> sources = MetaConfig.configSources(Config.create(meta1));
|
||||
assertThat(sources, hasSize(1));
|
||||
|
||||
ConfigSource source = sources.get(0);
|
||||
source.init(mock(ConfigContext.class));
|
||||
ObjectNode objectNode = source.load().get();
|
||||
assertThat(objectNode.get(TEST_SYS_PROP_NAME), valueNode(TEST_SYS_PROP_VALUE));
|
||||
@@ -173,7 +170,10 @@ public class ConfigSourcesTest {
|
||||
.build());
|
||||
|
||||
//meta1 has precedence over meta2
|
||||
ConfigSource source = ConfigSources.load(meta1, meta2).build();
|
||||
List<ConfigSource> sources = MetaConfig.configSources(Config.create(meta1));
|
||||
assertThat(sources, hasSize(1));
|
||||
ConfigSource source = sources.get(0);
|
||||
|
||||
ConfigContext context = mock(ConfigContext.class);
|
||||
when(context.findParser("text/x-java-properties")).thenReturn(Optional.of(ConfigParsers.properties()));
|
||||
|
||||
@@ -188,14 +188,14 @@ public class ConfigSourcesTest {
|
||||
public void testSystemPropertiesSourceType() {
|
||||
ConfigSource source = ConfigSources.systemProperties();
|
||||
assertThat(source, is(instanceOf(ConfigSources.SystemPropertiesConfigSource.class)));
|
||||
assertThat(source.description(), is("SystemPropertiesConfig"));
|
||||
assertThat(source.description(), is("SystemPropertiesConfig[]*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnvironmentVariablesSourceType() {
|
||||
ConfigSource source = ConfigSources.environmentVariables();
|
||||
assertThat(source, is(instanceOf(ConfigSources.EnvironmentVariablesConfigSource.class)));
|
||||
assertThat(source.description(), is("EnvironmentVariablesConfig"));
|
||||
assertThat(source.description(), is("EnvironmentVariablesConfig[]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -242,8 +242,8 @@ public class ConfigSourcesTest {
|
||||
// NOTE: This code should be kept in sync with MpcSourceEnvironmentVariablesTest.testPrecedence(), as we want
|
||||
// SE and MP to be as symmetrical as possible. There are two differences:
|
||||
//
|
||||
// 1. Env var and sys prop precedence is reversed in SE compared to MP (issue #507). This can be fixed
|
||||
// easily, but is a breaking change so should probably be addressed in the next major release.
|
||||
// 1. This is now resolved - SE and MP have the same behavior related
|
||||
// to System proprerties and Environment variables
|
||||
//
|
||||
// 2. An upper-to-lower case mapping is performed in SE but is not in MP (correctly, per spec). This is a
|
||||
// consequence of the static mapping (see EnvironmentVariables.expand()) required in SE to preserve
|
||||
@@ -319,7 +319,7 @@ public class ConfigSourcesTest {
|
||||
.build();
|
||||
|
||||
assertValue("app.key", "app-value", appSysAndEnv);
|
||||
assertValue("com.ACME.size", "mapped-env-value", appSysAndEnv); // DIFFERENCE 1: should be sys-prop-value
|
||||
assertValue("com.ACME.size", "sys-prop-value", appSysAndEnv);
|
||||
assertValue("server.executor-service.max-pool-size", "mapped-env-value", appSysAndEnv);
|
||||
|
||||
assertValue("com.acme.size","mapped-env-value", appAndEnv); // DIFFERENCE 2: should not exist
|
||||
|
||||
@@ -31,6 +31,7 @@ import io.helidon.config.spi.ConfigSourceTest;
|
||||
import io.helidon.config.test.infra.RestoreSystemPropertiesExt;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -115,6 +116,8 @@ public class ConfigTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
// this test is no long valid, as system properties have precedence over env vars
|
||||
public void testCreateKeyFromEnvVars() {
|
||||
System.setProperty(ConfigSourceTest.TEST_ENV_VAR_NAME, "This value is not used, but from Env Vars, see pom.xml!");
|
||||
|
||||
@@ -122,6 +125,8 @@ public class ConfigTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
// this test is no long valid, as system properties have precedence over env vars
|
||||
public void testBuilderDefaultConfigSourceKeyFromEnvVars() {
|
||||
System.setProperty(ConfigSourceTest.TEST_ENV_VAR_NAME, "This value is not used, but from Env Vars, see pom.xml!");
|
||||
|
||||
@@ -632,7 +637,7 @@ public class ConfigTest {
|
||||
assertThat(config.get(TEST_SYS_PROP_NAME).asString().get(), is(TEST_SYS_PROP_VALUE));
|
||||
assertThat(config.get(TEST_ENV_VAR_NAME).asString().get(), is(TEST_ENV_VAR_VALUE));
|
||||
|
||||
assertThat(config.get(OVERRIDE_NAME).asString().get(), is(OVERRIDE_ENV_VAR_VALUE));
|
||||
assertThat(config.get(OVERRIDE_NAME).asString().get(), is(OVERRIDE_SYS_PROP_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -22,8 +22,6 @@ import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
@@ -45,7 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link DirectoryConfigSource}.
|
||||
* Tests {@link io.helidon.config.DirectoryConfigSource}.
|
||||
*/
|
||||
public class DirectoryConfigSourceTest {
|
||||
|
||||
@@ -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,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -28,12 +28,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigHelper;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.PollingStrategies;
|
||||
import io.helidon.config.internal.FileConfigSource.FileBuilder;
|
||||
import io.helidon.config.FileConfigSource.FileBuilder;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
@@ -59,7 +54,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link FileConfigSource}.
|
||||
* Tests {@link io.helidon.config.FileConfigSource}.
|
||||
*/
|
||||
public class FileConfigSourceTest {
|
||||
|
||||
@@ -225,7 +220,7 @@ public class FileConfigSourceTest {
|
||||
public void testDataTimestamp() throws IOException {
|
||||
final String filename = "new-file";
|
||||
File file = folder.newFile(filename);
|
||||
FileConfigSource fcs = new FileConfigSource(new FileBuilder(Paths.get(filename)), file.toPath());
|
||||
FileConfigSource fcs = FileConfigSource.builder().path(file.toPath()).build();
|
||||
assertThat(fcs.dataStamp().isPresent(), is(true));
|
||||
assertThat(fcs.dataStamp().get().length, is(greaterThan(0)));
|
||||
}
|
||||
103
config/config/src/test/java/io/helidon/config/MpConfigTest.java
Normal file
103
config/config/src/test/java/io/helidon/config/MpConfigTest.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.internal.MapConfigSource;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
/**
|
||||
* Test MicroProfile config implementation.
|
||||
*/
|
||||
public class MpConfigTest {
|
||||
private static Config config;
|
||||
|
||||
@BeforeAll
|
||||
static void initClass() {
|
||||
Object helidonConfig = io.helidon.config.Config.builder()
|
||||
.addSource(ConfigSources.classpath("io/helidon/config/application.properties"))
|
||||
.addSource(ConfigSources.create(CollectionsHelper.mapOf("mp-1", "mp-value-1",
|
||||
"mp-2", "mp-value-2",
|
||||
"app.storageEnabled", "false",
|
||||
"mp-array", "a,b,c",
|
||||
"mp-list.0", "1",
|
||||
"mp-list.1", "2",
|
||||
"mp-list.2", "3")))
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.build();
|
||||
|
||||
assertThat(helidonConfig, instanceOf(Config.class));
|
||||
|
||||
config = (Config) helidonConfig;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigSources() {
|
||||
Iterable<ConfigSource> configSources = config.getConfigSources();
|
||||
List<ConfigSource> asList = new ArrayList<>();
|
||||
for (ConfigSource configSource : configSources) {
|
||||
asList.add(configSource);
|
||||
}
|
||||
|
||||
assertThat(asList, hasSize(2));
|
||||
assertThat(asList.get(0), instanceOf(ClasspathConfigSource.class));
|
||||
assertThat(asList.get(1), instanceOf(MapConfigSource.class));
|
||||
|
||||
ConfigSource classpath = asList.get(0);
|
||||
assertThat(classpath.getValue("app.storageEnabled"), is("true"));
|
||||
|
||||
ConfigSource map = asList.get(1);
|
||||
assertThat(map.getValue("mp-1"), is("mp-value-1"));
|
||||
assertThat(map.getValue("mp-2"), is("mp-value-2"));
|
||||
assertThat(map.getValue("app.storageEnabled"), is("false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOptionalValue() {
|
||||
assertThat(config.getOptionalValue("app.storageEnabled", Boolean.class), is(Optional.of(true)));
|
||||
assertThat(config.getOptionalValue("mp-1", String.class), is(Optional.of("mp-value-1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringArray() {
|
||||
String[] values = config.getValue("mp-array", String[].class);
|
||||
assertThat(values, arrayContaining("a", "b", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntArray() {
|
||||
Integer[] values = config.getValue("mp-list", Integer[].class);
|
||||
assertThat(values, arrayContaining(1, 2, 3));
|
||||
}
|
||||
|
||||
// TODO if I use app1.node1.value instead to test overriding, the override fails
|
||||
// probably relate to it being an object node in properties and value node in map?
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* 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.
|
||||
@@ -14,23 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.ConfigHelperTest;
|
||||
import io.helidon.config.ConfigParsers;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
|
||||
import com.xebialabs.restito.server.StubServer;
|
||||
import org.glassfish.grizzly.http.Method;
|
||||
|
||||
import static io.helidon.config.internal.PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;
|
||||
import static com.xebialabs.restito.builder.verify.VerifyHttp.verifyHttp;
|
||||
@@ -41,19 +39,17 @@ import static com.xebialabs.restito.semantics.Action.stringContent;
|
||||
import static com.xebialabs.restito.semantics.Condition.get;
|
||||
import static com.xebialabs.restito.semantics.Condition.method;
|
||||
import static com.xebialabs.restito.semantics.Condition.uri;
|
||||
import static io.helidon.config.internal.PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES;
|
||||
import static org.glassfish.grizzly.http.Method.HEAD;
|
||||
import static org.glassfish.grizzly.http.util.HttpStatus.OK_200;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests {@link UrlConfigSource} with running {@link StubServer Restito Server}.
|
||||
* Tests {@link io.helidon.config.UrlConfigSource} with running {@link StubServer Restito Server}.
|
||||
*/
|
||||
public class UrlConfigSourceServerMockTest {
|
||||
|
||||
@@ -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,17 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.internal;
|
||||
package io.helidon.config;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.RetryPolicies;
|
||||
import io.helidon.config.internal.UrlConfigSource.UrlBuilder;
|
||||
import io.helidon.config.UrlConfigSource.UrlBuilder;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
@@ -41,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link UrlConfigSource}.
|
||||
* Tests {@link io.helidon.config.UrlConfigSource}.
|
||||
*/
|
||||
public class UrlConfigSourceTest {
|
||||
|
||||
@@ -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.
|
||||
@@ -104,8 +104,9 @@ public class FileOverrideSourceTest {
|
||||
@Test
|
||||
public void testDataTimestamp() throws IOException {
|
||||
final String filename = "new-file";
|
||||
File file = folder.newFile(filename);
|
||||
FileOverrideSource fcs = new FileOverrideSource(new FileBuilder(Paths.get(filename)), file.toPath());
|
||||
FileOverrideSource fcs = FileOverrideSource.builder()
|
||||
.path(Paths.get(filename))
|
||||
.build();
|
||||
assertThat(fcs.dataStamp(), is(not(Instant.now())));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -17,30 +17,22 @@
|
||||
package io.helidon.config.internal;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.helidon.config.ValueNodeMatcher.valueNode;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests {@link MapConfigSource}.
|
||||
@@ -148,66 +140,6 @@ public class MapConfigSourceTest {
|
||||
is(Optional.of("app-name")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // since list and object nodes can now contain "direct" values, this no longer fails
|
||||
public void testBuilderOverlapParentFirst() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("app", "app-name");
|
||||
map.put("app.port", "8080");
|
||||
|
||||
MapConfigSource mapConfigSource = (MapConfigSource) ConfigSources.create(map).lax().build();
|
||||
mapConfigSource.init(mock(ConfigContext.class));
|
||||
ObjectNode objectNode = mapConfigSource.load().get();
|
||||
|
||||
assertThat(objectNode.entrySet(), hasSize(1));
|
||||
assertThat(objectNode.get("app"), valueNode("app-name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // since list and object nodes can now contain "direct" values, this no longer fails
|
||||
public void testBuilderOverlapParentLast() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("app.port", "8080");
|
||||
map.put("app", "app-name");
|
||||
|
||||
MapConfigSource mapConfigSource = (MapConfigSource) ConfigSources.create(map).lax().build();
|
||||
mapConfigSource.init(mock(ConfigContext.class));
|
||||
ObjectNode objectNode = mapConfigSource.load().get();
|
||||
|
||||
assertThat(objectNode.entrySet(), hasSize(1));
|
||||
assertThat(((ObjectNode) objectNode.get("app")).get("port"), valueNode("8080"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // since list and object nodes can now contain "direct" values, this no longer fails
|
||||
public void testBuilderOverlapStrictParentFirst() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("app", "app-name");
|
||||
map.put("app.port", "8080");
|
||||
|
||||
MapConfigSource mapConfigSource = (MapConfigSource) ConfigSources.create(map).build();
|
||||
|
||||
assertThrows(ConfigException.class, () -> {
|
||||
mapConfigSource.init(mock(ConfigContext.class));
|
||||
mapConfigSource.load();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // since list and object nodes can now contain "direct" values, this no longer fails
|
||||
public void testBuilderOverlapStrictParentLast() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("app.port", "8080");
|
||||
map.put("app", "app-name");
|
||||
|
||||
MapConfigSource mapConfigSource = (MapConfigSource) ConfigSources.create(map).build();
|
||||
|
||||
assertThrows(ConfigException.class, () -> {
|
||||
mapConfigSource.init(mock(ConfigContext.class));
|
||||
mapConfigSource.load();
|
||||
});
|
||||
}
|
||||
|
||||
private static class Name {
|
||||
private String name;
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -43,7 +43,6 @@ import static com.xebialabs.restito.semantics.Action.status;
|
||||
import static com.xebialabs.restito.semantics.Action.stringContent;
|
||||
import static com.xebialabs.restito.semantics.Condition.method;
|
||||
import static com.xebialabs.restito.semantics.Condition.uri;
|
||||
import static io.helidon.config.ConfigSources.create;
|
||||
import static io.helidon.config.ConfigTest.waitForAssert;
|
||||
import static io.helidon.config.OverrideSources.url;
|
||||
import static io.helidon.config.internal.PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES;
|
||||
|
||||
@@ -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.
|
||||
@@ -186,22 +186,9 @@ public class AbstractConfigSourceTest {
|
||||
assertThat(objectNode.get("key2"), valueNode("ooo=ppp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompositeBuilderSupplierGetOnce() {
|
||||
AbstractConfigSource.Builder builder = new AbstractConfigSource.Builder(Void.class) {
|
||||
@Override
|
||||
public ConfigSource build() {
|
||||
return Optional::empty;
|
||||
}
|
||||
};
|
||||
|
||||
ConfigSource configSource = builder.get();
|
||||
assertThat(configSource, sameInstance(builder.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitAll() {
|
||||
TestingConfigSource.TestingBuilder builder = TestingConfigSource.builder().init(Config.create(ConfigSources.create(
|
||||
TestingConfigSource.TestingBuilder builder = TestingConfigSource.builder().config(Config.create(ConfigSources.create(
|
||||
CollectionsHelper.mapOf("media-type-mapping.yaml", "application/x-yaml",
|
||||
"media-type-mapping.password", "application/base64"))));
|
||||
|
||||
@@ -213,7 +200,7 @@ public class AbstractConfigSourceTest {
|
||||
|
||||
@Test
|
||||
public void testInitNothing() {
|
||||
TestingConfigSource.TestingBuilder builder = TestingConfigSource.builder().init((Config.empty()));
|
||||
TestingConfigSource.TestingBuilder builder = TestingConfigSource.builder().config((Config.empty()));
|
||||
|
||||
//media-type-mapping
|
||||
assertThat(builder.mediaTypeMapping(), is(nullValue()));
|
||||
|
||||
@@ -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.
|
||||
@@ -528,7 +528,7 @@ public class AbstractParsableConfigSourceTest {
|
||||
@Test
|
||||
public void testInitAll() {
|
||||
TestingParsableConfigSource.TestingBuilder builder = TestingParsableConfigSource.builder()
|
||||
.init(Config.create(ConfigSources.create(CollectionsHelper.mapOf("media-type", "application/x-yaml"))));
|
||||
.config(Config.create(ConfigSources.create(CollectionsHelper.mapOf("media-type", "application/x-yaml"))));
|
||||
|
||||
//media-type
|
||||
assertThat(builder.mediaType(), is("application/x-yaml"));
|
||||
@@ -536,7 +536,7 @@ public class AbstractParsableConfigSourceTest {
|
||||
|
||||
@Test
|
||||
public void testInitNothing() {
|
||||
TestingParsableConfigSource.TestingBuilder builder = TestingParsableConfigSource.builder().init((Config.empty()));
|
||||
TestingParsableConfigSource.TestingBuilder builder = TestingParsableConfigSource.builder().config((Config.empty()));
|
||||
|
||||
//media-type
|
||||
assertThat(builder.mediaType(), is(nullValue()));
|
||||
|
||||
@@ -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.
|
||||
@@ -130,16 +130,14 @@ public class AbstractSourceTest {
|
||||
|
||||
@Test
|
||||
public void testInitAll() {
|
||||
TestingSource.TestingBuilder builder = TestingSource.builder().init(
|
||||
TestingSource.TestingBuilder builder = TestingSource.builder().config(
|
||||
Config.builder(ConfigSources.create(
|
||||
CollectionsHelper.mapOf("optional", "true",
|
||||
"polling-strategy.class", TestingPollingStrategy.class.getName(),
|
||||
"retry-policy.class", TestingRetryPolicy.class.getName()
|
||||
)))
|
||||
CollectionsHelper.mapOf("optional", "true")))
|
||||
.addMapper(TestingRetryPolicy.class, config -> new TestingRetryPolicy())
|
||||
.addMapper(TestingPollingStrategy.class, config -> new TestingPollingStrategy())
|
||||
.build()
|
||||
);
|
||||
).pollingStrategy(TestingPollingStrategy::new)
|
||||
.retryPolicy(new TestingRetryPolicy());
|
||||
|
||||
//optional
|
||||
assertThat(builder.isMandatory(), is(false));
|
||||
@@ -151,7 +149,7 @@ public class AbstractSourceTest {
|
||||
|
||||
@Test
|
||||
public void testInitNothing() {
|
||||
TestingSource.TestingBuilder builder = TestingSource.builder().init(Config.empty());
|
||||
TestingSource.TestingBuilder builder = TestingSource.builder().config(Config.empty());
|
||||
|
||||
//optional
|
||||
assertThat(builder.isMandatory(), is(true));
|
||||
|
||||
@@ -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.
|
||||
@@ -21,14 +21,15 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import io.helidon.config.ClasspathConfigSource;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.internal.ClasspathConfigSource;
|
||||
import io.helidon.config.internal.DirectoryConfigSource;
|
||||
import io.helidon.config.internal.FileConfigSource;
|
||||
import io.helidon.config.DirectoryConfigSource;
|
||||
import io.helidon.config.FileConfigSource;
|
||||
import io.helidon.config.MetaConfig;
|
||||
import io.helidon.config.UrlConfigSource;
|
||||
import io.helidon.config.internal.MapConfigSource;
|
||||
import io.helidon.config.internal.PrefixedConfigSource;
|
||||
import io.helidon.config.internal.UrlConfigSource;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.test.infra.RestoreSystemPropertiesExt;
|
||||
import io.helidon.config.test.infra.TemporaryFolderExt;
|
||||
@@ -48,9 +49,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
/**
|
||||
* Tests {@link ConfigSourceConfigMapper}.
|
||||
* Tests meta configuration of config sources.
|
||||
*/
|
||||
public class ConfigSourceConfigMapperTest {
|
||||
public class ConfigSourceMetaConfigTest {
|
||||
|
||||
private static final String TEST_SYS_PROP_NAME = "this_is_my_property-ConfigSourceConfigMapperTest";
|
||||
private static final String TEST_SYS_PROP_VALUE = "This Is My SYS PROPS Value.";
|
||||
@@ -71,9 +72,9 @@ public class ConfigSourceConfigMapperTest {
|
||||
.addValue("type", "system-properties")
|
||||
.build()));
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(MapConfigSource.class)));
|
||||
assertThat(source, is(instanceOf(AbstractMpSource.class)));
|
||||
|
||||
Config config = justFrom(source);
|
||||
|
||||
@@ -87,7 +88,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.addValue("type", "environment-variables")
|
||||
.build()));
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(MapConfigSource.class)));
|
||||
|
||||
@@ -107,7 +108,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(ClasspathConfigSource.class)));
|
||||
|
||||
@@ -127,7 +128,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(FileConfigSource.class)));
|
||||
|
||||
@@ -151,7 +152,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(DirectoryConfigSource.class)));
|
||||
|
||||
@@ -180,7 +181,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(UrlConfigSource.class)));
|
||||
|
||||
@@ -207,7 +208,7 @@ public class ConfigSourceConfigMapperTest {
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = metaConfig.as(MetaConfig::configSource).get();
|
||||
|
||||
assertThat(source, is(instanceOf(PrefixedConfigSource.class)));
|
||||
|
||||
@@ -113,7 +113,7 @@ public class ConfigSourceTest {
|
||||
public void testFromSystemPropertiesDescription() {
|
||||
ConfigSource configSource = ConfigSources.systemProperties();
|
||||
|
||||
assertThat(configSource.description(), is("SystemPropertiesConfig"));
|
||||
assertThat(configSource.description(), is("SystemPropertiesConfig[]*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,7 +131,7 @@ public class ConfigSourceTest {
|
||||
public void testFromEnvironmentVariablesDescription() {
|
||||
ConfigSource configSource = ConfigSources.environmentVariables();
|
||||
|
||||
assertThat(configSource.description(), is("EnvironmentVariablesConfig"));
|
||||
assertThat(configSource.description(), is("EnvironmentVariablesConfig[]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -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.
|
||||
@@ -108,7 +108,7 @@ public class TestingConfigSource extends AbstractConfigSource<Instant> {
|
||||
/**
|
||||
* Testing implementation of {@link AbstractSource.Builder}.
|
||||
*/
|
||||
public static class TestingBuilder extends Builder<TestingBuilder, Void> {
|
||||
public static class TestingBuilder extends Builder<TestingBuilder, Void, TestingConfigSource> {
|
||||
private ObjectNode objectNode;
|
||||
|
||||
private TestingBuilder() {
|
||||
@@ -118,8 +118,8 @@ public class TestingConfigSource extends AbstractConfigSource<Instant> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestingBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public TestingBuilder config(Config metaConfig) {
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
public TestingBuilder objectNode(ObjectNode objectNode) {
|
||||
|
||||
@@ -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.
|
||||
@@ -87,7 +87,7 @@ public class TestingParsableConfigSource extends AbstractParsableConfigSource<In
|
||||
/**
|
||||
* Testing implementation of {@link AbstractParsableConfigSource.Builder}.
|
||||
*/
|
||||
public static class TestingBuilder extends Builder<TestingBuilder, Void> {
|
||||
public static class TestingBuilder extends Builder<TestingBuilder, Void, TestingParsableConfigSource> {
|
||||
private Supplier<ConfigParser.Content<Instant>> contentSupplier;
|
||||
|
||||
private TestingBuilder() {
|
||||
@@ -97,8 +97,8 @@ public class TestingParsableConfigSource extends AbstractParsableConfigSource<In
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestingBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public TestingBuilder config(Config metaConfig) {
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
public TestingBuilder content(Supplier<ConfigParser.Content<Instant>> contentSupplier) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2018 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.
|
||||
#
|
||||
|
||||
testing1 = io.helidon.config.spi.ConfigSourceConfigMapperTest$MyConfigSource
|
||||
testing2 = io.helidon.config.spi.ConfigSourceConfigMapperTest$MyConfigSourceBuilder
|
||||
@@ -77,15 +77,6 @@
|
||||
<groupId>com.typesafe</groupId>
|
||||
<artifactId>config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--
|
||||
this dependency is only needed for meta configuration
|
||||
if config is not configured through meta configuration, leave it out
|
||||
-->
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-object-mapping</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-testing</artifactId>
|
||||
@@ -137,6 +128,21 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<!-- on big machines (e.g. 52 cores hyperthreaded)
|
||||
this failed with too many open files
|
||||
-->
|
||||
<name>io.netty.eventLoopThreads</name>
|
||||
<value>2</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
|
||||
@@ -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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package io.helidon.config.etcd;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
@@ -106,14 +107,39 @@ public class EtcdConfigSource extends AbstractParsableConfigSource<Long> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from configuration.
|
||||
* Create a configured instance with the provided options.
|
||||
*
|
||||
* @param config configuration to load from
|
||||
* @return configured source instance
|
||||
* @param uri Remote etcd URI
|
||||
* @param key key the configuration is associated with
|
||||
* @param api api version
|
||||
* @return a new etcd config source
|
||||
*/
|
||||
public static EtcdConfigSource create(Config config) {
|
||||
return EtcdConfigSourceBuilder
|
||||
.create(config)
|
||||
public static EtcdConfigSource create(URI uri, String key, EtcdConfigSourceBuilder.EtcdApi api) {
|
||||
return builder()
|
||||
.uri(uri)
|
||||
.key(key)
|
||||
.api(api)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from configuration.
|
||||
*
|
||||
* @param metaConfig meta configuration to load config source from
|
||||
* @return configured source instance
|
||||
*/
|
||||
public static EtcdConfigSource create(Config metaConfig) {
|
||||
return builder()
|
||||
.config(metaConfig)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new fluent API builder for etcd.
|
||||
*
|
||||
* @return a new builder
|
||||
*/
|
||||
public static EtcdConfigSourceBuilder builder() {
|
||||
return new EtcdConfigSourceBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -17,12 +17,9 @@
|
||||
package io.helidon.config.etcd;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigMappingException;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdEndpoint;
|
||||
import io.helidon.config.etcd.internal.client.EtcdClientFactory;
|
||||
import io.helidon.config.etcd.internal.client.v2.EtcdV2ClientFactory;
|
||||
@@ -52,71 +49,100 @@ import io.helidon.config.spi.PollingStrategy;
|
||||
* are set, then {@code parser} has precedence.
|
||||
*/
|
||||
public final class EtcdConfigSourceBuilder
|
||||
extends AbstractParsableConfigSource.Builder<EtcdConfigSourceBuilder, EtcdEndpoint> {
|
||||
extends AbstractParsableConfigSource.Builder<EtcdConfigSourceBuilder, EtcdEndpoint, EtcdConfigSource> {
|
||||
|
||||
/**
|
||||
* Default Etcd API version ({@link io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdApi#v2}).
|
||||
*/
|
||||
public static final EtcdApi DEFAULT_VERSION = EtcdApi.v2;
|
||||
/**
|
||||
* Default Etcd endpoint ({@code http://localhost:2379}).
|
||||
*/
|
||||
public static final URI DEFAULT_URI = URI.create("http://localhost:2379");
|
||||
|
||||
private static final String URI_KEY = "uri";
|
||||
private static final String KEY_KEY = "key";
|
||||
private static final String API_KEY = "api";
|
||||
private final EtcdEndpoint etcdEndpoint;
|
||||
|
||||
private EtcdConfigSourceBuilder(URI uri, String key, EtcdApi api) {
|
||||
private EtcdEndpoint etcdEndpoint;
|
||||
|
||||
private URI uri = DEFAULT_URI;
|
||||
private String key;
|
||||
private EtcdApi version = DEFAULT_VERSION;
|
||||
|
||||
EtcdConfigSourceBuilder() {
|
||||
super(EtcdEndpoint.class);
|
||||
|
||||
Objects.requireNonNull(uri, "uri cannot be null");
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(api, "api cannot be null");
|
||||
|
||||
this.etcdEndpoint = new EtcdEndpoint(uri, key, api);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of builder with specified mandatory Etcd endpoint remote descriptor.
|
||||
* Etcd endpoint remote URI.
|
||||
*
|
||||
* @param uri an Etcd endpoint remote URI.
|
||||
* @param key an Etcd key with which the value containing the configuration is associated.
|
||||
* @param api an Etcd API version.
|
||||
* @return new instance of builder
|
||||
* @see #create(Config)
|
||||
* @param uri endpoint URI
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public static EtcdConfigSourceBuilder create(URI uri, String key, EtcdApi api) {
|
||||
return new EtcdConfigSourceBuilder(uri, key, api);
|
||||
public EtcdConfigSourceBuilder uri(URI uri) {
|
||||
this.uri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes config source instance from meta configuration properties,
|
||||
* see {@link io.helidon.config.ConfigSources#load(Config)}.
|
||||
* <p>
|
||||
* Mandatory {@code properties}, see {@link #create(URI, String, EtcdApi)}:
|
||||
* Etcd key with which the value containing the configuration is associated.
|
||||
*
|
||||
* @param key key
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public EtcdConfigSourceBuilder key(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Etcd API version.
|
||||
*
|
||||
* @param version version, defaults to {@link EtcdApi#v3}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public EtcdConfigSourceBuilder api(EtcdApi version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <ul>
|
||||
* <li>{@code uri} - type {@link URI}</li>
|
||||
* <li>{@code key} - type {@code String}</li>
|
||||
* <li>{@code api} - type {@link EtcdApi}, e.g. {@code v3}</li>
|
||||
* <li>{@code uri} - type {@link URI} - Etcd instance remote URI</li>
|
||||
* <li>{@code key} - type {@code String} - Etcd key the configuration is associated with</li>
|
||||
* <li>{@code api} - type {@link EtcdApi} - Etcd API version such as {@code v3}</li>
|
||||
* </ul>
|
||||
* Optional {@code properties}: see {@link #init(Config)}.
|
||||
* Optional {@code properties}: see {@link #config(Config)}.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned config source builder instance from.
|
||||
* @return new instance of config source builder described by {@code metaConfig}
|
||||
* @throws MissingValueException in case the configuration tree does not contain all expected sub-nodes
|
||||
* required by the mapper implementation to provide instance of Java type.
|
||||
* @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 #create(URI, String, EtcdApi)
|
||||
* @see #init(Config)
|
||||
* @param metaConfig meta-configuration used to update the builder instance from
|
||||
* @return updated builder instance
|
||||
* @see #config(Config)
|
||||
*/
|
||||
public static EtcdConfigSourceBuilder create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return EtcdConfigSourceBuilder.create(metaConfig.get(URI_KEY).as(URI.class).get(),
|
||||
metaConfig.get(KEY_KEY).asString().get(),
|
||||
metaConfig.get(API_KEY).asString().as(EtcdApi::valueOf).get())
|
||||
.init(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EtcdConfigSourceBuilder init(Config metaConfig) {
|
||||
return super.init(metaConfig);
|
||||
public EtcdConfigSourceBuilder config(Config metaConfig) {
|
||||
metaConfig.get(URI_KEY).as(URI.class).ifPresent(this::uri);
|
||||
metaConfig.get(KEY_KEY).asString().ifPresent(this::key);
|
||||
metaConfig.get(API_KEY).asString().as(EtcdApi::valueOf).ifPresent(this::api);
|
||||
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EtcdEndpoint target() {
|
||||
if (null == etcdEndpoint) {
|
||||
if (null == uri) {
|
||||
throw new IllegalArgumentException("etcd URI must be defined");
|
||||
}
|
||||
if (null == key) {
|
||||
throw new IllegalArgumentException("etcd key must be defined");
|
||||
}
|
||||
if (null == version) {
|
||||
throw new IllegalArgumentException("etcd api (version) must be defined");
|
||||
}
|
||||
this.etcdEndpoint = new EtcdEndpoint(uri, key, version);
|
||||
}
|
||||
return etcdEndpoint;
|
||||
}
|
||||
|
||||
@@ -134,6 +160,9 @@ public final class EtcdConfigSourceBuilder
|
||||
*/
|
||||
@Override
|
||||
public EtcdConfigSource build() {
|
||||
// ensure endpoint is configured
|
||||
target();
|
||||
|
||||
return new EtcdConfigSource(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.config.etcd;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.ConfigSourceProvider;
|
||||
|
||||
/**
|
||||
* Service loader service for ETCD config source.
|
||||
*/
|
||||
public class EtcdConfigSourceProvider implements ConfigSourceProvider {
|
||||
private static final String TYPE = "etcd";
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource create(String type, Config metaConfig) {
|
||||
return EtcdConfigSource.create(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(TYPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.config.etcd;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
import io.helidon.config.spi.PollingStrategyProvider;
|
||||
|
||||
/**
|
||||
* Service loader service for ETCD config source.
|
||||
*/
|
||||
public class EtcdPollingStrategyProvider implements PollingStrategyProvider {
|
||||
static final String TYPE = "etcd";
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Object, PollingStrategy> create(String type, Config metaConfig) {
|
||||
return object -> {
|
||||
if (object instanceof EtcdConfigSourceBuilder.EtcdEndpoint) {
|
||||
return EtcdWatchPollingStrategy.create((EtcdConfigSourceBuilder.EtcdEndpoint) object);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("EtcdWatchPollingStrategy expects "
|
||||
+ EtcdConfigSourceBuilder.EtcdEndpoint.class.getName()
|
||||
+ ", but got: "
|
||||
+ (null == object ? "null" : object.getClass().getName()));
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(TYPE);
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,7 @@ module io.helidon.config.etcd {
|
||||
requires io.helidon.common;
|
||||
|
||||
exports io.helidon.config.etcd;
|
||||
|
||||
provides io.helidon.config.spi.ConfigSourceProvider with io.helidon.config.etcd.EtcdConfigSourceProvider;
|
||||
provides io.helidon.config.spi.PollingStrategyProvider with io.helidon.config.etcd.EtcdPollingStrategyProvider;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
# 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.
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
git = io.helidon.config.git.GitConfigSource
|
||||
io.helidon.config.etcd.EtcdConfigSourceProvider
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
# 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.
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
etcd = io.helidon.config.etcd.EtcdConfigSource
|
||||
io.helidon.config.etcd.EtcdPollingStrategyProvider
|
||||
@@ -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.
|
||||
@@ -17,18 +17,21 @@
|
||||
package io.helidon.config.etcd;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigParsers;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.MetaConfig;
|
||||
import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdApi;
|
||||
import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdEndpoint;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
import io.helidon.config.spi.PollingStrategyProvider;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -45,8 +48,10 @@ public class EtcdConfigSourceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testBuilderSuccessful() {
|
||||
EtcdConfigSource etcdConfigSource = EtcdConfigSourceBuilder
|
||||
.create(URI.create("http://localhost:2379"), "/registry", EtcdApi.v2)
|
||||
EtcdConfigSource etcdConfigSource = EtcdConfigSource.builder()
|
||||
.uri(URI.create("http://localhost:2379"))
|
||||
.key("/registry")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.build();
|
||||
|
||||
@@ -55,40 +60,49 @@ public class EtcdConfigSourceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testBuilderWithoutUri() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
EtcdConfigSourceBuilder
|
||||
.create(null, "/registry", EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
EtcdConfigSource.builder()
|
||||
.uri(null)
|
||||
.key("/registry")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Test public void testBuilderWithoutKey() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
EtcdConfigSourceBuilder
|
||||
.create(URI.create("http://localhost:2379"), null, EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
@Test
|
||||
public void testBuilderWithoutKey() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
EtcdConfigSource.builder()
|
||||
.uri(URI.create("http://localhost:2379"))
|
||||
.key(null)
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderWithoutVersion() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
EtcdConfigSourceBuilder
|
||||
.create(URI.create("http://localhost:2379"), "/registry", null)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
EtcdConfigSource.builder()
|
||||
.uri(URI.create("http://localhost:2379"))
|
||||
.key("/registry")
|
||||
.api(null)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEtcdConfigSourceDescription() {
|
||||
assertThat(EtcdConfigSourceBuilder
|
||||
.create(URI.create("http://localhost:2379"), "/registry", EtcdApi.v2)
|
||||
assertThat(EtcdConfigSource.builder()
|
||||
.uri(URI.create("http://localhost:2379"))
|
||||
.key("/registry")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType("my/media/type")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build().description(),
|
||||
@@ -98,8 +112,10 @@ public class EtcdConfigSourceBuilderTest {
|
||||
@Test
|
||||
public void testPollingStrategy() {
|
||||
URI uri = URI.create("http://localhost:2379");
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSourceBuilder
|
||||
.create(uri, "/registry", EtcdApi.v2)
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSource.builder()
|
||||
.uri(uri)
|
||||
.key("/registry")
|
||||
.api(EtcdApi.v2)
|
||||
.pollingStrategy(TestingEtcdEndpointPollingStrategy::new);
|
||||
|
||||
assertThat(builder.pollingStrategyInternal(), is(instanceOf(TestingEtcdEndpointPollingStrategy.class)));
|
||||
@@ -113,17 +129,18 @@ public class EtcdConfigSourceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testFromConfigNothing() {
|
||||
assertThrows(MissingValueException.class, () -> {
|
||||
EtcdConfigSourceBuilder.create(Config.empty());
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
EtcdConfigSource.create(Config.empty());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromConfigAll() {
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSourceBuilder.create(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3"))));
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSource.builder()
|
||||
.config(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3"))));
|
||||
|
||||
assertThat(builder.target().uri(), is(URI.create("http://localhost:2379")));
|
||||
assertThat(builder.target().key(), is("/registry"));
|
||||
@@ -132,11 +149,12 @@ public class EtcdConfigSourceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testFromConfigWithCustomPollingStrategy() {
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSourceBuilder.create(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3",
|
||||
"polling-strategy.class", TestingEtcdEndpointPollingStrategy.class.getName()))));
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSource.builder()
|
||||
.config(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3",
|
||||
"polling-strategy.type", TestingEtcdPollingStrategyProvider.TYPE))));
|
||||
|
||||
assertThat(builder.target().uri(), is(URI.create("http://localhost:2379")));
|
||||
assertThat(builder.target().key(), is("/registry"));
|
||||
@@ -153,11 +171,12 @@ public class EtcdConfigSourceBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testFromConfigEtcdWatchPollingStrategy() {
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSourceBuilder.create(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3",
|
||||
"polling-strategy.class", EtcdWatchPollingStrategy.class.getName()))));
|
||||
EtcdConfigSourceBuilder builder = EtcdConfigSource.builder()
|
||||
.config(Config.create(ConfigSources.create(CollectionsHelper.mapOf(
|
||||
"uri", "http://localhost:2379",
|
||||
"key", "/registry",
|
||||
"api", "v3",
|
||||
"polling-strategy.type", EtcdPollingStrategyProvider.TYPE))));
|
||||
|
||||
assertThat(builder.target().uri(), is(URI.create("http://localhost:2379")));
|
||||
assertThat(builder.target().key(), is("/registry"));
|
||||
@@ -175,15 +194,15 @@ public class EtcdConfigSourceBuilderTest {
|
||||
@Test
|
||||
public void testSourceFromConfigByClass() {
|
||||
Config metaConfig = Config.create(ConfigSources.create(ObjectNode.builder()
|
||||
.addValue("class", EtcdConfigSource.class.getName())
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("uri", "http://localhost:2379")
|
||||
.addValue("key", "/registry")
|
||||
.addValue("api", "v3")
|
||||
.build())
|
||||
.build()));
|
||||
.addValue("type", "etcd")
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("uri", "http://localhost:2379")
|
||||
.addValue("key", "/registry")
|
||||
.addValue("api", "v3")
|
||||
.build())
|
||||
.build()));
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = MetaConfig.configSource(metaConfig);
|
||||
|
||||
assertThat(source, is(instanceOf(EtcdConfigSource.class)));
|
||||
|
||||
@@ -196,15 +215,15 @@ public class EtcdConfigSourceBuilderTest {
|
||||
@Test
|
||||
public void testSourceFromConfigByType() {
|
||||
Config metaConfig = Config.create(ConfigSources.create(ObjectNode.builder()
|
||||
.addValue("type", "etcd")
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("uri", "http://localhost:2379")
|
||||
.addValue("key", "/registry")
|
||||
.addValue("api", "v3")
|
||||
.build())
|
||||
.build()));
|
||||
.addValue("type", "etcd")
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("uri", "http://localhost:2379")
|
||||
.addValue("key", "/registry")
|
||||
.addValue("api", "v3")
|
||||
.build())
|
||||
.build()));
|
||||
|
||||
ConfigSource source = metaConfig.as(ConfigSource::create).get();
|
||||
ConfigSource source = MetaConfig.configSource(metaConfig);
|
||||
|
||||
assertThat(source.get(), is(instanceOf(EtcdConfigSource.class)));
|
||||
|
||||
@@ -214,6 +233,34 @@ public class EtcdConfigSourceBuilderTest {
|
||||
assertThat(etcdSource.etcdEndpoint().api(), is(EtcdApi.v3));
|
||||
}
|
||||
|
||||
public static class TestingEtcdPollingStrategyProvider implements PollingStrategyProvider {
|
||||
private static final String TYPE = "etcd-testing";
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Object, PollingStrategy> create(String type, Config metaConfig) {
|
||||
return object -> {
|
||||
if (!(object instanceof EtcdEndpoint)) {
|
||||
throw new IllegalArgumentException("This polling strategy expects "
|
||||
+ EtcdEndpoint.class.getName()
|
||||
+ " as parameter, but got: "
|
||||
+ (null == object ? "null" : object.getClass().getName()));
|
||||
}
|
||||
|
||||
return new TestingEtcdEndpointPollingStrategy((EtcdEndpoint) object);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestingEtcdEndpointPollingStrategy implements PollingStrategy {
|
||||
private final EtcdEndpoint etcdEndpoint;
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -49,8 +49,10 @@ public class EtcdConfigSourceIT {
|
||||
public void testConfig(EtcdApi version) throws Exception {
|
||||
putConfiguration(version, "/application.conf");
|
||||
Config config = Config.builder()
|
||||
.sources(EtcdConfigSourceBuilder
|
||||
.create(DEFAULT_URI, "configuration", version)
|
||||
.sources(EtcdConfigSource.builder()
|
||||
.uri(DEFAULT_URI)
|
||||
.key("configuration")
|
||||
.api(version)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.build())
|
||||
.addParser(new HoconConfigParser())
|
||||
@@ -64,8 +66,10 @@ public class EtcdConfigSourceIT {
|
||||
public void testConfigChanges(EtcdApi version) throws Exception {
|
||||
putConfiguration(version, "/application.conf");
|
||||
Config config = Config.builder()
|
||||
.sources(EtcdConfigSourceBuilder
|
||||
.create(DEFAULT_URI, "configuration", version)
|
||||
.sources(EtcdConfigSource.builder()
|
||||
.uri(DEFAULT_URI)
|
||||
.key("configuration")
|
||||
.api(version)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.pollingStrategy(EtcdWatchPollingStrategy::create)
|
||||
.build())
|
||||
|
||||
@@ -62,8 +62,9 @@ public class EtcdConfigSourceTest {
|
||||
|
||||
@Test
|
||||
public void testConfigSourceBuilder() {
|
||||
EtcdConfigSource etcdConfigSource = (EtcdConfigSource) EtcdConfigSourceBuilder
|
||||
.create(DEFAULT_URI, "key", EtcdApi.v2)
|
||||
EtcdConfigSource etcdConfigSource = EtcdConfigSource.builder()
|
||||
.key("key")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.build();
|
||||
|
||||
@@ -73,8 +74,10 @@ public class EtcdConfigSourceTest {
|
||||
@Test
|
||||
public void testBadUri() {
|
||||
assertThrows(ConfigException.class, () -> {
|
||||
EtcdConfigSource etcdConfigSource = (EtcdConfigSource) EtcdConfigSourceBuilder
|
||||
.create(URI.create("http://localhost:1111"), "configuration", EtcdApi.v2)
|
||||
EtcdConfigSource etcdConfigSource = EtcdConfigSource.builder()
|
||||
.uri(URI.create("http://localhost:1111"))
|
||||
.key("configuration")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.build();
|
||||
|
||||
@@ -85,8 +88,10 @@ public class EtcdConfigSourceTest {
|
||||
@Test
|
||||
public void testBadKey() {
|
||||
assertThrows(ConfigException.class, () -> {
|
||||
EtcdConfigSource etcdConfigSource = (EtcdConfigSource) EtcdConfigSourceBuilder
|
||||
.create(DEFAULT_URI, "non-existing-key-23323423424234", EtcdApi.v2)
|
||||
EtcdConfigSource etcdConfigSource = EtcdConfigSource.builder()
|
||||
.uri(DEFAULT_URI)
|
||||
.key("non-existing-key-23323423424234")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.build();
|
||||
|
||||
@@ -98,8 +103,10 @@ public class EtcdConfigSourceTest {
|
||||
public void testConfig() {
|
||||
final AtomicLong revision = new AtomicLong(0);
|
||||
|
||||
EtcdConfigSource configSource = (EtcdConfigSource) EtcdConfigSourceBuilder
|
||||
.create(DEFAULT_URI, "configuration", EtcdApi.v2)
|
||||
EtcdConfigSource configSource = EtcdConfigSource.builder()
|
||||
.uri(DEFAULT_URI)
|
||||
.key("configuration")
|
||||
.api(EtcdApi.v2)
|
||||
.mediaType(MEDIA_TYPE_APPLICATION_HOCON)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
io.helidon.config.etcd.EtcdConfigSourceBuilderTest$TestingEtcdPollingStrategyProvider
|
||||
@@ -50,15 +50,6 @@
|
||||
<groupId>org.eclipse.jgit</groupId>
|
||||
<artifactId>org.eclipse.jgit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--
|
||||
This dependency is needed to load config source from
|
||||
meta configuration.
|
||||
Unless you need this feature, test scope is sufficient -->
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-object-mapping</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-testing</artifactId>
|
||||
|
||||
@@ -78,11 +78,32 @@ public class GitConfigSource extends AbstractParsableConfigSource<byte[]> {
|
||||
/**
|
||||
* Create an instance from meta configuration.
|
||||
*
|
||||
* @param config meta configuration of this source
|
||||
* @param metaConfig meta configuration of this source
|
||||
* @return config source configured from the meta configuration
|
||||
*/
|
||||
public static GitConfigSource create(Config config) {
|
||||
return GitConfigSourceBuilder.create(config).build();
|
||||
public static GitConfigSource create(Config metaConfig) {
|
||||
return builder().config(metaConfig).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fluent API builder for GIT config source.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static GitConfigSourceBuilder builder() {
|
||||
return new GitConfigSourceBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fluent API builder for GIT config source for a file.
|
||||
*
|
||||
* @param path path of the configuration file
|
||||
* @return a new builder instance
|
||||
* @deprecated use {@link #builder(String)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static GitConfigSourceBuilder builder(String path) {
|
||||
return new GitConfigSourceBuilder().path(path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,6 @@ import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
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;
|
||||
@@ -65,7 +63,8 @@ import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
|
||||
* are set, then {@code parser} has precedence.
|
||||
*/
|
||||
public final class GitConfigSourceBuilder
|
||||
extends AbstractParsableConfigSource.Builder<GitConfigSourceBuilder, GitConfigSourceBuilder.GitEndpoint> {
|
||||
extends
|
||||
AbstractParsableConfigSource.Builder<GitConfigSourceBuilder, GitConfigSourceBuilder.GitEndpoint, GitConfigSource> {
|
||||
|
||||
private static final String PATH_KEY = "path";
|
||||
private static final String URI_KEY = "uri";
|
||||
@@ -73,59 +72,33 @@ public final class GitConfigSourceBuilder
|
||||
private static final String DIRECTORY_KEY = "directory";
|
||||
private static final String USERNAME = "username";
|
||||
private static final String PASSWORD = "password";
|
||||
private final String path;
|
||||
private String path;
|
||||
private URI uri;
|
||||
private String branch = "master";
|
||||
private Path directory;
|
||||
private CredentialsProvider credentialsProvider;
|
||||
|
||||
private GitConfigSourceBuilder(String path) {
|
||||
GitConfigSourceBuilder() {
|
||||
super(GitEndpoint.class);
|
||||
|
||||
Objects.requireNonNull(path, "path cannot be null");
|
||||
|
||||
this.path = path;
|
||||
this.credentialsProvider = CredentialsProvider.getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder with mandatory path to the configuration source.
|
||||
* Configure path to use.
|
||||
*
|
||||
* @param path a path to the configuration file
|
||||
* @return a new builder
|
||||
* @see #create(Config)
|
||||
* @param path path to the configuration file
|
||||
* @return updated builder instance
|
||||
*/
|
||||
public static GitConfigSourceBuilder create(String path) {
|
||||
return new GitConfigSourceBuilder(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes config source instance from meta configuration properties,
|
||||
* see {@link io.helidon.config.ConfigSources#load(Config)}.
|
||||
* <p>
|
||||
* Mandatory {@code properties}, see {@link #create(String)}:
|
||||
* <ul>
|
||||
* <li>{@code path} - type {@code String}</li>
|
||||
* </ul>
|
||||
* Optional {@code properties}: see {@link #init(Config)}.
|
||||
*
|
||||
* @param metaConfig meta-configuration used to initialize returned config source builder instance from.
|
||||
* @return new instance of config source builder described by {@code metaConfig}
|
||||
* @throws MissingValueException in case the configuration tree does not contain all expected sub-nodes
|
||||
* required by the mapper implementation to provide instance of Java type.
|
||||
* @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 #create(String)
|
||||
* @see #init(Config)
|
||||
*/
|
||||
public static GitConfigSourceBuilder create(Config metaConfig) throws ConfigMappingException, MissingValueException {
|
||||
return GitConfigSourceBuilder.create(metaConfig.get(PATH_KEY).asString().get())
|
||||
.init(metaConfig);
|
||||
public GitConfigSourceBuilder path(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <ul>
|
||||
* <li>{@code path} - type {@code String}, see {@link #path(String)}</li>
|
||||
* <li>{@code uri} - type {@code URI}, see {@link #uri(URI)}</li>
|
||||
* <li>{@code branch} - type {@code String}, see {@link #branch(String)}</li>
|
||||
* <li>{@code directory} - type {@code Path}, see {@link #directory(Path)}</li>
|
||||
@@ -135,7 +108,8 @@ public final class GitConfigSourceBuilder
|
||||
* @return modified builder instance
|
||||
*/
|
||||
@Override
|
||||
protected GitConfigSourceBuilder init(Config metaConfig) {
|
||||
public GitConfigSourceBuilder config(Config metaConfig) {
|
||||
metaConfig.get(PATH_KEY).asString().ifPresent(this::path);
|
||||
//uri
|
||||
metaConfig.get(URI_KEY).as(URI.class)
|
||||
.ifPresent(this::uri);
|
||||
@@ -152,7 +126,7 @@ public final class GitConfigSourceBuilder
|
||||
this.credentialsProvider = new UsernamePasswordCredentialsProvider(user, password);
|
||||
});
|
||||
|
||||
return super.init(metaConfig);
|
||||
return super.config(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,6 +197,10 @@ public final class GitConfigSourceBuilder
|
||||
|
||||
@Override
|
||||
public GitConfigSource build() {
|
||||
if (null == path) {
|
||||
throw new IllegalArgumentException("git path must be defined");
|
||||
}
|
||||
|
||||
return new GitConfigSource(this, target());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.config.git;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.ConfigSourceProvider;
|
||||
|
||||
/**
|
||||
* Service loader service for meta configuration of this provider.
|
||||
*/
|
||||
public class GitConfigSourceProvider implements ConfigSourceProvider {
|
||||
static final String TYPE = "git";
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource create(String type, Config metaConfig) {
|
||||
return GitConfigSource.create(metaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(TYPE);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -24,4 +24,6 @@ module io.helidon.config.git {
|
||||
requires io.helidon.common;
|
||||
|
||||
exports io.helidon.config.git;
|
||||
|
||||
provides io.helidon.config.spi.ConfigSourceProvider with io.helidon.config.git.GitConfigSourceProvider;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
# 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.
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
meta2class = io.helidon.config.tests.module.meta2.MyConfigSource2
|
||||
io.helidon.config.git.GitConfigSourceProvider
|
||||
@@ -23,8 +23,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.helidon.common.CollectionsHelper;
|
||||
import io.helidon.common.reactive.Flow;
|
||||
@@ -32,12 +34,13 @@ import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigParsers;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.MetaConfig;
|
||||
import io.helidon.config.git.GitConfigSourceBuilder.GitEndpoint;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.PollingStrategy;
|
||||
import io.helidon.config.spi.PollingStrategyProvider;
|
||||
import io.helidon.config.test.infra.TemporaryFolderExt;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
@@ -96,8 +99,8 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
|
||||
@Test
|
||||
public void testMaster() throws Exception {
|
||||
try (ConfigSource source = GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
try (ConfigSource source = GitConfigSource
|
||||
.builder("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.parser(ConfigParsers.properties())
|
||||
.build()) {
|
||||
@@ -111,8 +114,8 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
|
||||
@Test
|
||||
public void testBranch() throws Exception {
|
||||
try (ConfigSource source = GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
try (ConfigSource source = GitConfigSource
|
||||
.builder("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.branch("test")
|
||||
.parser(ConfigParsers.properties())
|
||||
@@ -133,13 +136,13 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.setURI(fileUri())
|
||||
.setDirectory(tempDir)
|
||||
.call();
|
||||
ConfigSource source = GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
.directory(tempDir.toPath())
|
||||
.parser(ConfigParsers.properties())
|
||||
.build()) {
|
||||
ConfigSource source = GitConfigSource
|
||||
.builder("application.properties")
|
||||
.directory(tempDir.toPath())
|
||||
.parser(ConfigParsers.properties())
|
||||
.build()) {
|
||||
|
||||
assertThat(tempDir.toPath().resolve("application.properties").toFile().exists(), is(true));
|
||||
assertThat(tempDir.toPath().resolve("application.properties").toFile().exists(), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +150,8 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
public void testDirectoryEmpty() throws IOException, Exception {
|
||||
Path tempDir = folder.newFolder().toPath();
|
||||
|
||||
try (ConfigSource source = GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
try (ConfigSource source = GitConfigSource
|
||||
.builder("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.directory(tempDir)
|
||||
.parser(ConfigParsers.properties())
|
||||
@@ -163,24 +166,26 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
Path tempDir = folder.newFolder().toPath();
|
||||
final ConfigException ce = assertThrows(ConfigException.class, () -> {
|
||||
tempDir.resolve("dust").toFile().createNewFile();
|
||||
GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.directory(tempDir)
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
GitConfigSource
|
||||
.builder("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.directory(tempDir)
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
|
||||
assertThat(ce.getMessage(), startsWith(String.format("Directory '%s' is not empty and it is not a valid repository.", tempDir.toString())));
|
||||
assertThat(ce.getMessage(),
|
||||
startsWith(String.format("Directory '%s' is not empty and it is not a valid repository.",
|
||||
tempDir.toString())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirAndUriIsEmpty() throws IOException {
|
||||
final ConfigException ce = assertThrows(ConfigException.class, () -> {
|
||||
GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
GitConfigSource
|
||||
.builder("application.properties")
|
||||
.parser(ConfigParsers.properties())
|
||||
.build();
|
||||
});
|
||||
assertThat(ce.getMessage(), startsWith("Directory or Uri must be set."));
|
||||
}
|
||||
@@ -190,8 +195,8 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
|
||||
checkoutBranch("refs/heads/master");
|
||||
|
||||
try (ConfigSource source = GitConfigSourceBuilder
|
||||
.create("application.properties")
|
||||
try (ConfigSource source = GitConfigSource
|
||||
.builder("application.properties")
|
||||
.uri(URI.create(fileUri()))
|
||||
.pollingStrategy(regular(Duration.ofMillis(50)))
|
||||
.parser(ConfigParsers.properties())
|
||||
@@ -237,10 +242,10 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.setURI(fileUri())
|
||||
.setDirectory(dir.toFile())
|
||||
.call();
|
||||
ConfigSource source = GitConfigSourceBuilder.create("application.conf")
|
||||
.uri(URI.create(fileUri()))
|
||||
.directory(dir)
|
||||
.build()) {
|
||||
ConfigSource source = GitConfigSource.builder("application.conf")
|
||||
.uri(URI.create(fileUri()))
|
||||
.directory(dir)
|
||||
.build()) {
|
||||
|
||||
assertThat(source.description(), is(String.format("GitConfig[%s|%s#application.conf]", dir, fileUri())));
|
||||
}
|
||||
@@ -254,9 +259,9 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.setURI(fileUri())
|
||||
.setDirectory(dir.toFile())
|
||||
.call();
|
||||
ConfigSource source = GitConfigSourceBuilder.create("application.conf")
|
||||
.directory(dir)
|
||||
.build()) {
|
||||
ConfigSource source = GitConfigSource.builder("application.conf")
|
||||
.directory(dir)
|
||||
.build()) {
|
||||
|
||||
assertThat(source.description(), is(String.format("GitConfig[%s#application.conf]", dir)));
|
||||
}
|
||||
@@ -270,9 +275,9 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.setURI(fileUri())
|
||||
.setDirectory(dir.toFile())
|
||||
.call();
|
||||
ConfigSource source = GitConfigSourceBuilder.create("application.conf")
|
||||
.uri(URI.create(fileUri()))
|
||||
.build()) {
|
||||
ConfigSource source = GitConfigSource.builder("application.conf")
|
||||
.uri(URI.create(fileUri()))
|
||||
.build()) {
|
||||
|
||||
assertThat(source.description(), is(String.format("GitConfig[%s#application.conf]", fileUri())));
|
||||
}
|
||||
@@ -280,8 +285,8 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
|
||||
@Test
|
||||
public void testFromConfigNothing() {
|
||||
assertThrows(MissingValueException.class, () -> {
|
||||
GitConfigSourceBuilder.create(Config.empty());
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
GitConfigSource.create(Config.empty());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,7 +296,7 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.disableSystemPropertiesSource()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.build();
|
||||
GitConfigSourceBuilder builder = GitConfigSourceBuilder.create(metaConfig);
|
||||
GitConfigSourceBuilder builder = GitConfigSource.builder().config(metaConfig);
|
||||
|
||||
assertThat(builder.target().path(), is("application.properties"));
|
||||
assertThat(builder.target().uri(), is(nullValue()));
|
||||
@@ -310,7 +315,7 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
.disableSystemPropertiesSource()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.build();
|
||||
GitConfigSourceBuilder builder = GitConfigSourceBuilder.create(metaConfig);
|
||||
GitConfigSourceBuilder builder = GitConfigSource.builder().config(metaConfig);
|
||||
|
||||
assertThat(builder.target().path(), is("application.properties"));
|
||||
assertThat(builder.target().uri(), is(URI.create(fileUri())));
|
||||
@@ -327,11 +332,11 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
"uri", fileUri(),
|
||||
"branch", "test",
|
||||
"directory", directory.toString(),
|
||||
"polling-strategy.class", TestingGitEndpointPollingStrategy.class.getName())))
|
||||
"polling-strategy.type", TestingGitEndpointPollingStrategyProvider.TYPE)))
|
||||
.disableSystemPropertiesSource()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.build();
|
||||
GitConfigSourceBuilder builder = GitConfigSourceBuilder.create(metaConfig);
|
||||
GitConfigSourceBuilder builder = GitConfigSource.builder().config(metaConfig);
|
||||
|
||||
assertThat(builder.target().path(), is("application.properties"));
|
||||
assertThat(builder.target().uri(), is(URI.create(fileUri())));
|
||||
@@ -353,21 +358,20 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
Path directory = folder.newFolder().toPath();
|
||||
|
||||
Config metaConfig = Config.builder(ConfigSources.create(ObjectNode.builder()
|
||||
.addValue("class",
|
||||
GitConfigSource.class.getName())
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("path", "application.properties")
|
||||
.addValue("uri", fileUri())
|
||||
.addValue("branch", "test")
|
||||
.addValue("directory", directory.toString())
|
||||
.build())
|
||||
.build()))
|
||||
.addValue("type",
|
||||
GitConfigSourceProvider.TYPE)
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("path", "application.properties")
|
||||
.addValue("uri", fileUri())
|
||||
.addValue("branch", "test")
|
||||
.addValue("directory", directory.toString())
|
||||
.build())
|
||||
.build()))
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.build();
|
||||
|
||||
try (ConfigSource source = metaConfig.as(ConfigSource.class).get()) {
|
||||
|
||||
try (ConfigSource source = MetaConfig.configSource(metaConfig)) {
|
||||
assertThat(source, is(instanceOf(GitConfigSource.class)));
|
||||
|
||||
GitConfigSource gitSource = (GitConfigSource) source;
|
||||
@@ -383,19 +387,19 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
Path directory = folder.newFolder().toPath();
|
||||
|
||||
Config metaConfig = Config.builder(ConfigSources.create(ObjectNode.builder()
|
||||
.addValue("type", "git")
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("path", "application.properties")
|
||||
.addValue("uri", fileUri())
|
||||
.addValue("branch", "test")
|
||||
.addValue("directory", directory.toString())
|
||||
.build())
|
||||
.build()))
|
||||
.addValue("type", "git")
|
||||
.addObject("properties", ObjectNode.builder()
|
||||
.addValue("path", "application.properties")
|
||||
.addValue("uri", fileUri())
|
||||
.addValue("branch", "test")
|
||||
.addValue("directory", directory.toString())
|
||||
.build())
|
||||
.build()))
|
||||
.disableSystemPropertiesSource()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.build();
|
||||
|
||||
try (ConfigSource source = metaConfig.as(ConfigSource.class).get()) {
|
||||
try (ConfigSource source = MetaConfig.configSource(metaConfig)) {
|
||||
|
||||
assertThat(source, is(instanceOf(GitConfigSource.class)));
|
||||
|
||||
@@ -407,6 +411,30 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestingGitEndpointPollingStrategyProvider implements PollingStrategyProvider {
|
||||
private static final String TYPE = "git-test";
|
||||
|
||||
@Override
|
||||
public boolean supports(String type) {
|
||||
return TYPE.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Object, PollingStrategy> create(String type, Config metaConfig) {
|
||||
return object -> {
|
||||
if (object instanceof GitEndpoint) {
|
||||
return new TestingGitEndpointPollingStrategy((GitEndpoint) object);
|
||||
}
|
||||
throw new IllegalArgumentException("Testing polling strategy expects GitEndpoint, but got " + object);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supported() {
|
||||
return CollectionsHelper.setOf(TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestingGitEndpointPollingStrategy implements PollingStrategy {
|
||||
private final GitEndpoint gitEndpoint;
|
||||
|
||||
@@ -440,7 +468,7 @@ public class GitConfigSourceBuilderTest extends RepositoryTestCase {
|
||||
private volatile Flow.Subscription subscription = null;
|
||||
|
||||
CancelableSubscriber(CountDownLatch subscribeLatch,
|
||||
CountDownLatch changeLatch) {
|
||||
CountDownLatch changeLatch) {
|
||||
this.subscribeLatch = subscribeLatch;
|
||||
this.changeLatch = changeLatch;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
io.helidon.config.git.GitConfigSourceBuilderTest$TestingGitEndpointPollingStrategyProvider
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.helidon.config.objectmapping;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
@@ -112,11 +113,21 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider {
|
||||
|
||||
private static <T> Optional<Function<Config, T>> findStaticStringMethodMapper(Class<T> type,
|
||||
String methodName) {
|
||||
return findStaticMethod(type, methodName, String.class)
|
||||
.map(handle -> new StringMethodHandleConfigMapper<>(
|
||||
type,
|
||||
methodName + "(String) method",
|
||||
handle));
|
||||
|
||||
Optional<MethodHandle> method = findStaticMethod(type,
|
||||
methodName,
|
||||
String.class);
|
||||
|
||||
if (!method.isPresent()) {
|
||||
method = findStaticMethod(type,
|
||||
methodName,
|
||||
CharSequence.class);
|
||||
}
|
||||
|
||||
return method.map(handle -> new StringMethodHandleConfigMapper<>(
|
||||
type,
|
||||
methodName + "(String) method",
|
||||
handle));
|
||||
}
|
||||
|
||||
private static <T> Optional<Function<Config, T>> findParseCharSequenceMethodMapper(Class<T> type) {
|
||||
@@ -160,7 +171,7 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider {
|
||||
private static <T> Optional<Function<Config, T>> findGenericMapper(Class<T> type) {
|
||||
try {
|
||||
return findConstructor(type)
|
||||
.map(methodHandle -> new GenericConfigMapper<>(type, methodHandle));
|
||||
.map(methodHandle -> new GenericConfigMapper<>(type, methodHandle));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user