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

* If {@code media-type} not set it tries to guess it from resource extension. */ - public static final class ClasspathBuilder extends Builder { + public static final class ClasspathBuilder extends Builder { private String resource; /** * Initialize builder. - * - * @param resource classpath resource name */ - public ClasspathBuilder(String resource) { + private ClasspathBuilder() { super(Path.class); - - Objects.requireNonNull(resource, "resource name cannot be null"); - - this.resource = resource; } + /** + * Configure the classpath resource to load the configuration from. + * + * @param resource resource on classpath + * @return updated builder instance + */ + public ClasspathBuilder resource(String resource) { + this.resource = resource; + return this; + } + + /** + * {@inheritDoc} + *

    + *
  • {@code resource} - the classpath resource to load
  • + *
+ * @param metaConfig configuration properties used to configure a builder instance. + * @return updated builder instance + */ @Override - protected ClasspathBuilder init(Config metaConfig) { - return super.init(metaConfig); + public ClasspathBuilder config(Config metaConfig) { + metaConfig.get(RESOURCE_KEY).asString().ifPresent(this::resource); + return super.config(metaConfig); } @Override @@ -187,7 +183,10 @@ public class ClasspathConfigSource extends AbstractParsableConfigSource * @return new instance of Classpath ConfigSource. */ @Override - public ConfigSource build() { + public ClasspathConfigSource build() { + if (null == resource) { + throw new IllegalArgumentException("resource must be defined"); + } return new ClasspathConfigSource(this, resource); } diff --git a/config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java b/config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java similarity index 57% rename from config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java rename to config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java index 4bbe1ec12..4df790f70 100644 --- a/config/config/src/main/java/io/helidon/config/internal/ClasspathOverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/ClasspathOverrideSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,12 @@ * limitations under the License. */ -package io.helidon.config.internal; +package io.helidon.config; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Instant; -import java.util.Objects; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; -import io.helidon.config.ConfigException; import io.helidon.config.spi.AbstractOverrideSource; import io.helidon.config.spi.OverrideSource; import io.helidon.config.spi.PollingStrategy; @@ -38,16 +30,15 @@ import io.helidon.config.spi.PollingStrategy; * @see Builder */ public class ClasspathOverrideSource extends AbstractOverrideSource { - private static final Logger LOGGER = Logger.getLogger(ClasspathOverrideSource.class.getName()); - private final String resource; - ClasspathOverrideSource(ClasspathBuilder builder, String resource) { + ClasspathOverrideSource(ClasspathBuilder builder) { super(builder); + String builderResource = builder.resource; - this.resource = resource.startsWith("/") - ? resource.substring(1) - : resource; + this.resource = builderResource.startsWith("/") + ? builderResource.substring(1) + : builderResource; } @Override @@ -62,29 +53,31 @@ public class ClasspathOverrideSource extends AbstractOverrideSource { @Override public Data loadData() throws ConfigException { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream(resource); - if (inputStream == null) { - LOGGER.log(Level.FINE, - String.format("Error to get %s using %s CONTEXT ClassLoader.", description(), classLoader)); - throw new ConfigException(description() + " does not exist. Used ClassLoader: " + classLoader); - } - Instant resourceTimestamp = ClasspathSourceHelper.resourceTimestamp(resource); - try { - LOGGER.log(Level.FINE, - String.format("Getting content from '%s'. Last modified at %s. Used ClassLoader: %s", - ClasspathSourceHelper.resourcePath(resource), resourceTimestamp, classLoader)); - } catch (Exception ex) { - LOGGER.log(Level.FINE, "Error to get resource '" + resource + "' path. Used ClassLoader: " + classLoader, ex); - } - try { - return new Data<>( - Optional.of(OverrideData.create(new InputStreamReader(inputStream, StandardCharsets.UTF_8))), - Optional.ofNullable(resourceTimestamp) - ); - } catch (IOException e) { - throw new ConfigException("Cannot load dta from resource.", e); - } + return ClasspathSourceHelper.content(resource, + description(), + (inputStreamReader, instant) -> { + return new Data<>( + Optional.of(OverrideData.create(inputStreamReader)), + instant); + }); + } + + /** + * Create a new classpath override source from meta configuration, containing {@code resource} key and other options. + * @param config meta configuration + * @return a new classpath override source + */ + public static ClasspathOverrideSource create(Config config) { + return builder().config(config).build(); + } + + /** + * Create a new fluent API builder. + * + * @return a new builder + */ + public static ClasspathBuilder builder() { + return new ClasspathBuilder(); } /** @@ -105,19 +98,39 @@ public class ClasspathOverrideSource extends AbstractOverrideSource { /** * Initialize builder. - * - * @param resource classpath resource name */ - public ClasspathBuilder(String resource) { + private ClasspathBuilder() { super(Path.class); + } - Objects.requireNonNull(resource, "resource name cannot be null"); - + /** + * Configure the classpath resource to be used as a source. + * + * @param resource classpath resource path + * @return updated builder instance + */ + public ClasspathBuilder resource(String resource) { this.resource = resource; + return this; + } + + /** + * Update builder from meta configuration. + * + * @param metaConfig meta configuration to load this override source from + * @return updated builder instance + */ + public ClasspathBuilder config(Config metaConfig) { + metaConfig.get("resource").asString().ifPresent(this::resource); + return super.config(metaConfig); } @Override protected Path target() { + if (null == resource) { + throw new IllegalArgumentException("Resource name must be defined."); + } + try { Path resourcePath = ClasspathSourceHelper.resourcePath(resource); if (resourcePath != null) { @@ -136,8 +149,8 @@ public class ClasspathOverrideSource extends AbstractOverrideSource { * @return new instance of Classpath OverrideSource. */ @Override - public OverrideSource build() { - return new ClasspathOverrideSource(this, resource); + public ClasspathOverrideSource build() { + return new ClasspathOverrideSource(this); } PollingStrategy pollingStrategyInternal() { //just for testing purposes diff --git a/config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java b/config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java similarity index 66% rename from config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java rename to config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java index b250d86f7..a89745f1b 100644 --- a/config/config/src/main/java/io/helidon/config/internal/ClasspathSourceHelper.java +++ b/config/config/src/main/java/io/helidon/config/ClasspathSourceHelper.java @@ -14,22 +14,27 @@ * limitations under the License. */ -package io.helidon.config.internal; +package io.helidon.config; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; +import java.util.Optional; +import java.util.function.BiFunction; import java.util.logging.Level; import java.util.logging.Logger; /** * Utilities for file-related source classes. * - * @see ClasspathConfigSource + * @see io.helidon.config.ClasspathConfigSource * @see ClasspathOverrideSource */ class ClasspathSourceHelper { @@ -84,4 +89,29 @@ class ClasspathSourceHelper { } return Instant.EPOCH; } + + static T content(String resource, + String description, + BiFunction, T> processor) throws ConfigException { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + InputStream inputStream = classLoader.getResourceAsStream(resource); + + if (inputStream == null) { + LOGGER.log(Level.FINE, + String.format("Error to get %s using %s CONTEXT ClassLoader.", description, classLoader)); + throw new ConfigException(description + " does not exist. Used ClassLoader: " + classLoader); + } + Optional resourceTimestamp = Optional.ofNullable(resourceTimestamp(resource)); + try { + LOGGER.log(Level.FINE, + String.format("Getting content from '%s'. Last modified at %s. Used ClassLoader: %s", + resourcePath(resource), resourceTimestamp, classLoader)); + } catch (Exception ex) { + LOGGER.log(Level.FINE, "Error to get resource '" + resource + "' path. Used ClassLoader: " + classLoader, ex); + } + + return processor.apply(new InputStreamReader(inputStream, StandardCharsets.UTF_8), resourceTimestamp); + } + } diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java index c3748e465..df7fe4dd2 100644 --- a/config/config/src/main/java/io/helidon/config/Config.java +++ b/config/config/src/main/java/io/helidon/config/Config.java @@ -269,30 +269,6 @@ public interface Config { return BuilderImpl.EmptyConfigHolder.EMPTY; } - /** - * Create an empty configuration with mappers copied from another config. - * - * @param config config to get mappers from - * @return an empty config instance (empty Object) - */ - static Config empty(Config config) { - - return new BuilderImpl() - .sources(ConfigSources.empty()) - .overrides(OverrideSources.empty()) - .disableEnvironmentVariablesSource() - .disableSystemPropertiesSource() - .disableMapperServices() - .disableParserServices() - .disableFilterServices() - .mappersFrom(config) - .build(); - } - - // - // tree (config nodes) method - // - /** * Returns a new default {@link Config} loaded using one of the * configuration files available on the classpath and/or using the runtime @@ -353,10 +329,9 @@ public interface Config { * {@code Config} instance as desired. * * @return new instance of {@link Config} - * @see #loadSourcesFrom(Supplier[]) */ static Config create() { - return builder().build(); + return builder().metaConfig().build(); } /** @@ -375,9 +350,7 @@ public interface Config { * * @param configSources ordered list of configuration sources * @return new instance of {@link Config} - * @see #loadSourcesFrom(Supplier[]) * @see #builder(Supplier[]) - * @see #builderLoadSourcesFrom(Supplier[]) * @see Builder#sources(List) * @see Builder#disableEnvironmentVariablesSource() * @see Builder#disableSystemPropertiesSource() @@ -386,29 +359,10 @@ public interface Config { * @see ConfigSources.MergingStrategy */ @SafeVarargs - static Config create(Supplier... configSources) { + static Config create(Supplier... configSources) { return builder(configSources).build(); } - /** - * Creates a new {@link Config} loaded from the specified - * {@link ConfigSource}s representing meta-configurations. - *

- * See {@link ConfigSource#create(Config)} for more information about the - * format of meta-configuration. - * - * @param metaSources ordered list of meta sources - * @return new instance of {@link Config} - * @see #create(Supplier[]) - * @see #builder(Supplier[]) - * @see #builderLoadSourcesFrom(Supplier[]) - * @see ConfigSources#load(Supplier[]) - */ - @SafeVarargs - static Config loadSourcesFrom(Supplier... metaSources) { - return builderLoadSourcesFrom(metaSources).build(); - } - /** * Provides a {@link Builder} for creating a {@link Config} * based on the specified {@link ConfigSource} instances. @@ -427,8 +381,6 @@ public interface Config { * @return new initialized Builder instance * @see #builder() * @see #create(Supplier[]) - * @see #loadSourcesFrom(Supplier[]) - * @see #builderLoadSourcesFrom(Supplier[]) * @see Builder#sources(List) * @see Builder#disableEnvironmentVariablesSource() * @see Builder#disableSystemPropertiesSource() @@ -437,32 +389,10 @@ public interface Config { * @see ConfigSources.MergingStrategy */ @SafeVarargs - static Builder builder(Supplier... configSources) { + static Builder builder(Supplier... configSources) { return builder().sources(CollectionsHelper.listOf(configSources)); } - /** - * Provides a {@link Builder} for creating a {@link Config} based on the - * specified {@link ConfigSource}s representing meta-configurations. - *

- * Each meta-configuration source should set the {@code sources} property to - * be an array of config sources. See {@link ConfigSource#create(Config)} for - * more information about the format of meta-configuration. - * - * @param metaSources ordered list of meta sources - * @return new initialized Builder instance - * @see #builder() - * @see #builder(Supplier[]) - * @see ConfigSources#load(Supplier[]) - * @see #loadSourcesFrom(Supplier[]) - */ - @SafeVarargs - static Builder builderLoadSourcesFrom(Supplier... metaSources) { - return builder(ConfigSources.load(metaSources)) - .disableSystemPropertiesSource() - .disableEnvironmentVariablesSource(); - } - /** * Provides a {@link Builder} for creating a {@link Config} instance. * @@ -1281,6 +1211,13 @@ public interface Config { * @see ConfigFilter */ interface Builder { + /** + * Disable loading of config sources from Java service loader. + * This disables loading of MicroProfile Config sources. + * + * @return updated builder instance + */ + Builder disableSourceServices(); /** * Sets ordered list of {@link ConfigSource} instance to be used as single source of configuration @@ -1317,7 +1254,19 @@ public interface Config { * @see ConfigSources.CompositeBuilder * @see ConfigSources.MergingStrategy */ - Builder sources(List> configSources); + Builder sources(List> configSources); + + /** + * Add a config source to the list of sources. + * + * @param source to add + * @return updated builder instance + */ + Builder addSource(ConfigSource source); + + default Builder addSource(Supplier source) { + return addSource(source.get()); + } /** * Sets a {@link ConfigSource} instance to be used as a source of configuration to be wrapped into {@link Config} API. @@ -1338,7 +1287,7 @@ public interface Config { * @see #disableEnvironmentVariablesSource() * @see #disableSystemPropertiesSource() */ - default Builder sources(Supplier configSource) { + default Builder sources(Supplier configSource) { sources(CollectionsHelper.listOf(configSource)); return this; } @@ -1364,8 +1313,8 @@ public interface Config { * @see #disableEnvironmentVariablesSource() * @see #disableSystemPropertiesSource() */ - default Builder sources(Supplier configSource, - Supplier configSource2) { + default Builder sources(Supplier configSource, + Supplier configSource2) { sources(CollectionsHelper.listOf(configSource, configSource2)); return this; } @@ -1392,9 +1341,9 @@ public interface Config { * @see #disableEnvironmentVariablesSource() * @see #disableSystemPropertiesSource() */ - default Builder sources(Supplier configSource, - Supplier configSource2, - Supplier configSource3) { + default Builder sources(Supplier configSource, + Supplier configSource2, + Supplier configSource3) { sources(CollectionsHelper.listOf(configSource, configSource2, configSource3)); return this; } @@ -1697,12 +1646,143 @@ public interface Config { Config build(); /** - * Add mappers from another config instance. - * This may be useful if we need the same conversion behavior. + * Check if meta configuration is present and if so, update this builder using + * the meta configuration. * - * @param config config to extract mappers from * @return updated builder instance + * @see #config(Config) */ - Builder mappersFrom(Config config); + default Builder metaConfig() { + MetaConfig.metaConfig() + .ifPresent(this::config); + + return this; + } + + /** + * Configure this config builder from meta configuration. + *

+ * The following configuration options are supported in a meta configuration file: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Meta configuration
keydefault valuedescriptionreference
caching.enabled{@code true}Enable or disable caching of results of filters.{@link #disableCaching()}
key-resolving.enabled{@code true}Enable or disable resolving of placeholders in keys.{@link #disableKeyResolving()}
value-resolving.enabled{@code true}Enable or disable resolving of placeholders in values.{@link #disableValueResolving()}
parsers.enabled{@code true}Enable or disable parser services.{@link #disableParserServices()}
mappers.enabled{@code true}Enable or disable mapper services.{@link #disableMapperServices()}
override-sourcenoneConfigure an override source. Same as config source configuration (see below){@link #overrides(java.util.function.Supplier)}
sourcesDefault config sources are prefixed {@code application}, and suffix is the first available of + * {@code yaml, conf, json, properties}Configure config sources to be used by the application. This node contains the array of objects defining + * config sources{@link #addSource(io.helidon.config.spi.ConfigSource)}
+ * + * Config source configuration options: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Config source
keydefault valuedescriptionreference
type Type of a config source - a string supported by a provider.{@link io.helidon.config.spi.ConfigSourceProvider#create(String, Config)}
properties Configuration options to configure the config source (meta configuration of a source){@link io.helidon.config.spi.ConfigSourceProvider#create(String, Config)}, + * {@link MetaConfig#configSource(Config)}
properties.optionalfalseMost config sources can be configured to be optional{@link io.helidon.config.spi.AbstractSource.Builder#optional(boolean)}
properties.polling-strategy Some config sources can have a polling strategy defined{@link io.helidon.config.spi.AbstractSource.Builder#pollingStrategy(java.util.function.Function)}, + * {@link MetaConfig#pollingStrategy(Config)}
properties.retry-policy Some config sources can have a retry policy defined{@link io.helidon.config.spi.AbstractSource.Builder#retryPolicy(io.helidon.config.spi.RetryPolicy)}, + * {@link MetaConfig#retryPolicy(Config)}
+ * + * Full meta configuration example: + *

+         * 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"
+         * 
+ */ + Builder config(Config metaConfig); } } diff --git a/config/config/src/main/java/io/helidon/config/ConfigFactory.java b/config/config/src/main/java/io/helidon/config/ConfigFactory.java index 7c71220ee..53b8ca181 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigFactory.java +++ b/config/config/src/main/java/io/helidon/config/ConfigFactory.java @@ -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> aliasGenerator; - private final ConcurrentMap> configCache; + private final ConcurrentMap> configCache; private final Flow.Publisher changesPublisher; private final Instant timestamp; + private final List configSources; + private final List 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> aliasGenerator) { + Function> aliasGenerator, + List 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 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 reference = configCache.compute(prefixedKey, (k, v) -> { + Reference 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 configSources() { + return configSources; + } + + List 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> 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>() { + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Optional item) { + synchronized (lock) { + currentValues.set(loadMap(item)); + } + } + + @Override + public void onError(Throwable throwable) { + } + + @Override + public void onComplete() { + } + }); + } + + @Override + public Map 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 loadMap(Optional item) { + if (item.isPresent()) { + ConfigNode.ObjectNode node = item.get(); + Map values = new TreeMap<>(); + processNode(values, "", node); + return values; + } else { + return CollectionsHelper.mapOf(); + } + } + + private static void processNode(Map 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 values, String keyPrefix, ConfigNode.ListNode node) { + List directValue = new LinkedList<>(); + Map 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. diff --git a/config/config/src/main/java/io/helidon/config/ConfigMapperManager.java b/config/config/src/main/java/io/helidon/config/ConfigMapperManager.java index 8be26d445..72aea60c6 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigMapperManager.java +++ b/config/config/src/main/java/io/helidon/config/ConfigMapperManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 map(Config config, Class type) throws MissingValueException, ConfigMappingException { + if (type.isArray()) { + return mapArray(config, type); + } return map(config, GenericType.create(supportedType(type))); } + @SuppressWarnings("unchecked") + private T mapArray(Config config, Class 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 map(Config config, GenericType 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 converter = provider.genericTypeMappers().get(genericType); - - if (null != converter) { - return Optional.of(converter); - } - - // second try to get it from generic type method - Optional> 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 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 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, Optional>> { + private final ConfigMapperProvider provider; + + private ProviderWrapper(ConfigMapperProvider wrapped) { + this.provider = wrapped; + } + + @Override + public Optional> apply(GenericType genericType) { + // first try to get it from generic type mappers map + BiFunction converter = provider.genericTypeMappers().get(genericType); + + if (null != converter) { + return Optional.of(converter); + } + + // second try to get it from generic type method + Optional> 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 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(); + } + } } diff --git a/config/config/src/main/java/io/helidon/config/ConfigMappers.java b/config/config/src/main/java/io/helidon/config/ConfigMappers.java index e7a3d44cf..f3ae7ddff 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigMappers.java +++ b/config/config/src/main/java/io/helidon/config/ConfigMappers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; + } } /** diff --git a/config/config/src/main/java/io/helidon/config/ConfigSources.java b/config/config/src/main/java/io/helidon/config/ConfigSources.java index a98ac6fbd..567128e80 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigSources.java +++ b/config/config/src/main/java/io/helidon/config/ConfigSources.java @@ -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 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 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 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 - , 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 classpathAll(String resource) { + + List result = new LinkedList<>(); + try { + Enumeration 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 - , 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 - , 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 - , 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... configSources) { + public static CompositeBuilder create(Supplier... 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> configSources) { + public static CompositeBuilder create(List> 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. - *

- * 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. - *

- * 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... 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. - *

- * 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. - *

- * 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> 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 map; private boolean strict; private String mapSourceName; - private volatile ConfigSource configSource; private MapBuilder(final Map 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> configSources) { + private CompositeBuilder(List> 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 initConfigSources(List> sourceSuppliers) { + private static List initConfigSources(List> sourceSuppliers) { List configSources = new LinkedList<>(); - for (Supplier configSupplier : sourceSuppliers) { + for (Supplier 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 source) { + public CompositeBuilder add(Supplier 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 { /** * 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 loadData() throws ConfigException { + return new Data<>(Optional.of(ConfigUtils + .mapToObjectNode(ConfigUtils.propertiesToMap(System.getProperties()), false)), + Optional.of(Instant.now())); + } + + @Override + protected Optional dataStamp() { + // each polling event will trigger a load and comparison of config tree + return Optional.of(Instant.now()); + } + + @Override + public Set getPropertyNames() { + return System.getProperties().stringPropertyNames(); + } + + @Override + public void init(ConfigContext context) { + } + + @Override + public Map getProperties() { + Map result = new HashMap<>(); + + System.getProperties().stringPropertyNames() + .forEach(it -> { + result.put(it, System.getProperty(it)); + }); + + return result; + } + + private static final class Builder extends AbstractSource.Builder { + private Builder() { + super(Instant.class); + } + + @Override + public SystemPropertiesConfigSource build() { + return new SystemPropertiesConfigSource(); + } } } } diff --git a/config/config/src/main/java/io/helidon/config/ConfigValues.java b/config/config/src/main/java/io/helidon/config/ConfigValues.java index d0b208219..5ad95a466 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigValues.java +++ b/config/config/src/main/java/io/helidon/config/ConfigValues.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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(); } } diff --git a/config/config/src/main/java/io/helidon/config/internal/DirectoryConfigSource.java b/config/config/src/main/java/io/helidon/config/DirectoryConfigSource.java similarity index 76% rename from config/config/src/main/java/io/helidon/config/internal/DirectoryConfigSource.java rename to config/config/src/main/java/io/helidon/config/DirectoryConfigSource.java index 51a62b78c..7723dcc23 100644 --- a/config/config/src/main/java/io/helidon/config/internal/DirectoryConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/DirectoryConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,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 { *

  • {@code path} - type {@link Path}
  • * * 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 { * @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 { * 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 { + public static final class DirectoryBuilder extends Builder { 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} + *
      + *
    • {@code path} - directory path
    • + *
    + * @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 { * * @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); } } diff --git a/config/config/src/main/java/io/helidon/config/internal/FileConfigSource.java b/config/config/src/main/java/io/helidon/config/FileConfigSource.java similarity index 74% rename from config/config/src/main/java/io/helidon/config/internal/FileConfigSource.java rename to config/config/src/main/java/io/helidon/config/FileConfigSource.java index 35c406f62..3bba76642 100644 --- a/config/config/src/main/java/io/helidon/config/internal/FileConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/FileConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,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 { 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 { *
      *
    • {@code path} - type {@link Path}
    • *
    - * 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 { * @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 { *

    * If {@code media-type} not set it tries to guess it from file extension. */ - public static final class FileBuilder extends Builder { + public static final class FileBuilder extends Builder { 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} + *

      + *
    • {@code path} - path to the file containing the configuration
    • + *
    + * + * @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 { * * @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 diff --git a/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java b/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java index 170eadb71..94bd8518f 100644 --- a/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java @@ -56,7 +56,8 @@ class InMemoryConfigSource extends AbstractParsableConfigSource { return new Builder(); } - static final class Builder extends AbstractParsableConfigSource.Builder { + static final class Builder + extends AbstractParsableConfigSource.Builder { private String uri; private ConfigParser.Content content; diff --git a/config/config/src/main/java/io/helidon/config/MetaConfig.java b/config/config/src/main/java/io/helidon/config/MetaConfig.java new file mode 100644 index 000000000..4a7e3347e --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/MetaConfig.java @@ -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 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 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 configSources(Config metaConfig) { + List 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 configSources(Function supportedMediaType) { + Optional metaConfigOpt = metaConfig(); + + return metaConfigOpt + .map(MetaConfig::configSources) + .orElseGet(() -> MetaConfigFinder.findConfigSource(supportedMediaType) + .map(CollectionsHelper::listOf) + .orElseGet(CollectionsHelper::listOf)); + + } + + private static Function supportedMediaTypes() { + Set 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(); + } +} diff --git a/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java b/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java new file mode 100644 index 000000000..1bfb4f9fd --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java @@ -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 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 findMetaConfig(Function supportedMediaType) { + return findMetaConfigSource(supportedMediaType) + .map(source -> Config.builder(source).build()); + } + + static Optional findConfigSource(Function supportedMediaType) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return findSource(supportedMediaType, cl, CONFIG_PREFIX); + } + + private static Optional findMetaConfigSource(Function supportedMediaType) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Optional 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 findSource(Function supportedMediaType, + ClassLoader cl, + String configPrefix) { + Optional source; + + List 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 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 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(); + } +} diff --git a/config/config/src/main/java/io/helidon/config/MetaProviders.java b/config/config/src/main/java/io/helidon/config/MetaProviders.java new file mode 100644 index 000000000..347ef4081 --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/MetaProviders.java @@ -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 CONFIG_SOURCE_PROVIDERS; + private static final List RETRY_POLICY_PROVIDERS; + private static final List POLLING_STRATEGY_PROVIDERS; + private static final List OVERRIDE_SOURCE_PROVIDERS; + + private static final Set SUPPORTED_CONFIG_SOURCES = new HashSet<>(); + private static final Set SUPPORTED_RETRY_POLICIES = new HashSet<>(); + private static final Set SUPPORTED_POLLING_STRATEGIES = new HashSet<>(); + private static final Set 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 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>> 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 create(String type, Config metaConfig) { + return BUILT_IN.get(type).apply(metaConfig); + } + + @Override + public Set 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 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> 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 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> 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 supported() { + return BUILT_INS.keySet(); + } + } +} diff --git a/config/config/src/main/java/io/helidon/config/MpConfigBuilder.java b/config/config/src/main/java/io/helidon/config/MpConfigBuilder.java new file mode 100644 index 000000000..2eead3167 --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/MpConfigBuilder.java @@ -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 ConfigBuilder withConverter(Class aClass, int ordinal, Converter 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(); + } +} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigProviderResolver.java b/config/config/src/main/java/io/helidon/config/MpConfigProviderResolver.java similarity index 97% rename from microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigProviderResolver.java rename to config/config/src/main/java/io/helidon/config/MpConfigProviderResolver.java index ff7451308..d6c241800 100644 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigProviderResolver.java +++ b/config/config/src/main/java/io/helidon/config/MpConfigProviderResolver.java @@ -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; diff --git a/config/config/src/main/java/io/helidon/config/OverrideSources.java b/config/config/src/main/java/io/helidon/config/OverrideSources.java index 582b03312..e1eeff246 100644 --- a/config/config/src/main/java/io/helidon/config/OverrideSources.java +++ b/config/config/src/main/java/io/helidon/config/OverrideSources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 , 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 , 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 , URL> url(URL url) { - return new UrlOverrideSource.UrlBuilder(url); + return UrlOverrideSource.builder().url(url); } /** diff --git a/config/config/src/main/java/io/helidon/config/ProviderImpl.java b/config/config/src/main/java/io/helidon/config/ProviderImpl.java index eac1c98a0..6a7a29863 100644 --- a/config/config/src/main/java/io/helidon/config/ProviderImpl.java +++ b/config/config/src/main/java/io/helidon/config/ProviderImpl.java @@ -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> 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> 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 rootNode) { + private synchronized AbstractConfigImpl build(Optional 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, 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; diff --git a/config/config/src/main/java/io/helidon/config/internal/UrlConfigSource.java b/config/config/src/main/java/io/helidon/config/UrlConfigSource.java similarity index 87% rename from config/config/src/main/java/io/helidon/config/internal/UrlConfigSource.java rename to config/config/src/main/java/io/helidon/config/UrlConfigSource.java index 4814ed8dd..0a6e1ca07 100644 --- a/config/config/src/main/java/io/helidon/config/internal/UrlConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/UrlConfigSource.java @@ -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 { *
      *
    • {@code url} - type {@link URL}
    • *
    - * 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 { * @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 { * 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 { + public static final class UrlBuilder extends Builder { 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} + *
      + *
    • {@code url} - URL of the configuration source
    • + *
    + * @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 { * * @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); } diff --git a/config/config/src/main/java/io/helidon/config/internal/ConfigUtils.java b/config/config/src/main/java/io/helidon/config/internal/ConfigUtils.java index f7b889c89..7a8fa24d7 100644 --- a/config/config/src/main/java/io/helidon/config/internal/ConfigUtils.java +++ b/config/config/src/main/java/io/helidon/config/internal/ConfigUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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) diff --git a/config/config/src/main/java/io/helidon/config/internal/FileOverrideSource.java b/config/config/src/main/java/io/helidon/config/internal/FileOverrideSource.java index f5c43a98c..78477d04b 100644 --- a/config/config/src/main/java/io/helidon/config/internal/FileOverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/internal/FileOverrideSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { 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 { Optional 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 { /** * 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 { * * @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 diff --git a/config/config/src/main/java/io/helidon/config/internal/FileSourceHelper.java b/config/config/src/main/java/io/helidon/config/internal/FileSourceHelper.java index 8f86ba8d6..04b83c835 100644 --- a/config/config/src/main/java/io/helidon/config/internal/FileSourceHelper.java +++ b/config/config/src/main/java/io/helidon/config/internal/FileSourceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { diff --git a/config/config/src/main/java/io/helidon/config/internal/MapConfigSource.java b/config/config/src/main/java/io/helidon/config/internal/MapConfigSource.java index 84370f502..84e57e58e 100644 --- a/config/config/src/main/java/io/helidon/config/internal/MapConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/internal/MapConfigSource.java @@ -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 { private final Map 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 map, boolean strict, String mapSourceName) { + protected MapConfigSource(Map 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 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 map, boolean strict, String mapSourceName) { + return new MapConfigSource(map, strict, mapSourceName); } @Override - public Optional load() { - return Optional.of(ConfigUtils.mapToObjectNode(map, strict)); + protected String uid() { + return mapSourceName.isEmpty() ? "" : mapSourceName; + } + + @Override + protected Optional dataStamp() { + return Optional.of(Instant.EPOCH); + } + + @Override + protected Data 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 { + private Builder() { + super(String.class); + } + + @Override + public MapConfigSource build() { + return new MapConfigSource(this); + } } } diff --git a/config/config/src/main/java/io/helidon/config/internal/PrefixedConfigSource.java b/config/config/src/main/java/io/helidon/config/internal/PrefixedConfigSource.java index 99f3eeaa1..1a36ff581 100644 --- a/config/config/src/main/java/io/helidon/config/internal/PrefixedConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/internal/PrefixedConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 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(); } diff --git a/config/config/src/main/java/io/helidon/config/internal/UrlOverrideSource.java b/config/config/src/main/java/io/helidon/config/internal/UrlOverrideSource.java index 51bcb5600..d1389bcdb 100644 --- a/config/config/src/main/java/io/helidon/config/internal/UrlOverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/internal/UrlOverrideSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { 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 { /** * 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 { * * @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 diff --git a/config/config/src/main/java/io/helidon/config/spi/AbstractConfigSource.java b/config/config/src/main/java/io/helidon/config/spi/AbstractConfigSource.java index ba2d3dff3..b9e98fe89 100644 --- a/config/config/src/main/java/io/helidon/config/spi/AbstractConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/AbstractConfigSource.java @@ -42,7 +42,7 @@ import io.helidon.config.spi.ConfigParser.Content; * @param a type of data stamp * @see Builder */ -public abstract class AbstractConfigSource extends AbstractSource implements ConfigSource { +public abstract class AbstractConfigSource extends AbstractMpSource implements ConfigSource { private final Function mediaTypeMapping; private final Function parserMapping; @@ -54,7 +54,7 @@ public abstract class AbstractConfigSource extends AbstractSource builder) { + protected AbstractConfigSource(Builder builder) { super(builder); mediaTypeMapping = builder.mediaTypeMapping(); @@ -80,7 +80,12 @@ public abstract class AbstractConfigSource extends AbstractSource(Optional.of(processObject(data.stamp(), ConfigKeyImpl.of(), data.data().get())), data.stamp()); + Data result = new Data<>(Optional.of(processObject(data.stamp(), ConfigKeyImpl.of(), data.data().get())), + data.stamp()); + + super.processLoadedData(result); + + return result; } private ConfigNode processNode(Optional datastamp, ConfigKeyImpl key, ConfigNode node) { @@ -151,15 +156,16 @@ public abstract class AbstractConfigSource extends AbstractSource type of Builder implementation * @param type of key source attributes (target) used to construct polling strategy from + * @param Type of the source to be built */ - public abstract static class Builder, T> - extends AbstractSource.Builder - implements io.helidon.common.Builder { + public abstract static class Builder, T, S extends AbstractMpSource> + extends AbstractSource.Builder + implements io.helidon.common.Builder { private static final String MEDIA_TYPE_MAPPING_KEY = "media-type-mapping"; private Function mediaTypeMapping; private Function parserMapping; - private volatile ConfigSource configSource; + private volatile S configSource; /** * Initialize builder. @@ -174,7 +180,7 @@ public abstract class AbstractConfigSource extends AbstractSource extends AbstractSource{@code media-type-mapping} - type {@code Map} - key to media type, see {@link #mediaTypeMapping(Function)} * * - * @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 mediaTypeMapping) { diff --git a/config/config/src/main/java/io/helidon/config/spi/AbstractMpSource.java b/config/config/src/main/java/io/helidon/config/spi/AbstractMpSource.java new file mode 100644 index 000000000..e933acbe0 --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/spi/AbstractMpSource.java @@ -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 Type of the stamp of this config source + */ +public abstract class AbstractMpSource extends AbstractSource implements ConfigSource, io.helidon.config.spi.ConfigSource { + private final AtomicReference> currentValues = new AtomicReference<>(); + + /** + * Initializes config source from builder. + * + * @param builder builder to be initialized from + */ + protected AbstractMpSource(Builder builder) { + super(builder); + } + + @Override + protected Data processLoadedData(Data data) { + currentValues.set(loadMap(data.data())); + return super.processLoadedData(data); + } + + @Override + public void init(ConfigContext context) { + this.changes().subscribe(new Flow.Subscriber>() { + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Optional item) { + currentValues.set(loadMap(item)); + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onComplete() { + + } + }); + } + + @Override + public Map 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 loadMap(Optional item) { + if (item.isPresent()) { + ConfigNode.ObjectNode node = item.get(); + Map values = new TreeMap<>(); + processNode(values, "", node); + return values; + } else { + return CollectionsHelper.mapOf(); + } + } + + private static void processNode(Map 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 values, String keyPrefix, ConfigNode.ListNode node) { + List directValue = new LinkedList<>(); + Map 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; + } +} diff --git a/config/config/src/main/java/io/helidon/config/spi/AbstractOverrideSource.java b/config/config/src/main/java/io/helidon/config/spi/AbstractOverrideSource.java index 1b82209a6..ef67f2b52 100644 --- a/config/config/src/main/java/io/helidon/config/spi/AbstractOverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/AbstractOverrideSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public abstract class AbstractOverrideSource extends AbstractSource builder) { super(builder); } diff --git a/config/config/src/main/java/io/helidon/config/spi/AbstractParsableConfigSource.java b/config/config/src/main/java/io/helidon/config/spi/AbstractParsableConfigSource.java index 7f23bde0d..8e543fdce 100644 --- a/config/config/src/main/java/io/helidon/config/spi/AbstractParsableConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/AbstractParsableConfigSource.java @@ -127,8 +127,10 @@ public abstract class AbstractParsableConfigSource extends AbstractConfigSour * * @param type of Builder implementation * @param type of key source attributes (target) used to construct polling strategy from + * @param type of the config source to be built */ - public abstract static class Builder, T> extends AbstractConfigSource.Builder { + public abstract static class Builder, T, S extends AbstractMpSource> + extends AbstractConfigSource.Builder { private static final String MEDIA_TYPE_KEY = "media-type"; private String mediaType; private ConfigParser parser; @@ -148,16 +150,17 @@ public abstract class AbstractParsableConfigSource extends AbstractConfigSour *
  • {@code media-type} - type {@code String}, see {@link #mediaType(String)}
  • * * - * @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); } /** diff --git a/config/config/src/main/java/io/helidon/config/spi/AbstractSource.java b/config/config/src/main/java/io/helidon/config/spi/AbstractSource.java index 9d8814bf4..96590b2b4 100644 --- a/config/config/src/main/java/io/helidon/config/spi/AbstractSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/AbstractSource.java @@ -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 implements Source { * polling strategy from * @param type of source that should be built */ - public abstract static class Builder, T, S> { + public abstract static class Builder, T, S extends Source> + implements io.helidon.common.Builder { + /** * Default executor where the changes threads are run. */ @@ -358,31 +361,36 @@ public abstract class AbstractSource implements Source { } /** - * 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). *

    * Supported configuration {@code properties}: *

      - *
    • {@code optional} - type {@code boolean}, see {@link #optional()}
    • - *
    • {@code polling-strategy} - see {@link PollingStrategy} for details about configuration format, - * see {@link #pollingStrategy(Supplier)} or {@link #pollingStrategy(Function)}
    • + *
    • {@code optional} - type {@code boolean}, see {@link #optional()}
    • + *
    • {@code polling-strategy} - see {@link PollingStrategy} for details about configuration format, + * see {@link #pollingStrategy(Supplier)} or {@link #pollingStrategy(Function)}
    • + *
    • {@code retry-policy} - see {@link io.helidon.config.spi.RetryPolicy} for details about + * configuration format
    • *
    * - * @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 implements Source { return null; } + /** + * Type of target used by this builder. + * + * @return target type, used by {@link #pollingStrategy(java.util.function.Function)} + */ + public Class 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 implements Source { 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 implements Source { return thisBuilder; } + /** + * Set a {@link RetryPolicy} that will be responsible for invocation of {@link AbstractSource#load()}. + *

    + * The default reply policy is {@link RetryPolicies#justCall()}. + *

    + * 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}. * diff --git a/config/config/src/main/java/io/helidon/config/spi/Changeable.java b/config/config/src/main/java/io/helidon/config/spi/Changeable.java index a683fd719..1faf9fbcd 100644 --- a/config/config/src/main/java/io/helidon/config/spi/Changeable.java +++ b/config/config/src/main/java/io/helidon/config/spi/Changeable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 a type of source */ public interface Changeable { //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. diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigNode.java b/config/config/src/main/java/io/helidon/config/spi/ConfigNode.java index 246ce35ec..87e668856 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigNode.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { 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}. * diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java b/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java index eb1d37416..4f319c310 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigSource.java @@ -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, Supplier { - - /** - * Initializes a {@link ConfigSource} from meta-configuration. - *

    - * Meta-config can contain the following top level properties to define one - * or more {@code ConfigSource}s: - *

      - *
    • {@code type} - specifies the built-in configuration type - * (environment-variables, system-properties, directory, file, url, - * prefixed, classpath) or the custom - * config types of the {@code ConfigSource} being defined. - *
    • - *
    • {@code class} - fully-qualified class name of one of: - *
        - *
      • a {@link ConfigSource} implementation,
      • - *
      • a {@link Config.Builder} implementation with a {@code build()} method - * that returns a {@code ConfigSource} instance.
      • - *
      - * See the section below on custom source - * types. - *
    - * 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. - *

    - * As the config system loads configuration it uses mappers to convert the - * raw data into Java types. See {@link ConfigMapperProvider} for - * details about mapping. - *

    - * 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). - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    Predefined Configuration Source Types
    Source TypeFurther InformationMandatory Properties
    {@code system-properties}{@link ConfigSources#systemProperties()}none
    {@code environment-variables}{@link ConfigSources#environmentVariables()}none
    {@code classpath}{@link ConfigSources#classpath(String)}{@code resource}
    {@code file}{@link ConfigSources#file(String)}{@code path}
    {@code directory}{@link ConfigSources#directory(String)}{@code path}
    {@code url}{@link ConfigSources#url(URL)}{@code url}
    {@code prefixed}{@link ConfigSources#prefixed(String, Supplier)} {@code key}
    {@code type} or {@code class}
    {@code properties} - *
    - * - * Example configuration in HOCON format: - *

    -     * 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"
    -     *         }
    -     *     }
    -     * ]
    -     * 
    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. - * - *

    Custom Sources and Source - * Types

    - *

    Custom Configuration Sources

    - * 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. - *

    Custom Configuration Source Types

    - * 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}. - *

    - * For example, the module {@code helidon-config-git} includes the resource - * {@code META-INF/resources/meta-config-sources.properties} containing - *

    -     * git = io.helidon.config.git.GitConfigSourceBuilder
    -     * 
    This defines the new source type {@code git} which can then be - * referenced from meta-configuration this way: - *
    -     * {
    -     *     type = "git"
    -     *     properties {
    -     *         path = "application.conf"
    -     *         directory = "/app-config/"
    -     *     }
    -     * }
    -     * 
    - * - * @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, Supplier */ default void init(ConfigContext context) { } - } diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigSourceConfigMapper.java b/config/config/src/main/java/io/helidon/config/spi/ConfigSourceConfigMapper.java deleted file mode 100644 index 010209223..000000000 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigSourceConfigMapper.java +++ /dev/null @@ -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 { - - 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 customSources; - - ConfigSourceConfigMapper() { - customSources = initCustomSources(); - } - - private static Map initCustomSources() { - try { - Properties properties = new Properties(); - Enumeration 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 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 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 providers(String type, Config properties) { - return Optional.ofNullable(customSources.get(type)) - .map(ConfigMappers::toClass) - .flatMap(clazz -> custom(clazz, properties)); - } - - private Optional 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() { - } - } - -} diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigSourceProvider.java b/config/config/src/main/java/io/helidon/config/spi/ConfigSourceProvider.java new file mode 100644 index 000000000..ad76da5f3 --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigSourceProvider.java @@ -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 { +} diff --git a/config/config/src/main/java/io/helidon/config/spi/MetaConfigurableProvider.java b/config/config/src/main/java/io/helidon/config/spi/MetaConfigurableProvider.java new file mode 100644 index 000000000..9a18095a4 --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/spi/MetaConfigurableProvider.java @@ -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 { + /** + * 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 supported(); +} diff --git a/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java b/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java index aa78e6b5e..a8912e3fb 100644 --- a/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java @@ -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, 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, String>> data = properties.orderedMap().entrySet() .stream() diff --git a/config/config/src/main/java/io/helidon/config/spi/OverrideSourceProvider.java b/config/config/src/main/java/io/helidon/config/spi/OverrideSourceProvider.java new file mode 100644 index 000000000..3761128dd --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/spi/OverrideSourceProvider.java @@ -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 { +} diff --git a/config/config/src/main/java/io/helidon/config/spi/PollingStrategy.java b/config/config/src/main/java/io/helidon/config/spi/PollingStrategy.java index 207861ad4..8ec575719 100644 --- a/config/config/src/main/java/io/helidon/config/spi/PollingStrategy.java +++ b/config/config/src/main/java/io/helidon/config/spi/PollingStrategy.java @@ -55,11 +55,11 @@ import io.helidon.config.PollingStrategies; * {@link AbstractParsableConfigSource} can use a different * {@code PollingStrategy}. *

    - * 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.) *

    diff --git a/config/config/src/main/java/io/helidon/config/spi/PollingStrategyConfigMapper.java b/config/config/src/main/java/io/helidon/config/spi/PollingStrategyConfigMapper.java deleted file mode 100644 index a18bdbfb8..000000000 --- a/config/config/src/main/java/io/helidon/config/spi/PollingStrategyConfigMapper.java +++ /dev/null @@ -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 Function> apply(Config config, Class 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 Optional>> builtin(String type, - Config properties, - Class targetType) { - final Function> 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 Supplier 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 Optional>> custom(Class clazz, - Config properties, - Class targetType) { - Function> 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) 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 Function> customSupplier(MethodHandle constructorHandle) { - return target -> { - try { - return (Supplier) 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(); - } - -} diff --git a/config/config/src/main/java/io/helidon/config/spi/PollingStrategyProvider.java b/config/config/src/main/java/io/helidon/config/spi/PollingStrategyProvider.java new file mode 100644 index 000000000..7a7bbbbad --- /dev/null +++ b/config/config/src/main/java/io/helidon/config/spi/PollingStrategyProvider.java @@ -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> { +} diff --git a/config/config/src/main/java/io/helidon/config/spi/RetryPolicy.java b/config/config/src/main/java/io/helidon/config/spi/RetryPolicy.java index bc779f96b..994e1a316 100644 --- a/config/config/src/main/java/io/helidon/config/spi/RetryPolicy.java +++ b/config/config/src/main/java/io/helidon/config/spi/RetryPolicy.java @@ -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 { - - /** - * Constructs a {@code RetryPolicy} from meta-configuration. - *

    - * 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.) - *

    - * The meta-configuration for a config source can set the property - * {@code retry-policy} using the following nested {@code properties}: - *

      - *
    • {@code type} - name of the retry policy implementation. - * - * - * - * - * - * - * - * - * - * - * - * - *
      Built-in Retry Policies
      NamePolicyProperties
      {@code repeat}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}. - *
        - *
      • {@code retries} (required) in {@code int} format
      • - *
      • {@code delay} in {@link Duration} format
      • - *
      • {@code delay-factor} - in {@code double} format
      • - *
      • {@code call-timeout} - in {@link Duration} format
      • - *
      • {@code overall-timeout} - in {@link Duration} format
      • - *
      - *
      - * - *
    • - *
    • {@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}. - *
    • - *
    - * 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. - *

    - * See {@link ConfigSource#create(Config)} for example of using built-in retry - * policies. - *

    Meta-configuration Support for Custom Retry Policies

    - * To support settings in meta-configuration, a custom retry policy must - * follow this pattern. - *

    - * 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 { default boolean cancel(boolean mayInterruptIfRunning) { return false; } - - @Override - default RetryPolicy get() { - return this; - } } diff --git a/config/config/src/main/java/io/helidon/config/spi/RetryPolicyConfigMapper.java b/config/config/src/main/java/io/helidon/config/spi/RetryPolicyConfigMapper.java deleted file mode 100644 index 28b1d0d68..000000000 --- a/config/config/src/main/java/io/helidon/config/spi/RetryPolicyConfigMapper.java +++ /dev/null @@ -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 { - - 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 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 custom(Class clazz, Config properties) { - final RetryPolicy retryPolicy; - if (RetryPolicy.class.isAssignableFrom(clazz)) { - retryPolicy = properties.as((Class) 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(); - } - -} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/package-info.java b/config/config/src/main/java/io/helidon/config/spi/RetryPolicyProvider.java similarity index 68% rename from microprofile/config/config/src/main/java/io/helidon/microprofile/config/package-info.java rename to config/config/src/main/java/io/helidon/config/spi/RetryPolicyProvider.java index 352a652e8..52226d3ea 100644 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/package-info.java +++ b/config/config/src/main/java/io/helidon/config/spi/RetryPolicyProvider.java @@ -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 { +} diff --git a/config/config/src/main/java9/module-info.java b/config/config/src/main/java9/module-info.java index aa2222ef5..846fa2e07 100644 --- a/config/config/src/main/java9/module-info.java +++ b/config/config/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; } diff --git a/microprofile/config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver b/config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver similarity index 82% rename from microprofile/config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver rename to config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver index 822d994d1..d1cabddfa 100644 --- a/microprofile/config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver +++ b/config/config/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigProviderResolver @@ -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 diff --git a/config/config/src/test/java/io/helidon/config/AbstractConfigImplTest.java b/config/config/src/test/java/io/helidon/config/AbstractConfigImplTest.java index b37d19c3a..24265cdf1 100644 --- a/config/config/src/test/java/io/helidon/config/AbstractConfigImplTest.java +++ b/config/config/src/test/java/io/helidon/config/AbstractConfigImplTest.java @@ -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 diff --git a/config/config/src/test/java/io/helidon/config/BuilderImplTest.java b/config/config/src/test/java/io/helidon/config/BuilderImplTest.java index b6cb6bc2e..cee106235 100644 --- a/config/config/src/test/java/io/helidon/config/BuilderImplTest.java +++ b/config/config/src/test/java/io/helidon/config/BuilderImplTest.java @@ -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)); } diff --git a/config/config/src/test/java/io/helidon/config/internal/ClasspathConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java similarity index 94% rename from config/config/src/test/java/io/helidon/config/internal/ClasspathConfigSourceTest.java rename to config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java index 5537116ab..a06c055c0 100644 --- a/config/config/src/test/java/io/helidon/config/internal/ClasspathConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java @@ -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(); } diff --git a/config/config/src/test/java/io/helidon/config/internal/ClasspathOverrideSourceTest.java b/config/config/src/test/java/io/helidon/config/ClasspathOverrideSourceTest.java similarity index 92% rename from config/config/src/test/java/io/helidon/config/internal/ClasspathOverrideSourceTest.java rename to config/config/src/test/java/io/helidon/config/ClasspathOverrideSourceTest.java index 57ad2f6aa..79b83ffee 100644 --- a/config/config/src/test/java/io/helidon/config/internal/ClasspathOverrideSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/ClasspathOverrideSourceTest.java @@ -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 { diff --git a/config/config/src/test/java/io/helidon/config/CompositeConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/CompositeConfigSourceTest.java index d56351853..1e851aae0 100644 --- a/config/config/src/test/java/io/helidon/config/CompositeConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/CompositeConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; diff --git a/config/config/src/test/java/io/helidon/config/ConfigChangesTest.java b/config/config/src/test/java/io/helidon/config/ConfigChangesTest.java index 191ed1d06..2d7c9120a 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigChangesTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigChangesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 diff --git a/config/config/src/test/java/io/helidon/config/ConfigMappersTest.java b/config/config/src/test/java/io/helidon/config/ConfigMappersTest.java index 53db41ec5..c0478ab68 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigMappersTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigMappersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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> 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 diff --git a/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java b/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java index e0972228c..501bf228f 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java @@ -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 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 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 diff --git a/config/config/src/test/java/io/helidon/config/ConfigTest.java b/config/config/src/test/java/io/helidon/config/ConfigTest.java index 9f57e7fdd..383c23d90 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigTest.java @@ -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 diff --git a/config/config/src/test/java/io/helidon/config/internal/DirectoryConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java similarity index 95% rename from config/config/src/test/java/io/helidon/config/internal/DirectoryConfigSourceTest.java rename to config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java index ae8e340cb..c592c094d 100644 --- a/config/config/src/test/java/io/helidon/config/internal/DirectoryConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,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 { diff --git a/config/config/src/test/java/io/helidon/config/internal/FileConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java similarity index 94% rename from config/config/src/test/java/io/helidon/config/internal/FileConfigSourceTest.java rename to config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java index 182a8f27e..aca3b2b0a 100644 --- a/config/config/src/test/java/io/helidon/config/internal/FileConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,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))); } diff --git a/config/config/src/test/java/io/helidon/config/MpConfigTest.java b/config/config/src/test/java/io/helidon/config/MpConfigTest.java new file mode 100644 index 000000000..073415f8b --- /dev/null +++ b/config/config/src/test/java/io/helidon/config/MpConfigTest.java @@ -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 configSources = config.getConfigSources(); + List 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? +} diff --git a/config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceServerMockTest.java b/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java similarity index 97% rename from config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceServerMockTest.java rename to config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java index d1f558fbf..60221a987 100644 --- a/config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceServerMockTest.java +++ b/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java @@ -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 { diff --git a/config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java similarity index 94% rename from config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceTest.java rename to config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java index 19f4fd49f..4a90630c2 100644 --- a/config/config/src/test/java/io/helidon/config/internal/UrlConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,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 { diff --git a/config/config/src/test/java/io/helidon/config/internal/FileOverrideSourceTest.java b/config/config/src/test/java/io/helidon/config/internal/FileOverrideSourceTest.java index c1fa14aa1..04ea604f0 100644 --- a/config/config/src/test/java/io/helidon/config/internal/FileOverrideSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/internal/FileOverrideSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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()))); } diff --git a/config/config/src/test/java/io/helidon/config/internal/MapConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/internal/MapConfigSourceTest.java index 1fba9de8f..88654c9eb 100644 --- a/config/config/src/test/java/io/helidon/config/internal/MapConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/internal/MapConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 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 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 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 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; diff --git a/config/config/src/test/java/io/helidon/config/internal/UrlOverrideSourceServerMockTest.java b/config/config/src/test/java/io/helidon/config/internal/UrlOverrideSourceServerMockTest.java index 15f62fec7..e0866c4b4 100644 --- a/config/config/src/test/java/io/helidon/config/internal/UrlOverrideSourceServerMockTest.java +++ b/config/config/src/test/java/io/helidon/config/internal/UrlOverrideSourceServerMockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; diff --git a/config/config/src/test/java/io/helidon/config/spi/AbstractConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/spi/AbstractConfigSourceTest.java index 656832f19..26b6e699c 100644 --- a/config/config/src/test/java/io/helidon/config/spi/AbstractConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/spi/AbstractConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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())); diff --git a/config/config/src/test/java/io/helidon/config/spi/AbstractParsableConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/spi/AbstractParsableConfigSourceTest.java index d9266120e..96d66e332 100644 --- a/config/config/src/test/java/io/helidon/config/spi/AbstractParsableConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/spi/AbstractParsableConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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())); diff --git a/config/config/src/test/java/io/helidon/config/spi/AbstractSourceTest.java b/config/config/src/test/java/io/helidon/config/spi/AbstractSourceTest.java index 7784c340d..c356cda58 100644 --- a/config/config/src/test/java/io/helidon/config/spi/AbstractSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/spi/AbstractSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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)); diff --git a/config/config/src/test/java/io/helidon/config/spi/ConfigSourceConfigMapperTest.java b/config/config/src/test/java/io/helidon/config/spi/ConfigSourceMetaConfigTest.java similarity index 89% rename from config/config/src/test/java/io/helidon/config/spi/ConfigSourceConfigMapperTest.java rename to config/config/src/test/java/io/helidon/config/spi/ConfigSourceMetaConfigTest.java index f3c1e3137..d5821ea08 100644 --- a/config/config/src/test/java/io/helidon/config/spi/ConfigSourceConfigMapperTest.java +++ b/config/config/src/test/java/io/helidon/config/spi/ConfigSourceMetaConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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))); diff --git a/config/config/src/test/java/io/helidon/config/spi/ConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/spi/ConfigSourceTest.java index 965dce07e..4980cf66f 100644 --- a/config/config/src/test/java/io/helidon/config/spi/ConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/spi/ConfigSourceTest.java @@ -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 diff --git a/config/config/src/test/java/io/helidon/config/spi/TestingConfigSource.java b/config/config/src/test/java/io/helidon/config/spi/TestingConfigSource.java index 2d282ffe2..32d6983a3 100644 --- a/config/config/src/test/java/io/helidon/config/spi/TestingConfigSource.java +++ b/config/config/src/test/java/io/helidon/config/spi/TestingConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,7 +108,7 @@ public class TestingConfigSource extends AbstractConfigSource { /** * Testing implementation of {@link AbstractSource.Builder}. */ - public static class TestingBuilder extends Builder { + public static class TestingBuilder extends Builder { private ObjectNode objectNode; private TestingBuilder() { @@ -118,8 +118,8 @@ public class TestingConfigSource extends AbstractConfigSource { } @Override - protected TestingBuilder init(Config metaConfig) { - return super.init(metaConfig); + public TestingBuilder config(Config metaConfig) { + return super.config(metaConfig); } public TestingBuilder objectNode(ObjectNode objectNode) { diff --git a/config/config/src/test/java/io/helidon/config/spi/TestingParsableConfigSource.java b/config/config/src/test/java/io/helidon/config/spi/TestingParsableConfigSource.java index ba2afe97f..cb3c1bd2f 100644 --- a/config/config/src/test/java/io/helidon/config/spi/TestingParsableConfigSource.java +++ b/config/config/src/test/java/io/helidon/config/spi/TestingParsableConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ public class TestingParsableConfigSource extends AbstractParsableConfigSource { + public static class TestingBuilder extends Builder { private Supplier> contentSupplier; private TestingBuilder() { @@ -97,8 +97,8 @@ public class TestingParsableConfigSource extends AbstractParsableConfigSource> contentSupplier) { diff --git a/config/config/src/test/resources/META-INF/resources/meta-config-sources.properties b/config/config/src/test/resources/META-INF/resources/meta-config-sources.properties deleted file mode 100644 index ad80c1ea3..000000000 --- a/config/config/src/test/resources/META-INF/resources/meta-config-sources.properties +++ /dev/null @@ -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 diff --git a/config/etcd/pom.xml b/config/etcd/pom.xml index baf885252..b46a5a3da 100644 --- a/config/etcd/pom.xml +++ b/config/etcd/pom.xml @@ -77,15 +77,6 @@ com.typesafe config - - - io.helidon.config - helidon-config-object-mapping - test - io.helidon.config helidon-config-testing @@ -137,6 +128,21 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + + + io.netty.eventLoopThreads + 2 + + + + org.codehaus.mojo build-helper-maven-plugin diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java index 8fc7c10a6..03426e05d 100644 --- a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { } /** - * 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(); + } } diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java index 8a7697298..f6eef7596 100644 --- a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 { + extends AbstractParsableConfigSource.Builder { + + /** + * 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)}. - *

    - * 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} *

      - *
    • {@code uri} - type {@link URI}
    • - *
    • {@code key} - type {@code String}
    • - *
    • {@code api} - type {@link EtcdApi}, e.g. {@code v3}
    • + *
    • {@code uri} - type {@link URI} - Etcd instance remote URI
    • + *
    • {@code key} - type {@code String} - Etcd key the configuration is associated with
    • + *
    • {@code api} - type {@link EtcdApi} - Etcd API version such as {@code v3}
    • *
    - * 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); } diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceProvider.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceProvider.java new file mode 100644 index 000000000..89cf1b914 --- /dev/null +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceProvider.java @@ -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 supported() { + return CollectionsHelper.setOf(TYPE); + } +} diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdPollingStrategyProvider.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdPollingStrategyProvider.java new file mode 100644 index 000000000..b9060bd10 --- /dev/null +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdPollingStrategyProvider.java @@ -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 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 supported() { + return CollectionsHelper.setOf(TYPE); + } +} diff --git a/config/etcd/src/main/java9/module-info.java b/config/etcd/src/main/java9/module-info.java index 91aa7fcfb..b60be10bf 100644 --- a/config/etcd/src/main/java9/module-info.java +++ b/config/etcd/src/main/java9/module-info.java @@ -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; } diff --git a/config/git/src/main/resources/META-INF/resources/meta-config-sources.properties b/config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider similarity index 81% rename from config/git/src/main/resources/META-INF/resources/meta-config-sources.properties rename to config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider index 2a84115f1..2dcc42ebc 100644 --- a/config/git/src/main/resources/META-INF/resources/meta-config-sources.properties +++ b/config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider @@ -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 diff --git a/config/etcd/src/main/resources/META-INF/resources/meta-config-sources.properties b/config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider similarity index 81% rename from config/etcd/src/main/resources/META-INF/resources/meta-config-sources.properties rename to config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider index 3adde3f53..a4c337768 100644 --- a/config/etcd/src/main/resources/META-INF/resources/meta-config-sources.properties +++ b/config/etcd/src/main/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider @@ -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 diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java index 25c0cace1..a2d490e69 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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 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 supported() { + return CollectionsHelper.setOf(TYPE); + } + } + public static class TestingEtcdEndpointPollingStrategy implements PollingStrategy { private final EtcdEndpoint etcdEndpoint; diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java index cc51e5c14..08fed4867 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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()) diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java index 0a9b750ba..882d0e1ac 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java @@ -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(); diff --git a/config/etcd/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider b/config/etcd/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider new file mode 100644 index 000000000..a300e44c0 --- /dev/null +++ b/config/etcd/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider @@ -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 diff --git a/config/git/pom.xml b/config/git/pom.xml index 8f049c409..b9b8d2be4 100644 --- a/config/git/pom.xml +++ b/config/git/pom.xml @@ -50,15 +50,6 @@ org.eclipse.jgit org.eclipse.jgit - - - io.helidon.config - helidon-config-object-mapping - test - io.helidon.config helidon-config-testing diff --git a/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java b/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java index f7aeb0fcb..4b775dac2 100644 --- a/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java +++ b/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java @@ -78,11 +78,32 @@ public class GitConfigSource extends AbstractParsableConfigSource { /** * 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); } /** diff --git a/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java index ae83ca114..0747c5fc3 100644 --- a/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java +++ b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java @@ -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 { + extends + AbstractParsableConfigSource.Builder { 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)}. - *

    - * Mandatory {@code properties}, see {@link #create(String)}: - *

      - *
    • {@code path} - type {@code String}
    • - *
    - * 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} *
      + *
    • {@code path} - type {@code String}, see {@link #path(String)}
    • *
    • {@code uri} - type {@code URI}, see {@link #uri(URI)}
    • *
    • {@code branch} - type {@code String}, see {@link #branch(String)}
    • *
    • {@code directory} - type {@code Path}, see {@link #directory(Path)}
    • @@ -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()); } diff --git a/config/git/src/main/java/io/helidon/config/git/GitConfigSourceProvider.java b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceProvider.java new file mode 100644 index 000000000..6ef9743c9 --- /dev/null +++ b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceProvider.java @@ -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 supported() { + return CollectionsHelper.setOf(TYPE); + } +} diff --git a/config/git/src/main/java9/module-info.java b/config/git/src/main/java9/module-info.java index d2aae1acf..5e0e2686e 100644 --- a/config/git/src/main/java9/module-info.java +++ b/config/git/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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; } diff --git a/config/tests/module-meta-source-2/src/main/resources/META-INF/resources/meta-config-sources.properties b/config/git/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider similarity index 79% rename from config/tests/module-meta-source-2/src/main/resources/META-INF/resources/meta-config-sources.properties rename to config/git/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider index 69d35cce1..115a2917d 100644 --- a/config/tests/module-meta-source-2/src/main/resources/META-INF/resources/meta-config-sources.properties +++ b/config/git/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider @@ -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 diff --git a/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java b/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java index b00b8a2a9..b029c2729 100644 --- a/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java +++ b/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java @@ -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 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 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; } diff --git a/config/git/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider b/config/git/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider new file mode 100644 index 000000000..2c1b313fe --- /dev/null +++ b/config/git/src/test/resources/META-INF/services/io.helidon.config.spi.PollingStrategyProvider @@ -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 diff --git a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java index 6195da823..c92e3d180 100644 --- a/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java +++ b/config/object-mapping/src/main/java/io/helidon/config/objectmapping/ObjectConfigMapperProvider.java @@ -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 Optional> findStaticStringMethodMapper(Class type, String methodName) { - return findStaticMethod(type, methodName, String.class) - .map(handle -> new StringMethodHandleConfigMapper<>( - type, - methodName + "(String) method", - handle)); + + Optional 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 Optional> findParseCharSequenceMethodMapper(Class type) { @@ -160,7 +171,7 @@ public class ObjectConfigMapperProvider implements ConfigMapperProvider { private static Optional> findGenericMapper(Class type) { try { return findConstructor(type) - .map(methodHandle -> new GenericConfigMapper<>(type, methodHandle)); + .map(methodHandle -> new GenericConfigMapper<>(type, methodHandle)); } catch (IllegalArgumentException e) { return Optional.empty(); } diff --git a/config/tests/integration-tests/src/test/java/io/helidon/config/tests/DemoTest.java b/config/tests/integration-tests/src/test/java/io/helidon/config/tests/DemoTest.java index 28ba2bb96..cf278540a 100644 --- a/config/tests/integration-tests/src/test/java/io/helidon/config/tests/DemoTest.java +++ b/config/tests/integration-tests/src/test/java/io/helidon/config/tests/DemoTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,8 +46,8 @@ public class DemoTest { Config config = Config.create(); assertThat( // STRING - config.get("app.greeting").asString(), - is(simpleValue("Hello"))); + config.get("app.greeting").asString().get(), + is("Hello")); } @Test diff --git a/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSource1Provider.java b/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSource1Provider.java new file mode 100644 index 000000000..f9e2d7d3a --- /dev/null +++ b/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSource1Provider.java @@ -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.tests.module.meta1; + +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 to load the config source. + */ +public class MyConfigSource1Provider implements ConfigSourceProvider { + private static final String TYPE = "meta1class"; + + @Override + public boolean supports(String type) { + return TYPE.equals(type); + } + + @Override + public ConfigSource create(String type, Config metaConfig) { + return metaConfig.as(MyConfigSource1.class).get(); + } + + @Override + public Set supported() { + return CollectionsHelper.setOf(TYPE); + } +} diff --git a/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSourceBuilder1.java b/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSourceBuilder1.java index 284c348ee..b40198e4d 100644 --- a/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSourceBuilder1.java +++ b/config/tests/module-meta-source-1/src/main/java/io/helidon/config/tests/module/meta1/MyConfigSourceBuilder1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,13 +59,13 @@ public class MyConfigSourceBuilder1 public static MyConfigSourceBuilder1 from(Config metaConfig) { return from(metaConfig.get("myProp1").asString().get(), metaConfig.get("myProp2").asInt().get()) - .init(metaConfig); + .config(metaConfig); } @Override - protected MyConfigSourceBuilder1 init(Config metaConfig) { + public MyConfigSourceBuilder1 config(Config metaConfig) { metaConfig.get("myProp3").asBoolean().ifPresent(this::myProp3); - return super.init(metaConfig); + return super.config(metaConfig); } /** diff --git a/config/tests/module-meta-source-1/src/main/resources/META-INF/resources/meta-config-sources.properties b/config/tests/module-meta-source-1/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider similarity index 79% rename from config/tests/module-meta-source-1/src/main/resources/META-INF/resources/meta-config-sources.properties rename to config/tests/module-meta-source-1/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider index 58fc0d7b8..c89c02ae5 100644 --- a/config/tests/module-meta-source-1/src/main/resources/META-INF/resources/meta-config-sources.properties +++ b/config/tests/module-meta-source-1/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider @@ -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. # -meta1class = io.helidon.config.tests.module.meta1.MyConfigSource1 +io.helidon.config.tests.module.meta1.MyConfigSource1Provider diff --git a/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2.java b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2.java index acea3ffb6..3118edac4 100644 --- a/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2.java +++ b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2Provider.java b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2Provider.java new file mode 100644 index 000000000..136dc95d6 --- /dev/null +++ b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSource2Provider.java @@ -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.tests.module.meta2; + +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 implementation. + */ +public class MyConfigSource2Provider implements ConfigSourceProvider { + private static final String PROVIDER_TYPE = "meta2class"; + + @Override + public boolean supports(String type) { + return PROVIDER_TYPE.equals(type); + } + + @Override + public ConfigSource create(String type, Config metaConfig) { + return metaConfig.as(MyConfigSource2.class).get(); + } + + @Override + public Set supported() { + return CollectionsHelper.setOf(PROVIDER_TYPE); + } +} diff --git a/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSourceBuilder2.java b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSourceBuilder2.java index fe981ea3b..f5ec99ccd 100644 --- a/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSourceBuilder2.java +++ b/config/tests/module-meta-source-2/src/main/java/io/helidon/config/tests/module/meta2/MyConfigSourceBuilder2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,13 +59,13 @@ public class MyConfigSourceBuilder2 public static MyConfigSourceBuilder2 from(Config metaConfig) { return from(metaConfig.get("myProp1").asString().get(), metaConfig.get("myProp2").asInt().get()) - .init(metaConfig); + .config(metaConfig); } @Override - protected MyConfigSourceBuilder2 init(Config metaConfig) { + public MyConfigSourceBuilder2 config(Config metaConfig) { metaConfig.get("myProp3").asBoolean().ifPresent(this::myProp3); - return super.init(metaConfig); + return super.config(metaConfig); } /** diff --git a/config/tests/module-meta-source-2/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider b/config/tests/module-meta-source-2/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider new file mode 100644 index 000000000..68fc513b6 --- /dev/null +++ b/config/tests/module-meta-source-2/src/main/resources/META-INF/services/io.helidon.config.spi.ConfigSourceProvider @@ -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.tests.module.meta2.MyConfigSource2Provider diff --git a/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java b/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java index 91e9a6a4a..dbad0a450 100644 --- a/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java +++ b/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public class ConfigCreateDefaultFromPropertiesTest { public void testCreate() { Config config = Config.create(); - assertThat(config.get(KEY).asString(), is(ConfigValues.simpleValue(CONFIG_VALUE))); + assertThat(config.get(KEY).asString().get(), is(CONFIG_VALUE)); } @Test diff --git a/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java b/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java index 766a4ac54..67805d465 100644 --- a/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java +++ b/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; /** - * Tests {@link Config#create()} from meta config with just built-in Properties Parser available, missing other parsers. + * Tests {@link Config#create()} from meta config with just built-in Properties Parser available, + * missing other parsers. */ public class ConfigCreateDefaultFromMetaPropertiesTest { @@ -50,7 +51,7 @@ public class ConfigCreateDefaultFromMetaPropertiesTest { Config config = Config.create(); - assertThat(config.get(KEY).asString(), is(ConfigValues.simpleValue(PROP_VALUE))); + assertThat(config.get(KEY).asString().get(), is(PROP_VALUE)); } } diff --git a/config/tests/test-default_config-6-meta-properties/src/test/resources/meta-config.properties b/config/tests/test-default_config-6-meta-properties/src/test/resources/meta-config.properties index 892afe006..6295fc124 100644 --- a/config/tests/test-default_config-6-meta-properties/src/test/resources/meta-config.properties +++ b/config/tests/test-default_config-6-meta-properties/src/test/resources/meta-config.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. @@ -14,6 +14,6 @@ # limitations under the License. # - -sources.0.type=classpath -sources.0.properties.resource=config.properties +sources.0.type=system-properties +sources.1.type=classpath +sources.1.properties.resource=config.properties diff --git a/config/tests/test-default_config-7-meta-hocon-json/src/test/resources/meta-config.json b/config/tests/test-default_config-7-meta-hocon-json/src/test/resources/meta-config.json index 4db2d4484..4e3b58e67 100644 --- a/config/tests/test-default_config-7-meta-hocon-json/src/test/resources/meta-config.json +++ b/config/tests/test-default_config-7-meta-hocon-json/src/test/resources/meta-config.json @@ -1,5 +1,8 @@ { "sources": [ + { + "type": "system-properties" + }, { "type": "classpath", "properties": { diff --git a/config/tests/test-default_config-8-meta-hocon/src/test/resources/meta-config.conf b/config/tests/test-default_config-8-meta-hocon/src/test/resources/meta-config.conf index 40bb696c3..c6b4f5e6b 100644 --- a/config/tests/test-default_config-8-meta-hocon/src/test/resources/meta-config.conf +++ b/config/tests/test-default_config-8-meta-hocon/src/test/resources/meta-config.conf @@ -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,9 @@ sources = [ + { + type = "system-properties" + }, { type = "classpath" properties { diff --git a/config/tests/test-default_config-9-meta-yaml/src/test/resources/meta-config.yaml b/config/tests/test-default_config-9-meta-yaml/src/test/resources/meta-config.yaml index f8b8a31a5..6f1b3285f 100644 --- a/config/tests/test-default_config-9-meta-yaml/src/test/resources/meta-config.yaml +++ b/config/tests/test-default_config-9-meta-yaml/src/test/resources/meta-config.yaml @@ -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 @@ sources: + - type: "system-properties" - type: "classpath" properties: resource: "config.yaml" diff --git a/config/tests/test-mappers-1-common/src/test/java/io/helidon/config/tests/mappers1/MapperServicesDisabledTest.java b/config/tests/test-mappers-1-common/src/test/java/io/helidon/config/tests/mappers1/MapperServicesDisabledTest.java index 8d24bf6b5..1d8a53771 100644 --- a/config/tests/test-mappers-1-common/src/test/java/io/helidon/config/tests/mappers1/MapperServicesDisabledTest.java +++ b/config/tests/test-mappers-1-common/src/test/java/io/helidon/config/tests/mappers1/MapperServicesDisabledTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/config/tests/test-meta-source/src/test/java/io/helidon/config/tests/meta/CustomConfigSourceTypesTest.java b/config/tests/test-meta-source/src/test/java/io/helidon/config/tests/meta/CustomConfigSourceTypesTest.java index 5ecbe2bd3..183f25cd2 100644 --- a/config/tests/test-meta-source/src/test/java/io/helidon/config/tests/meta/CustomConfigSourceTypesTest.java +++ b/config/tests/test-meta-source/src/test/java/io/helidon/config/tests/meta/CustomConfigSourceTypesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package io.helidon.config.tests.meta; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.config.ConfigValues; +import io.helidon.config.MetaConfig; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigSource; import io.helidon.config.tests.module.meta1.MyConfigSource1; @@ -50,7 +51,7 @@ public class CustomConfigSourceTypesTest { .build()) .build())); - ConfigSource source = metaConfig.as(ConfigSource::create).get(); + ConfigSource source = metaConfig.as(MetaConfig::configSource).get(); assertThat(source, is(instanceOf(sourceClass))); diff --git a/docs-internal/config-api.md b/docs-internal/config-api.md index 12ee4976e..fe96f0127 100644 --- a/docs-internal/config-api.md +++ b/docs-internal/config-api.md @@ -1,101 +1,52 @@ -# Helidon Config API design changes proposal +Helidon Config API +_____________________ -## Summary +# Helidon Config 2.0 -Summary table: +This section describes API changes between Helidon 1.x and Helidon 2.0 for Config -| Area | Impact on API | Impact on behavior | Impact on SPI | Urgency | -| ----------------- | ------------- | ------------------ | ------------- | ------- | -| Too many methods | High | Low or None | Low | High | -| Source types | Low or None | High | Medium | Medium | -| Change support | Medium | Medium | High | High | -| Polling strategies| Medium | Low | Medium | Low | -| Debugging | None | None | None | Medium | -| FileDetector SPI | None | None | None | High | -| Java Beans | None | Low | None | High | -| No reflection as()| None | High | None | High | -| Remove ConfigMapper| Medium | None | Medium | High | -| Source is Supplier| Compatible | None | Low or None | Medium | +## Meta Configuration +Issue: https://github.com/oracle/helidon/issues/1101 +PR: https://github.com/oracle/helidon/pull/1102 -### Too Many Methods -Too many methods are part of public API - this makes it very complicated to test, maintain and (sometimes) use +### API Changes -This is caused by: -1. each primitive type has its own method + methods for any type and methods for map, list and nodes - 1. boolean - 2. int - 3. long - 4. Double - 5. String - 5. Map - 5. List - 5. Config - 5. Class -1. supporting too many paradigms - 1. returning T, Optional, Supplier, Supplier> - 1. for each of these methods (except for Optional) supporting a method without and with a default value - 1. this means that we have 6 methods for each type (using boolean as an example): - 1. asBoolean() - 1. asBoolean(boolean default) - 1. asBooleanSupplier() - 1. asBooleanSupplier(boolean default) - 1. asOptionalBoolean() - 1. asOptionalBooleanSupplier() - -#### Proposal -Create a typed config value `ConfigValue`. -This would leave config with the following accessor methods: -1. Required: - 1. `ConfigValue as(Class type) throws ConfigMappingException` - 2. `ConfigValue> asList(Class type) throws ConfigMappingException` - 3. `ConfigValue> asMap()` -2. Optional (shortcut): - 2. `ConfigValue asNode()` - 3. `ConfigValue> asNodeList()` - 4. `ConfigValue asBoolean() throws ConfigMappingException` - 5. other shortcut methods for primitive types - -The `ConfigValue` interface would have the following methods to access typed value (as supported in original API): -1. `Optional asOptional()` - to get the "real" Optional value -3. `Supplier asSupplier()` - supplier of current value (if config changes) -4. `Supplier asSupplier(T defaultValue)` - supplier of current value with default -5. `Supplier> asOptionalSupplier()` - supplier of current value as an optional -2. `T get() throws MissingValueException` - same as in java.util.Optional, just throws a different exception -6. and all methods of java.util.Optional (unfortunatelly optional is a final class, so we have no choice but to copy - the methods) - including the methods from java9+ (stream(), ifPresentOrElse(), or()) +| Class | Method | Type | Description | +| ----------------- | ----------------- | ---- | -------------------------- | +| `Config.Builder` | `loadSourcesFrom` | remove | Replaced with methods on `MetaConfig` | +| | `builderLoadSourceFrom` | remove | Replaced with methods on `MetaConfig` | +| | `addSource` | add | Add a config source | +| | `config(Config)` | add | Use meta configuration to configure the builder | +| | `metaConfig()` | add | Use meta configuration (located automatically) to configure the builder | +| `ConfigSources` | `file(Path)` | add | Create a FileConfigSource builder from Path | +| | `load(Supplier...)` | remove | Replaced with methods on `MetaConfig` | +| | `load(Config)` | remove | Replaced with methods on `MetaConfig` | +| `MetaConfig` | | add | A new class for all things related to meta configuration | +| `MetaConfigFinder`| | add | Utility class to locate meta configuration and default configuration | +| `MetaProviders` | | add | Utility class to gather service loader implementations and to add built-ins | +| `ClasspathConfigSource`, `ClasspathOverrideSource`, `DirectoryConfigSource`, `FileConfigSource`, `FileOverrideSource`, `PrefixedConfigSource`, `UrlConfigSource`, `UrlOverrideSource`, `etcd.EtcdConfigSource`, `git.GitConfigSource` | | | | +| | `builder()` | add | Refactored to support builder pattern as rest of Helidon | +| `.Builder` | constructor | mod | No longer public, no parameters, usages replaced with `builder()` | +| | property setter | add | For each config source a property setter is added (`url`, `resource` etc.) | +| | `init` -> `config`| rename | Meta configuration method was renamed from `init` to `config` and made public | +| | `build()` | behavior | Method can now throw an `IllegalArgumentException` - originally the constructor would fail with `NullPointerException` | +| `MapConfigSource` | constructor | mod | No long public | +| | `create` | add | Static factory methods | +| `spi.AbstractConfigSource`, `spi.AbstractParsableConfigSource`, `spi.AbstractSource` | `init` | mod | Renamed to `config` and made public | +| `spi.ConfigSource` | `create(Config)` | remove | Moved to `MetaConfig` | +| `spi.ConfigSourceProvider`| | add | Java service loader service to support custom meta configurable sources | +| `spi.OverrideSource` | `create` | mod | No longer throws an `IOException`, now throws `ConfigException` | +| `spi.OverrideSourceProvider` | | add | Java service loader service to support custom meta configurable override sources | +| `spi.PollingStrategyProvider` | | add | Java service loader service to support custom meta configurable polling strategies | +| `spi.RetryPolicy` | `create(Config)` | remove | Moved to `MetaConfig` | +| | `get` | remove | No longer implements `Supplier` | +| `spi.RetryPolicyProvider` | | add | Java service loader service to support custom meta configurable retry policies | -Example: -```java -// unchanged for String, as Config implements Value -config.get("client-id").value().ifPresent(this::clientId); + +# Other proposed features -// current for primitive type -config.get("proxy-port").asOptionalInt().ifPresent(this::proxyPort); -// new for primitive type -config.get("proxy-port").as(Integer.class).ifPresent(this::proxyPort); - -// current for type with a mapper -config.get("identity-uri").asOptional(URI.class).ifPresent(this::identityUri); -// new for type with a mapper -config.get("identity-uri").as(URI.class).ifPresent(this::identityUri); - -// current for type with a factory method -config.get("oidc-config").asOptional(OidcConfig.class).ifPresent(this::oidcConfig); -// new for type with a factory method (part of "No reflection as()" problem -config.get("oidc-config").as(OidcConfig::create).ifPresent(this::oidcConfig); - -// current using response value -int port = config.get("proxy-port").asInt(7001); -// new using response value (if we decide to have shortcuts for primitives) -int port = config.get("proxy-prot").asInt().get(7001); -// new otherwise -int port = config.get("proxy-port").as(Integer.class).get(7001); - -``` - -### Source types - -#### Lazy config sources +## Source types +### Lazy config sources We do not support config sources that require lazy access to values (using term "lazy sources" in the text) _This may be sources that cannot list the keys, or where listing the keys is not feasible (speed, memory consumption etc.)_ @@ -116,7 +67,7 @@ Other changes: 2. Weld 4. Behavior must be clearly documented -#### Mutable sources with no notification support +## Mutable sources with no notification support Some of our config source are mutable, yet do not support notifications. We should change all config sources to support notifications if so chosen by the user. If need be, these should be polled regularly and compared with previous version. @@ -131,43 +82,18 @@ Current change support is too complex and uses APIs not suitable for the purpose boolean 3. Remove dependency on SubmissionPublisher (and on project Reactor transitively) -### Polling strategies +## Polling strategies 1. Check if these can be simplified, as current API and SPI is not easy to use. 2. Make sure one thing can be achieved only one way - e.g why do we have polling and watching both available for File config sources? -### Debugging -Provide better support for debugging: -1. Keep information about a config source that provided a value of a node -2. This may be accessible only in a debugger (e.g. no need to change API or SPI) - -### File Detector SPI +## File Detector SPI Currently the FileDetector service does not work consistently in all environments. Known problems: 1. when running JDK9+ using maven exec plugin (test with yaml config) 2. when running in some docker images (need to find the failing image) -### Java Beans -Separate java beans support into a different module (including annotations @Value and @Transient). -The current support can build instances from config using reflection. This is complicated -part of the code that should not be part of SE Config by default. -Add SPI to allow for such (more complex) config.as(AClass.class) transformation - -### No reflection as() -Do not use reflection in T Config.as(Class type) method. Currently there is a complicated -code that introspects the class to find suitable constructor or factory method. -Create a new method `ConfigValue as(Function factoryMethod)`. - -We can then use: -```java -config.as(OidcConfig::create); -config.as(SomeClass::new); -``` - -### Remove ConfigMapper -Remove ConfigMapper interface, as it is in fact a Function. - -### Source is Supplier +## Source is Supplier Currently the ConfigSource interface extends Supplier and default implementation of the `get()` method returns `this`. Reason behind this (probably) is to have a single set of methods on `Builder`, that diff --git a/docs/src/main/docs/config/01_introduction.adoc b/docs/src/main/docs/config/01_introduction.adoc index 78a08f759..e4e325ca7 100644 --- a/docs/src/main/docs/config/01_introduction.adoc +++ b/docs/src/main/docs/config/01_introduction.adoc @@ -221,7 +221,7 @@ link:{javadoc-base-url-api}/Config.Builder.html[`Config.Builder`]. + or * creating a <> -file on the runtime classpath to control how the config system prepares the +file on the runtime classpath or file system to control how the config system prepares the default configuration. Once created, the `Config` object provides many methods the application can use to diff --git a/docs/src/main/docs/config/06_advanced-configuration.adoc b/docs/src/main/docs/config/06_advanced-configuration.adoc index a6ad14afe..90931cb44 100644 --- a/docs/src/main/docs/config/06_advanced-configuration.adoc +++ b/docs/src/main/docs/config/06_advanced-configuration.adoc @@ -20,7 +20,7 @@ = Advanced Configuration Topics :description: Helidon config advanced configuration -:keywords: helidon, config +:keywords: helidon, config, meta :toc: preamble :toclevels: 4 @@ -478,19 +478,14 @@ list of possible sources. ==== Loading Config by Specifying a Meta-configuration File [[Config-Advanced-Sources-MetaSource]] Your application loads -the configuration specified by a meta-config file by: - -* invoking the link:{javadoc-base-url-api}/ConfigSources.html#load-io.helidon.config.Config-[`ConfigSources.load(Config)`] +the configuration specified by a meta-config file by invoking the link:{javadoc-base-url-api}/MetaConfig.html#config-io.helidon.config.Config-[`MetaConfig.config(Config)`] method, passing a config object read from the meta-config source as the argument; -* invoking the link:{javadoc-base-url-api}/Config.html#loadSourcesFrom-java.util.function.Supplier...[`Config.loadSourcesFrom`] -method, or -* invoking the link:{javadoc-base-url-api}/Config.html#builderLoadSourcesFrom-java.util.function.Supplier...[`Config.builderLoadSourceFrom`] -method. -These methods return either a `Config` tree or a `Config.Builder` which -your application can further fine-tune before using to construct a `Config` -tree. The config system interprets the meta-config as directions for how to -build a config tree, rather than as the config data itself. +If you desire a `Config.Builder` instead of a fully built `Config` instance, +you can use the `Config.Builder.config(Config)` method to update the builder with +meta configuration. Your application can further fine-tune this builder +before using it to construct a `Config` tree. The config system interprets the meta-config +as directions for how to build a config tree, rather than as the config data itself. ==== Loading Config from an Implicit Meta-configuration File [[Config-Advanced-Config-MetaConfig]] The <> section shows how to use @@ -501,7 +496,8 @@ from which to load config sources to be used for the default config. The `Config.create()` method determines the default configuration from the following search: -. Attempt to load _meta-config_ from at most one of the following, checked in this order: +. Attempt to load _meta-config_ from at most one of the following files, +first on the file system in current directory, then on classpath, checked in this order: .. `meta-config.yaml` - meta configuration file in YAML format .. `meta-config.conf` - meta configuration file in HOCON format .. `meta-config.json` - meta configuration file in JSON format @@ -520,32 +516,17 @@ only if the classpath includes the corresponding parsers. The introduction secti section describes this further. ==== Meta-configuration File Format -Each meta-configuration file must contain the top-level `sources` property that is an -array (ordered list) of config sources. The meta-config file can contain other -top-level keys as well but the config system ignores them when it interprets the contents as -meta-configuration. +See javadoc link:{javadoc-base-url-api}/Config.Builder.html#config-io.helidon.config.Config-[`Config.Builder.config(Config)`]. -Each `sources` property must contain exactly one of following top level properties: +Meta configuration can be used to configure various options that are available on configuration +builder. +It must contain at least the list of sources to use to load configuration from. -.Meta-configuration Required Top-level Property -|=== -|Property Name |Usage - -|`type` a|Either: + - -* a predefined type (see <>), or -* a custom config source ID -|`class` a|Fully-qualified class name of either: + - -* a custom config source implementation, or -* a builder class with a `build()` method that returns `ConfigSource` -|=== - -If you specify both `type` and `class`, the config system ignores the `class` -setting. - -In addition, each `sources` property can optionally have a `properties` property -which assigns type-specific attributes for the config source being defined. +The root `sources` property contains an array (ordered) of objects defining each config source to +be used. +Each element of the array must contain at least the `type` property, determining the +config source type (such as `system-properties`, `file`). It may also contain a `properties` +property with additional configuration of the config source. ===== Built-in Types [[MetaConfig-built-in-types]] The config system supports these built-in types: @@ -561,7 +542,7 @@ The config system supports these built-in types: |`directory` |Each file in directory used as config entry, with key = file name and value = file contents |`ConfigSources.directory(String)` |`path` - path to the directory to use |`url` |Specified URL is read as a config source |`ConfigSources.url(URL)` | `url` - URL from which to load the config |`prefixed` |Associated config source is loaded with the specified prefix |`ConfigSources.prefixed(String,Supplier)` a|* `key` - key of config element in associated source to load -* `type` or `class` - associated config source specification +* `type` - associated config source specification * `properties` - as needed to further qualify the associated config source |=== @@ -571,75 +552,50 @@ corresponding config source type. The JavaDoc for the related config source type builders lists the supported properties for each type. (For example, link:{javadoc-base-url-api}/internal/FileConfigSource.FileBuilder.html[`FileConfigSource.FileBuilder`].) -Here is example meta-configuration in HOCON format. Note how the `properties` sections +Here is example meta-configuration in YAML format. Note how the `properties` sections are at the same level as the `type` or `class` within a `sources` array entry. -[source,hocon] -.Meta-configuration `config-meta-all.conf` illustrating all built-in sources available on the classpath +[source,yaml] +.Meta-configuration `meta-config.yaml` illustrating all built-in sources available on the classpath ---- -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/env.yaml" - polling-strategy { - type = "watch" - } - } - } - { - type = "prefixed" - properties { - key = "app" - type = "classpath" - properties { - resource = "app.conf" - } - } - } - { - type = "classpath" - properties { - resource = "application.conf" - } - } -] +caching.enabled: false +sources: + - type: "system-properties" + - type: "environment-variables" + - 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: + optional: true + path: "conf/env.yaml" + polling-strategy: + type: "watch" + - type: "prefixed" + properties: + key: "app" + type: "classpath" + properties: + resource: "app.conf" + - type: "classpath" + properties: + resource: "application.conf" ---- Note that the example shows how your meta-configuration can configure optional features such as polling @@ -647,60 +603,55 @@ strategies and retry policies for config sources. ==== Meta-config for Custom Source Types You can use meta-config to set up custom config source types as well as the -built-in ones described above. Meta-config supports this in two ways: +built-in ones described above. -* by class name -* by custom type name +A custom config source can easily support meta configuration by implementing the +`io.helidon.config.spi.ConfigSourceProvider` interface and registering it as a Java service loader service. +Note that the provider can be used for multiple ConfigSource implementations. -===== Custom Source Types using `class` -Use the `class` property in one of your `sources` entries and as its value -give the fully-qualified class name of your custom source type. The config -system will use that class as the `ConfigSource` (or as a builder for one) -for that source. -[source,hocon] +Implement the `ConfigSourceProvider` +[source,java] ---- -{ - class = "io.helidon.config.git.GitConfigSourceBuilder" - properties { - path = "application.conf" - directory = "/app-config" +public class MyConfigSourceProvider implements ConfigSourceProvider { + private static final String TYPE = "my-type"; + + @Override + public boolean supports(String type) { + return TYPE.equals(type); + } + + @Override + public ConfigSource create(String type, Config metaConfig) { + // as we only support one in this implementation, we can just return it + return MyConfigSource.create(metaConfig); + } + + @Override + public Set supported() { + return Collections.singleton(TYPE); } } ---- -===== Custom Source Type using `type` -You can add your own custom type names to the built-in ones by adding to a -`META-INF/resources/meta-config-sources.properties` file on the classpath. -In this file each property name is a custom config source type name and its -value is the fully-qualified class name for a custom `ConfigSource` implementation -or a builder for it. - -For example, the Helidon module `helidon-config-git` provides this -`META-INF/resources/meta-config-sources.properties` file: - +Register it as a java service loader service [source] -.Definition of `git` Config Source Type +.File `META-INF/services/io.helidon.config.spi.ConfigSourceProvider` ---- -git = io.helidon.config.git.GitConfigSourceBuilder +io.helidon.examples.MyConfigSourceProvider ---- -This definition lets you configure a git config source in meta-configuration as -follows: - -[source,hocon] +Now you can use the following meta configuration: +[source,yaml] ---- -{ - type = "git" - properties { - path = "application.conf" - directory = "/app-config" - } -} +sources: + - type: "system-properties" + - type: "environment-variables" + - type: "my-type" + properties: + my-property: "some-value" ---- -You can define and use your own custom config source type names similarly. - Note that it is the `AbstractSource` SPI class that provides support for polling strategies and retry policies. If you create custom config sources that should also offer this support be @@ -713,24 +664,18 @@ classpath to load a `Config` tree: [source,java] .Loading Config using Meta-configuration ---- -ConfigSource sourceFromMetaConfig = ConfigSources.load( // <1> - classpath("config-meta-all.conf")).build(); // <2> - -Config config = Config.create(sourceFromMetaConfig); // <3> +Config metaConfig = Config.create(classpath("config-meta-all.conf")); // <1> +Config config = MetaConfig.config(metaConfig); // <2> ---- -<1> The `ConfigSources.load` method creates a config source for the eventual config -from the config source argument which specifies the meta-config. -<2> This example uses meta-config from a resource on the classpath but you can -use meta-config from any valid config source. -<3> The `load` method populates the `sourceFromMetaConfig` `ConfigSource` from -all the actual sources declared in the meta-configuration. The returned `ConfigSource` -is ready for use in creating a `Config` instance. +<1> We create a meta configuration from our meta configuration source +<2> This method populates the config sources from +all the actual sources declared in the meta-configuration. ==== Meta-config for Polling Strategies and Retry Policies Your meta-config can include the set-up for polling strategies and retry policies if the config source supports them. Declare them in a way similar to -how you declare the config sources themselves: by `type` or `class` and with +how you declare the config sources themselves: by `type` and with accompanying `properties`. .Meta-config Support for Built-in Polling Strategies @@ -764,10 +709,13 @@ the delay for each successive retry * `overall-timeout` (`Duration`) - total timeout for all retry calls and delays |=== -To specify a custom polling strategy or custom retry policy, specify `class` (instead of -`type`) and give the fully-qualified class name for the implementation class. -If your custom class needs parameters to control its behavior the config system -uses `io.helidon.config.ConfigMapper` to initialize the class instance. +To specify a custom polling strategy or custom retry policy, implement the interface +and then implement the `io.helidon.config.spi.PollingStrategyProvider` or +`io.helidon.config.spi.RetryPolicyProvider` to enable your custom implementations for +meta configuration. +You can then use any custom properties - these are provided as a `Config` instance to +the `create` method of the Provider implementation. + See link:{javadoc-base-url-api}/spi/RetryPolicy.html[`RetryPolicy`] and link:{javadoc-base-url-api}/spi/PollingStrategy.html[`PollingStrategy`] JavaDoc sections. diff --git a/examples/config/git/src/main/java/io/helidon/config/examples/git/Main.java b/examples/config/git/src/main/java/io/helidon/config/examples/git/Main.java index 3ed848111..1c7cdb412 100644 --- a/examples/config/git/src/main/java/io/helidon/config/examples/git/Main.java +++ b/examples/config/git/src/main/java/io/helidon/config/examples/git/Main.java @@ -20,7 +20,7 @@ import java.net.URI; import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.config.git.GitConfigSourceBuilder; +import io.helidon.config.git.GitConfigSource; /** * Git source example. @@ -66,7 +66,7 @@ public class Main { System.out.println("Loading from branch " + env.get(ENVIRONMENT_NAME_PROPERTY).asString().orElse("null")); Config config = Config.create( - GitConfigSourceBuilder.create("application.conf") + GitConfigSource.builder("application.conf") .uri(URI.create("https://github.com/helidonrobot/test-config.git")) .branch(env.get(ENVIRONMENT_NAME_PROPERTY).asString().orElse("master")) .build()); diff --git a/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java index 6aadde4a4..a0067e90d 100644 --- a/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java +++ b/examples/config/sources/src/main/java/io/helidon/config/examples/sources/LoadSourcesExample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,8 +46,11 @@ public class LoadSourcesExample { with a filter which convert values with keys ending with "level" to upper case */ - Config config = Config.builderLoadSourcesFrom(file("conf/meta-config.yaml").optional(), - classpath("meta-config.yaml")) + Config metaConfig = Config.create(file("conf/meta-config.yaml").optional(), + classpath("meta-config.yaml")); + + Config config = Config.builder() + .config(metaConfig) .addFilter((key, stringValue) -> key.name().equals("level") ? stringValue.toUpperCase() : stringValue) .build(); diff --git a/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml b/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml index f6acbf76c..18dff5283 100644 --- a/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml +++ b/examples/integrations/cdi/datasource-hikaricp-h2/pom.xml @@ -88,7 +88,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml b/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml index 53ff11542..47c2b789e 100644 --- a/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml +++ b/examples/integrations/cdi/datasource-hikaricp-mysql/pom.xml @@ -89,7 +89,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/integrations/cdi/datasource-hikaricp/pom.xml b/examples/integrations/cdi/datasource-hikaricp/pom.xml index 1bdbeb2e4..267f0f3ba 100644 --- a/examples/integrations/cdi/datasource-hikaricp/pom.xml +++ b/examples/integrations/cdi/datasource-hikaricp/pom.xml @@ -93,7 +93,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/integrations/cdi/jedis/pom.xml b/examples/integrations/cdi/jedis/pom.xml index b92060a3f..3f554ff1c 100644 --- a/examples/integrations/cdi/jedis/pom.xml +++ b/examples/integrations/cdi/jedis/pom.xml @@ -87,7 +87,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/integrations/cdi/jpa/pom.xml b/examples/integrations/cdi/jpa/pom.xml index 1103f346d..b5ff32159 100644 --- a/examples/integrations/cdi/jpa/pom.xml +++ b/examples/integrations/cdi/jpa/pom.xml @@ -98,7 +98,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/integrations/cdi/oci-objectstorage/pom.xml b/examples/integrations/cdi/oci-objectstorage/pom.xml index e450f832e..0e7f13913 100644 --- a/examples/integrations/cdi/oci-objectstorage/pom.xml +++ b/examples/integrations/cdi/oci-objectstorage/pom.xml @@ -83,7 +83,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime diff --git a/examples/microprofile/hello-world-explicit/src/main/java/io/helidon/microprofile/example/helloworld/explicit/Main.java b/examples/microprofile/hello-world-explicit/src/main/java/io/helidon/microprofile/example/helloworld/explicit/Main.java index e26922746..6b26c152f 100644 --- a/examples/microprofile/hello-world-explicit/src/main/java/io/helidon/microprofile/example/helloworld/explicit/Main.java +++ b/examples/microprofile/hello-world-explicit/src/main/java/io/helidon/microprofile/example/helloworld/explicit/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,9 @@ package io.helidon.microprofile.example.helloworld.explicit; import java.util.logging.Logger; -import io.helidon.config.Config; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.weld.environment.se.Weld; import org.jboss.weld.environment.se.WeldContainer; @@ -50,7 +49,9 @@ public class Main { .addApplication(HelloWorldApplication.class) .cdiContainer(cdiContainer) // using a customized helidon config instance (in this case the default...) - .config(MpConfig.builder().config(Config.create()).build()) + .config(ConfigProviderResolver.instance() + .getBuilder() + .build()) .host("localhost") // use a random free port .port(0) diff --git a/integrations/cdi/datasource-hikaricp/pom.xml b/integrations/cdi/datasource-hikaricp/pom.xml index 3fd6e7510..1607aa588 100644 --- a/integrations/cdi/datasource-hikaricp/pom.xml +++ b/integrations/cdi/datasource-hikaricp/pom.xml @@ -83,7 +83,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime true diff --git a/integrations/cdi/datasource-ucp/pom.xml b/integrations/cdi/datasource-ucp/pom.xml index edb4dbd8f..852e51fdd 100644 --- a/integrations/cdi/datasource-ucp/pom.xml +++ b/integrations/cdi/datasource-ucp/pom.xml @@ -82,7 +82,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime true diff --git a/integrations/cdi/datasource/pom.xml b/integrations/cdi/datasource/pom.xml index dcedfa245..5cbf2d821 100644 --- a/integrations/cdi/datasource/pom.xml +++ b/integrations/cdi/datasource/pom.xml @@ -62,7 +62,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime true diff --git a/integrations/cdi/jedis-cdi/pom.xml b/integrations/cdi/jedis-cdi/pom.xml index eeb89ea22..c52aa5a42 100644 --- a/integrations/cdi/jedis-cdi/pom.xml +++ b/integrations/cdi/jedis-cdi/pom.xml @@ -63,7 +63,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime true diff --git a/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java b/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java index 3070074e1..b7dbe9df9 100644 --- a/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java +++ b/integrations/cdi/jedis-cdi/src/test/java/io/helidon/integrations/jedis/cdi/TestExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,12 +32,8 @@ import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.junit.jupiter.api.Assertions.assertAll; @ApplicationScoped class TestExtension { @@ -62,7 +58,7 @@ class TestExtension { this.cdiContainer.close(); } } - + private void onStartup(@Observes @Initialized(ApplicationScoped.class) final Object event, final Config config, @Named("fred") final JedisPoolConfig fredJedisPoolConfig, diff --git a/integrations/cdi/jpa-cdi/pom.xml b/integrations/cdi/jpa-cdi/pom.xml index e8fb2bce6..19fd1a9a7 100644 --- a/integrations/cdi/jpa-cdi/pom.xml +++ b/integrations/cdi/jpa-cdi/pom.xml @@ -78,7 +78,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config test diff --git a/integrations/cdi/jpa-weld/pom.xml b/integrations/cdi/jpa-weld/pom.xml index 2047e3987..5b4de2413 100644 --- a/integrations/cdi/jpa-weld/pom.xml +++ b/integrations/cdi/jpa-weld/pom.xml @@ -84,7 +84,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config test diff --git a/integrations/cdi/oci-objectstorage-cdi/pom.xml b/integrations/cdi/oci-objectstorage-cdi/pom.xml index 79fbe302e..85c80e145 100644 --- a/integrations/cdi/oci-objectstorage-cdi/pom.xml +++ b/integrations/cdi/oci-objectstorage-cdi/pom.xml @@ -81,7 +81,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config runtime true diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/ConfigSubstitutions.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/ConfigSubstitutions.java deleted file mode 100644 index 5f240b4f7..000000000 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/ConfigSubstitutions.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.integrations.graal.nativeimage.extension; - -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; - -import io.helidon.config.Config; -import io.helidon.config.spi.PollingStrategy; - -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; - -/** - * Graal VM native image substitutions for Config. - */ -public final class ConfigSubstitutions { - @TargetClass(className = "io.helidon.config.spi.PollingStrategyConfigMapper") - static final class ConfigMapperSvmExtension { - @Substitute - private Optional>> custom(Class clazz, - Config properties, - Class targetType) { - return Optional.empty(); - } - } -} diff --git a/microprofile/bundles/helidon-microprofile-1.1/pom.xml b/microprofile/bundles/helidon-microprofile-1.1/pom.xml index 558d7109f..10b830fe3 100644 --- a/microprofile/bundles/helidon-microprofile-1.1/pom.xml +++ b/microprofile/bundles/helidon-microprofile-1.1/pom.xml @@ -50,7 +50,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config diff --git a/microprofile/bundles/helidon-microprofile-1.1/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-1.1/src/main/java9/module-info.java index 508ca485a..46f5bf57e 100644 --- a/microprofile/bundles/helidon-microprofile-1.1/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-1.1/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ * Aggregator module for microprofile 1.1. */ module io.helidon.microprofile.v1_1 { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; } diff --git a/microprofile/bundles/helidon-microprofile-1.2/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-1.2/src/main/java9/module-info.java index 5baf10329..23538ab18 100644 --- a/microprofile/bundles/helidon-microprofile-1.2/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-1.2/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ * Aggregator module for microprofile 1.2. */ module io.helidon.microprofile.v1_2 { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; requires transitive io.helidon.microprofile.health; diff --git a/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java index eed5a4672..427345af1 100644 --- a/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java @@ -18,7 +18,6 @@ * Aggregator module for microprofile 2.2. */ module io.helidon.microprofile.v2_2 { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; requires transitive io.helidon.microprofile.health; diff --git a/microprofile/bundles/helidon-microprofile-3.0/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-3.0/src/main/java9/module-info.java index 55e76e82e..bb06ae5a8 100644 --- a/microprofile/bundles/helidon-microprofile-3.0/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-3.0/src/main/java9/module-info.java @@ -18,7 +18,6 @@ * Aggregator module for microprofile 3.0. */ module io.helidon.microprofile.v3_0 { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; requires transitive io.helidon.microprofile.health; diff --git a/microprofile/bundles/helidon-microprofile-3.1/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-3.1/src/main/java9/module-info.java index b1d64178a..ce0bc287c 100644 --- a/microprofile/bundles/helidon-microprofile-3.1/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-3.1/src/main/java9/module-info.java @@ -18,7 +18,6 @@ * Aggregator module for microprofile 3.1. */ module io.helidon.microprofile.v3_1 { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; requires transitive io.helidon.microprofile.health; diff --git a/microprofile/bundles/helidon-microprofile-core/pom.xml b/microprofile/bundles/helidon-microprofile-core/pom.xml index 0a5051daa..5af0f4ea1 100644 --- a/microprofile/bundles/helidon-microprofile-core/pom.xml +++ b/microprofile/bundles/helidon-microprofile-core/pom.xml @@ -46,7 +46,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config diff --git a/microprofile/bundles/helidon-microprofile-core/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-core/src/main/java9/module-info.java index 95f5a77ca..693056138 100644 --- a/microprofile/bundles/helidon-microprofile-core/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-core/src/main/java9/module-info.java @@ -18,7 +18,6 @@ * Aggregator module for microprofile core. */ module io.helidon.microprofile.bundle.core { - requires transitive io.helidon.microprofile.config.cdi; requires transitive io.helidon.microprofile.config; requires transitive io.helidon.microprofile.server; diff --git a/microprofile/bundles/helidon-microprofile/pom.xml b/microprofile/bundles/helidon-microprofile/pom.xml index ada5e634c..2697d023c 100644 --- a/microprofile/bundles/helidon-microprofile/pom.xml +++ b/microprofile/bundles/helidon-microprofile/pom.xml @@ -36,7 +36,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config io.helidon.microprofile.health diff --git a/microprofile/config/config-cdi/pom.xml b/microprofile/config/config-cdi/pom.xml deleted file mode 100644 index f0f6293ea..000000000 --- a/microprofile/config/config-cdi/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - 4.0.0 - - io.helidon.microprofile.config - helidon-microprofile-config-project - 2.0-SNAPSHOT - - helidon-microprofile-config-cdi - Helidon Microprofile Config CDI - - - Microprofile config implementation - CDI extension - - - - - javax.enterprise - cdi-api - - provided - - - io.helidon.microprofile.config - helidon-microprofile-config - - - io.helidon.microprofile.bundles - internal-test-libs - test - - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - - - org.jboss.weld.se - weld-se-core - test - - - io.helidon.microprofile.config - helidon-microprofile-config - tests - ${project.version} - test-jar - test - - - diff --git a/microprofile/config/config/pom.xml b/microprofile/config/config/pom.xml deleted file mode 100644 index 795bfb02b..000000000 --- a/microprofile/config/config/pom.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - 4.0.0 - - io.helidon.microprofile.config - helidon-microprofile-config-project - 2.0-SNAPSHOT - - helidon-microprofile-config - Helidon Microprofile Config - - - Microprofile config implementation - core (without CDI) - - - - - org.eclipse.microprofile.config - microprofile-config-api - - - io.helidon.bundles - helidon-bundles-config - - - io.helidon.config - helidon-config-encryption - - - io.helidon.microprofile.bundles - internal-test-libs - test - - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - unmapped-env-value - mapped-env-value - <_underscore>mapped-env-value - mapped-env-value - mapped-env-value - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfig.java b/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfig.java deleted file mode 100644 index 9ccdd48cb..000000000 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfig.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 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.microprofile.config; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import io.helidon.config.Config; -import io.helidon.config.ConfigMappingException; -import io.helidon.config.ConfigSources; -import io.helidon.config.MissingValueException; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.Converter; - -/** - * Microprofile config wrapper of {@link Config}. - */ -public final class MpConfig implements org.eclipse.microprofile.config.Config { - private static final Pattern SPLIT_PATTERN = Pattern.compile("(? config; - private final List mpConfigSources; - private final Iterable propertyNames; - private final Map, Converter> converters; - private final AtomicReference helidonConverter; - - /** - * Create a new instance. - * @param config configuration - * @param mpConfigSources config sources - * @param converters class to converter mapping - */ - MpConfig(Config config, - List mpConfigSources, - Map, Converter> converters) { - - final AtomicReference ref = new AtomicReference<>(config); - config.onChange(ref::set); - - this.config = ref::get; - - this.mpConfigSources = mpConfigSources; - - this.propertyNames = - Stream.concat(mpConfigSources.stream() - .flatMap(cs -> cs.getPropertyNames().stream()), - config.traverse() - .filter(Config::isLeaf) - .map(Config::key) - .map(Config.Key::toString)) - .collect(Collectors.toSet()); - - this.converters = converters; - this.helidonConverter = new AtomicReference<>(); - } - - /** - * Get a builder for config instances. - * - * @return a new builder - */ - public static MpConfigBuilder builder() { - return new MpConfigBuilder(); - } - - static String[] toArray(String stringValue) { - String[] values = SPLIT_PATTERN.split(stringValue, -1); - - for (int i = 0; i < values.length; i++) { - String value = values[i]; - values[i] = value.replace("\\,", ","); - } - return values; - } - - @SuppressWarnings("unchecked") - @Override - public T getValue(String propertyName, Class propertyType) { - try { - if (propertyType.isArray()) { - Class element = propertyType.getComponentType(); - return (T) findArrayValue(propertyName, element); - } - - return findValue(propertyName, propertyType); - } catch (MissingValueException e) { - throw new NoSuchElementException(e.getMessage()); - } catch (ConfigMappingException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Return value as a {@link Set} of values. - * - * @param propertyName name of property - * @param typeArg type of elements in the set - * @param type of elements - * @return set with elements found in properties - */ - public Set asSet(String propertyName, Class typeArg) { - return new HashSet<>(asList(propertyName, typeArg)); - } - - /** - * Return value as a {@link List} of values. - * - * @param propertyName name of property - * @param typeArg type of elements in the list - * @param type of elements - * @return list with elements found in properties - */ - public List asList(String propertyName, Class typeArg) { - if (typeArg == Config.class) { - return config.get().get(propertyName).asList(typeArg).get(); - } - return findInMpSources(propertyName) - .map(value -> toList(value, typeArg)) - .orElseGet(() -> config.get().get(propertyName).asList(typeArg).get()); - } - - private List toList(final String value, final Class elementType) { - final String[] valueArray = toArray(value); - final List result = new ArrayList<>(); - for (final String element : valueArray) { - result.add(convert(elementType, element)); - } - return result; - } - - T findValue(String propertyName, Class propertyType) { - if (propertyType == Config.class) { - return config.get().get(propertyName).as(propertyType).get(); - } - //first iterate over mp sources, than use config - return findInMpSources(propertyName) - .map(value -> convert(propertyType, value)) - .orElseGet(() -> config.get().get(propertyName).as(propertyType).get()); - } - - private Object findArrayValue(String propertyName, Class element) { - // there should not be io.helidon.Config[] - return findInMpSources(propertyName) - .map(value -> asArray(value, element)) - .orElseGet(() -> { - Config arrayConfig = config.get().get(propertyName); - if (arrayConfig.isLeaf()) { - return asArray(arrayConfig.asString().get(), element); - } - List objects = arrayConfig.asList(element).get(); - Object array = Array.newInstance(element, objects.size()); - for (int i = 0; i < objects.size(); i++) { - Array.set(array, i, objects.get(i)); - } - - return array; - }); - } - - private Object asArray(String value, Class element) { - String[] valueArray = toArray(value); - - Object array = Array.newInstance(element, valueArray.length); - for (int i = 0; i < valueArray.length; i++) { - Array.set(array, i, convert(element, valueArray[i])); - } - - return array; - } - - private Optional findInMpSources(String propertyName) { - String propertyValue = null; - for (ConfigSource source : mpConfigSources) { - propertyValue = source.getValue(propertyName); - if (null != propertyValue) { - break; - } - } - - return Optional.ofNullable(propertyValue); - } - - /** - * Get value with a default if it does not exist. - * - * @param propertyName name of the property - * @param propertyType type of the property - * @param defaultValue default value correctly typed - * @param type of the property - * @return value from configuration or default value if not available - */ - public T value(String propertyName, Class propertyType, T defaultValue) { - return getOptionalValue(propertyName, propertyType).orElse(defaultValue); - } - - /** - * Get value with a default if it does not exist. - * - * @param propertyName name of the property - * @param propertyType type of the property - * @param defaultValue default value as String - * @param type of the property - * @return value from configuration or default value coerced to correct type if not available - */ - public T valueWithDefault(String propertyName, Class propertyType, String defaultValue) { - return getOptionalValue(propertyName, propertyType).orElse(convert(propertyType, defaultValue)); - } - - @Override - public Optional getOptionalValue(String propertyName, Class propertyType) { - try { - return Optional.of(getValue(propertyName, propertyType)); - } catch (NoSuchElementException e) { - return Optional.empty(); - } catch (ConfigMappingException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public Iterable getPropertyNames() { - return propertyNames; - } - - @Override - public Iterable getConfigSources() { - return mpConfigSources; - } - - /** - * Try to coerce the value to the specific type. - * - * @param type type to return - * @param value string value to parse - * @param type we expect (e.g. String, Integer or a java bean) - * @return null if value is null, transformed object otherwise - * @throws IllegalArgumentException if the value cannot be converted to the specified type using. - */ - @SuppressWarnings("unchecked") - public T convert(Class type, String value) { - if (null == value) { - return null; - } - - // Use a local converter for this class if we have one - final Converter converter = converters.get(type); - if (null != converter) { - return (T) converter.convert(value); - } else { - // If the request is for a String, we're done - if (type == String.class) { - return (T) value; - } else if (type.isArray()) { - // Recurse - return (T) asArray(value, type.getComponentType()); - } else { - // Ask helidon config to do appropriate conversion (built-in, implicit and classpath mappers) - final Config c = helidonConverter(); - try { - return c.convert(type, value); - } catch (ConfigMappingException e) { - throw new IllegalArgumentException("Failed to convert " + value + " to " + type.getName(), e); - } - } - } - } - - private Config helidonConverter() { - Config converter = helidonConverter.get(); - if (converter == null) { - converter = Config.builder() - .disableSystemPropertiesSource() - .disableFilterServices() - .disableEnvironmentVariablesSource() - .sources(ConfigSources.empty()) - .build(); - helidonConverter.set(converter); - } - return converter; - } - - /** - * Get an instance of Helidon config (a tree structure) rather than the microprofile config. - * - * @return config instance that has the same properties as this instance - */ - public Config helidonConfig() { - return this.config.get(); - } - - /** - * Get all properties of this config as a map. - * - * @return map where keys are configuration keys and values are associated string values - */ - public Map asMap() { - // config from helidon config instance - Map map = new HashMap<>(config.get().asMap().get()); - // now add all properties from sources of MP config - List configSources = new ArrayList<>(mpConfigSources); - Collections.reverse(configSources); - for (ConfigSource configSource : configSources) { - map.putAll(configSource.getProperties()); - } - return map; - } - - @Override - public String toString() { - return "microprofileConfig: " + config; - } -} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigBuilder.java b/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigBuilder.java deleted file mode 100644 index 62ab3ec17..000000000 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpConfigBuilder.java +++ /dev/null @@ -1,383 +0,0 @@ -/* - * 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.microprofile.config; - -import java.io.IOException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.URL; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import javax.annotation.Priority; - -import io.helidon.common.reactive.Flow; -import io.helidon.config.ConfigException; -import io.helidon.config.ConfigMappers; -import io.helidon.config.ConfigSources; -import io.helidon.config.spi.ConfigContext; -import io.helidon.config.spi.ConfigNode; - -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.ConfigSourceProvider; -import org.eclipse.microprofile.config.spi.Converter; - -/** - * Configuration builder. - */ -public class MpConfigBuilder implements ConfigBuilder { - private static final Logger LOGGER = Logger.getLogger(MpConfigBuilder.class.getName()); - private static final int DEFAULT_ORDINAL = 100; - private static final int BUILT_IN_ORDINAL = 1; - private static final List> BUILT_IN_CONVERTERS = initBuiltInConverters(); - - private final List mpConfigSources = new LinkedList<>(); - private final List> converters = new LinkedList<>(); - private final List helidonConfigSources = new LinkedList<>(); - private final io.helidon.config.Config.Builder helidonConfigBuilder = io.helidon.config.Config.builder(); - - private ClassLoader classLoader; - private io.helidon.config.Config helidonConfig; - - MpConfigBuilder() { - helidonConfigBuilder.disableEnvironmentVariablesSource(); - helidonConfigBuilder.disableSystemPropertiesSource(); - converters.addAll(BUILT_IN_CONVERTERS); - } - - private static List> initBuiltInConverters() { - List> result = new LinkedList<>(); - - result.add(new OrdinalConverter<>(boolean.class, MpConfigBuilder::toBoolean, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Boolean.class, MpConfigBuilder::toBoolean, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(byte.class, Byte::parseByte, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Byte.class, Byte::parseByte, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(short.class, Short::parseShort, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Short.class, Short::parseShort, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(int.class, Integer::parseInt, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Integer.class, Integer::parseInt, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(long.class, Long::parseLong, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Long.class, Long::parseLong, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(float.class, Float::parseFloat, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Float.class, Float::parseFloat, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(double.class, Double::parseDouble, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Double.class, Double::parseDouble, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(char.class, ConfigMappers::toChar, BUILT_IN_ORDINAL)); - result.add(new OrdinalConverter<>(Character.class, ConfigMappers::toChar, BUILT_IN_ORDINAL)); - - result.add(new OrdinalConverter<>(Class.class, ConfigMappers::toClass, BUILT_IN_ORDINAL)); - - return result; - } - - private static boolean toBoolean(final String value) { - final String lower = value.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; - } - } - - @Override - public ConfigBuilder addDefaultSources() { - // system properties - mpConfigSources.add(new MpcSourceSystemProperties()); - helidonConfigSources.add(ConfigSources.systemProperties()); - - // environment variables - mpConfigSources.add(new MpcSourceEnvironmentVariables()); - helidonConfigSources.add(ConfigSources.environmentVariables()); - - // /META-INF/microprofile-config.properties - try { - Enumeration resources = getClassLoader().getResources("META-INF/microprofile-config.properties"); - - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - mpConfigSources.add(MpcSourceUrl.create(url)); - helidonConfigSources.add(ConfigSources.url(url).build()); - } - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to read microprofile-config.properties", e); - } - - // application.yaml - helidon default - io.helidon.config.spi.ConfigSource cs = ConfigSources.classpath("application.yaml").optional().build(); - helidonConfigSources.add(cs); - cs = ConfigSources.file("application.yaml").optional().build(); - helidonConfigSources.add(cs); - - return this; - } - - @Override - public ConfigBuilder addDiscoveredSources() { - ServiceLoader.load(ConfigSource.class, getClassLoader()) - .forEach(this::addConfigSource); - - ServiceLoader.load(ConfigSourceProvider.class, getClassLoader()) - .forEach(csp -> csp.getConfigSources(getClassLoader()) - .forEach(this::addConfigSource)); - - return this; - } - - @Override - public ConfigBuilder addDiscoveredConverters() { - ServiceLoader.load(Converter.class, getClassLoader()) - .forEach(this::addConverter); - - return this; - } - - @Override - public ConfigBuilder forClassLoader(ClassLoader loader) { - this.classLoader = loader; - - return this; - } - - @Override - public ConfigBuilder withSources(ConfigSource... sources) { - for (ConfigSource source : sources) { - addConfigSource(source); - } - - return this; - } - - @Override - public ConfigBuilder withConverter(Class aClass, int ordinal, Converter converter) { - converters.add(new OrdinalConverter<>(aClass, - converter, - ordinal)); - return this; - } - - /** - * Set the Helidon config to be used as a "backend" for this MP config. - * - * @param config config instance to query if MP sources do not contain the key - * @return modified builder - */ - public MpConfigBuilder config(io.helidon.config.Config config) { - this.helidonConfig = config; - return this; - } - - private void addConfigSource(ConfigSource source) { - LOGGER.finest(() -> "Adding config source: " + source.getName() + " (" + source.getClass() - .getName() + "), values: " + source.getProperties()); - - mpConfigSources.add(source); - helidonConfigSources.add(wrapSource(source)); - } - - @Override - public ConfigBuilder withConverters(Converter... converters) { - for (Converter converter : converters) { - addConverter(converter); - } - return this; - } - - @Override - public Config build() { - orderLists(); - - Map, Function> configMappers = new IdentityHashMap<>(); - - converters.forEach(oc -> { - final Class type = oc.type; - Function mapper = config -> oc.converter.convert(config.asString().get()); - configMappers.put(type, mapper); - }); - - if (null == helidonConfig) { - // only helidon config sources - helidonConfigBuilder.sources(toSupplierList(helidonConfigSources)); - helidonConfigBuilder.addMapper(() -> configMappers); - - helidonConfig = helidonConfigBuilder.build(); - } - - Map, Converter> converterMap = new HashMap<>(); - for (OrdinalConverter converter : this.converters) { - converterMap.put(converter.type, converter.converter); - } - - return new MpConfig(helidonConfig, mpConfigSources, converterMap); - } - - private void orderLists() { - - // Order lowest to highest so higher takes precedence when map is built - mpConfigSources.sort(Comparator.comparingInt(ConfigSource::getOrdinal)); - - // Order highest to lowest - converters.sort(Comparator.comparingInt(OrdinalConverter::getOrdinal)); - Collections.reverse(mpConfigSources); - } - - private List> - toSupplierList(List configSources) { - - return configSources.stream() - .map(cs -> (Supplier) () -> cs) - .collect(Collectors.toList()); - - } - - private ClassLoader getClassLoader() { - return (null == classLoader ? Thread.currentThread().getContextClassLoader() : classLoader); - } - - @SuppressWarnings("unchecked") - private void addConverter(Converter converter) { - Class type = (Class) getTypeOfConverter(converter.getClass()); - if (type == null) { - throw new IllegalStateException("Converter " + converter.getClass() + " must be a ParameterizedType"); - } - - LOGGER.finest(() -> "Adding converter for type: " + type.getName() + " (" + converter.getClass().getName() + ")"); - - converters.add(new OrdinalConverter<>(type, - converter, - findPriority(converter.getClass()))); - } - - private int findPriority(Class aClass) { - Priority priorityAnnot = aClass.getAnnotation(Priority.class); - if (null != priorityAnnot) { - return priorityAnnot.value(); - } - - return DEFAULT_ORDINAL; - } - - private Type getTypeOfConverter(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 getTypeOfConverter(clazz.getSuperclass()); - } - - private OrdinalConfigSource wrapSource(ConfigSource source) { - io.helidon.config.spi.ConfigSource myCs = ConfigSources.create(source.getProperties()).build(); - - return new OrdinalConfigSource(myCs, source.getOrdinal()); - } - - private static class OrdinalConverter { - private final Class type; - private final Converter converter; - private final int ordinal; - - OrdinalConverter(Class type, Converter converter, int ordinal) { - this.type = type; - this.converter = converter; - this.ordinal = ordinal; - } - - int getOrdinal() { - return ordinal; - } - } - - private static final class OrdinalConfigSource implements io.helidon.config.spi.ConfigSource { - private final io.helidon.config.spi.ConfigSource configSource; - private final int ordinal; - - private OrdinalConfigSource(io.helidon.config.spi.ConfigSource configSource, int ordinal) { - this.configSource = configSource; - this.ordinal = ordinal; - } - - @Override - public Optional load() throws ConfigException { - return configSource.load(); - } - - @Override - public io.helidon.config.spi.ConfigSource get() { - return configSource.get(); - } - - @Override - public void init(ConfigContext context) { - configSource.init(context); - } - - @Override - public String description() { - return configSource.description(); - } - - @Override - public Flow.Publisher> changes() { - return configSource.changes(); - } - - int getOrdinal() { - return ordinal; - } - } -} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariables.java b/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariables.java deleted file mode 100644 index b1277dcd8..000000000 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariables.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.microprofile.config; - -import java.util.Map; -import java.util.Set; - -import io.helidon.config.EnvironmentVariableAliases; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -import static java.lang.System.getenv; - -/** - * Environment variables config source. - */ -class MpcSourceEnvironmentVariables implements ConfigSource { - - MpcSourceEnvironmentVariables() { - } - - @Override - public Map getProperties() { - return getenv(); - } - - @Override - public String getValue(final String propertyName) { - String result = getenv(propertyName); - if (result == null) { - for (final String alias : EnvironmentVariableAliases.aliasesOf(propertyName)) { - result = getenv(alias); - if (result != null) { - break; - } - } - } - return result; - } - - @Override - public String getName() { - return "helidon:environment-variables"; - } - - @Override - public int getOrdinal() { - return 300; - } - - @Override - public Set getPropertyNames() { - return getProperties().keySet(); - } -} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceSystemProperties.java b/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceSystemProperties.java deleted file mode 100644 index d33ee4afb..000000000 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceSystemProperties.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 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.microprofile.config; - -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -/** - * System properties config source. - */ -class MpcSourceSystemProperties implements ConfigSource { - static Map toMap(Properties properties) { - Map result = new HashMap<>(); - - for (String name : properties.stringPropertyNames()) { - result.put(name, properties.getProperty(name)); - } - - return result; - } - - @Override - public Map getProperties() { - return toMap(System.getProperties()); - } - - @Override - public String getValue(String propertyName) { - return System.getProperty(propertyName); - } - - @Override - public String getName() { - return "helidon:system-properties"; - } - - @Override - public int getOrdinal() { - return 400; - } - - @Override - public Set getPropertyNames() { - return System.getProperties().stringPropertyNames(); - } -} diff --git a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceUrl.java b/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceUrl.java deleted file mode 100644 index fd361693f..000000000 --- a/microprofile/config/config/src/main/java/io/helidon/microprofile/config/MpcSourceUrl.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 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.microprofile.config; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Collections; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -/** - * URL based config source. - */ -final class MpcSourceUrl implements ConfigSource { - - private final Map props; - private final String source; - - private MpcSourceUrl(Properties props, String source) { - this.props = MpcSourceSystemProperties.toMap(props); - this.source = source; - } - - public static ConfigSource create(URL url) throws IOException { - try (InputStream inputStream = url.openStream()) { - Properties props = new Properties(); - props.load(inputStream); - return new MpcSourceUrl(props, url.toString()); - } - } - - @Override - public Map getProperties() { - return Collections.unmodifiableMap(props); - } - - @Override - public String getValue(String propertyName) { - return props.get(propertyName); - } - - @Override - public String getName() { - return "helidon:url:" + source; - } - - @Override - public Set getPropertyNames() { - return Collections.unmodifiableSet(props.keySet()); - } -} diff --git a/microprofile/config/config/src/main/java9/module-info.java b/microprofile/config/config/src/main/java9/module-info.java deleted file mode 100644 index 4f4278ae0..000000000 --- a/microprofile/config/config/src/main/java9/module-info.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -/** - * Microprofile configuration module. - */ -module io.helidon.microprofile.config { - requires java.logging; - - requires transitive io.helidon.config; - requires transitive microprofile.config.api; - requires io.helidon.config.encryption; - - exports io.helidon.microprofile.config; - - provides org.eclipse.microprofile.config.spi.ConfigProviderResolver with io.helidon.microprofile.config.MpConfigProviderResolver; - - uses org.eclipse.microprofile.config.spi.ConfigSource; - uses org.eclipse.microprofile.config.spi.ConfigSourceProvider; - uses org.eclipse.microprofile.config.spi.Converter; -} diff --git a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpConfigTest.java b/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpConfigTest.java deleted file mode 100644 index 4c4c3032b..000000000 --- a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpConfigTest.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.microprofile.config; - -import java.time.YearMonth; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - -import io.helidon.common.CollectionsHelper; -import io.helidon.config.MissingValueException; -import io.helidon.microprofile.config.Converters.Ctor; -import io.helidon.microprofile.config.Converters.Of; -import io.helidon.microprofile.config.Converters.Parse; -import io.helidon.microprofile.config.Converters.ValueOf; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.hasItems; -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.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -/** - * Unit test for {@link MpConfig}. - */ -class MpConfigTest { - private static MpConfig mpConfig; - - @BeforeAll - static void initClass() { - Map properties = CollectionsHelper.mapOf( - "boolean", "true", - "array", "a,b,c,d", - "int", "14" - ); - - ConfigSource myConfigSource = new ConfigSource() { - @Override - public Map getProperties() { - return properties; - } - - @Override - public String getValue(String propertyName) { - return properties.get(propertyName); - } - - @Override - public String getName() { - return "helidon:unit-test"; - } - }; - - mpConfig = (MpConfig) new MpConfigBuilder() - .addDefaultSources() - .withSources(myConfigSource) - .build(); - } - - @Test - void testBooleans() { - MpConfig mpConfig = (MpConfig) new MpConfigBuilder() - .build(); - - assertAll("Boolean conversions", - () -> assertThat("true", mpConfig.convert(Boolean.class, "true"), is(true)), - () -> assertThat("false", mpConfig.convert(Boolean.class, "false"), is(false)), - () -> assertThat("true, primitive", mpConfig.convert(boolean.class, "true"), is(true)), - () -> assertThat("false, primitive", mpConfig.convert(boolean.class, "false"), is(false)), - () -> assertThat("on", mpConfig.convert(boolean.class, "on"), is(true)), - () -> assertThat("1", mpConfig.convert(boolean.class, "1"), is(true)), - () -> assertThat("true, array", mpConfig.convert(Boolean[].class, "true, false"), - arrayContaining(true, false)) - ); - } - - @Test - void testStringTypes() { - assertAll("Various property types", - () -> assertThat("As string list", - mpConfig.asList("array", String.class), - hasItems("a", "b", "c", "d")), - () -> assertThat("As string array", - mpConfig.getValue("array", String[].class), - arrayContaining("a", "b", "c", "d")), - () -> assertThat("As string array", - mpConfig.convert(String[].class, "a,b\\,,c,d"), - arrayContaining("a", "b,", "c", "d")), - () -> assertThat("As set", - mpConfig.asSet("array", String.class), - hasItems("a", "b", "c", "d")), - () -> assertThat("As string", - mpConfig.getValue("array", String.class), - is("a,b,c,d")) - ); - } - - @Test - void testSystemPropertyAndEnvironmentVariableNamesArePresent() { - final Iterable propertyNames = mpConfig.getPropertyNames(); - assertThat("Set", propertyNames, instanceOf(Set.class)); - assertThat("System properties", ((Set) propertyNames).contains("java.home")); - assumeTrue(System.getenv("PATH") != null); - assertThat("Environment variables", ((Set) propertyNames).contains("PATH")); - } - - @Test - public void testGetValueOfNonExistentValueShouldThrowNoSuchElementException() { - assertThrows(NoSuchElementException.class, () -> mpConfig.getValue("nonexistent.property", String.class)); - } - - @Test - public void testFindValueOfNonExistentValueShouldThrowMissingValueException() { - assertThrows(MissingValueException.class, () -> mpConfig.findValue("nonexistent.property", String.class)); - } - - @Test - public void testImplicitConversion() { - MpConfig mpConfig = (MpConfig) new MpConfigBuilder() - .build(); - - assertAll("Implicit conversions", - () -> assertThat("of", mpConfig.convert(Of.class, "foo"), is(Of.of("foo"))), - () -> assertThat("valueOf", mpConfig.convert(ValueOf.class, "foo"), is(ValueOf.valueOf("foo"))), - () -> assertThat("parse", mpConfig.convert(Parse.class, "foo"), is(Parse.parse("foo"))), - () -> assertThat("ctor", mpConfig.convert(Ctor.class, "foo"), is(new Ctor("foo"))), - () -> assertThat("year month", mpConfig.convert(YearMonth.class, "2019-03"), is(YearMonth.parse("2019-03"))) - ); - } -} diff --git a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariablesTest.java b/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariablesTest.java deleted file mode 100644 index efd6bdcf2..000000000 --- a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/MpcSourceEnvironmentVariablesTest.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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.microprofile.config; - -import java.util.Map; -import java.util.NoSuchElementException; - -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static io.helidon.common.CollectionsHelper.mapOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Unit test for {@link MpcSourceEnvironmentVariables}. - */ -@ExtendWith(RestoreSystemPropertiesExt.class) -public class MpcSourceEnvironmentVariablesTest { - private static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().contains("win"); - - @Test - public void testEnvironmentVariableAliases() { - MpConfig config = (MpConfig) MpConfig.builder() - .withSources(new MpcSourceEnvironmentVariables()) - .build(); - - assertValue("simple", "unmapped-env-value", config); - - assertValue("_underscore", "mapped-env-value", config); - assertValue("/underscore", "mapped-env-value", config); - - assertValue("FOO_BAR", "mapped-env-value", config); - assertValue("foo.bar", "mapped-env-value", config); - assertValue("foo/bar", "mapped-env-value", config); - assertValue("foo#bar", "mapped-env-value", config); - - assertValue("com_ACME_size", "mapped-env-value", config); - assertValue("com.ACME.size", "mapped-env-value", config); - assertValue("com!ACME@size", "mapped-env-value", config); - assertValue("com#ACME$size", "mapped-env-value", config); - assertValue("com/ACME/size", "mapped-env-value", config); - - assertValue("SERVER_EXECUTOR_dash_SERVICE_MAX_dash_POOL_dash_SIZE", "mapped-env-value", config); - assertValue("SERVER.EXECUTOR-SERVICE.MAX-POOL-SIZE", "mapped-env-value", config); - assertValue("server.executor-service.max-pool-size", "mapped-env-value", config); - assertValue("SERVER_EXECUTOR_dash_SERVICE_MAX_dash_POOL_dash_SIZE", "mapped-env-value", config); - } - - @Test - public void testPrecedence() { - - // NOTE: This code should be kept in sync with ConfigSourcesTest.testPrecedence(), as we want SE and MP to be as - // symmetrical as possible. - - System.setProperty("com.ACME.size", "sys-prop-value"); - - final ConfigSource appSource = toConfigSource(mapOf("app.key", "app-value", - "com.ACME.size", "app-value", - "server.executor-service.max-pool-size", "app-value")); - // Application source only. - // Key mapping should NOT occur. - - MpConfig appOnly = (MpConfig) MpConfig.builder() - .withSources(appSource) - .build(); - - assertValue("app.key", "app-value", appOnly); - assertValue("com.ACME.size", "app-value", appOnly); - assertValue("server.executor-service.max-pool-size", "app-value", appOnly); - - assertNoValue("com.acme.size", appOnly); - assertNoValue("com/ACME/size", appOnly); - assertNoValue("server/executor-service/max-pool-size", appOnly); - - - // Application and system property sources. - // System properties should take precedence over application values. - // Key mapping should NOT occur. - - MpConfig appAndSys = (MpConfig) MpConfig.builder() - .withSources(appSource) - .withSources(new MpcSourceSystemProperties()) - .build(); - - assertValue("app.key", "app-value", appAndSys); - assertValue("com.ACME.size", "sys-prop-value", appAndSys); - assertValue("server.executor-service.max-pool-size", "app-value", appAndSys); - - assertNoValue("com.acme.size", appOnly); - assertNoValue("com/ACME/size", appAndSys); - assertNoValue("server/executor-service/max-pool-size", appAndSys); - - - // Application and environment variable sources. - // Environment variables should take precedence over application values. - // Key mapping SHOULD occur. - - MpConfig appAndEnv = (MpConfig) MpConfig.builder() - .withSources(appSource) - .withSources(new MpcSourceEnvironmentVariables()) - .build(); - - assertValue("app.key", "app-value", appAndEnv); - assertValue("com.ACME.size", "mapped-env-value", appAndEnv); - assertValue("server.executor-service.max-pool-size", "mapped-env-value", appAndEnv); - - assertNoValueExceptOnWindows("com.acme.size", "mapped-env-value", appAndEnv); - assertValue("com/ACME/size", "mapped-env-value", appAndEnv); - assertValue("server/executor-service/max-pool-size", "mapped-env-value", appAndEnv); - - - // Application, system property and environment variable sources. - // System properties should take precedence over environment variables. - // Environment variables should take precedence over application values. - // Key mapping SHOULD occur. - - MpConfig appSysAndEnv = (MpConfig) MpConfig.builder() - .withSources(appSource) - .withSources(new MpcSourceEnvironmentVariables()) - .withSources(new MpcSourceSystemProperties()) - .build(); - - assertValue("app.key", "app-value", appSysAndEnv); - assertValue("com.ACME.size", "sys-prop-value", appSysAndEnv); - assertValue("server.executor-service.max-pool-size", "mapped-env-value", appSysAndEnv); - - assertNoValueExceptOnWindows("com.acme.size", "mapped-env-value", appAndEnv); - assertValue("com/ACME/size", "mapped-env-value", appSysAndEnv); - assertValue("server/executor-service/max-pool-size", "mapped-env-value", appSysAndEnv); - } - - static void assertValue(final String key, final String expectedValue, final MpConfig config) { - assertThat(config.getValue(key, String.class), is(expectedValue)); - } - - static void assertNoValue(final String key, final MpConfig config) { - assertThrows(NoSuchElementException.class, () -> config.getValue(key, String.class)); - } - - static void assertNoValueExceptOnWindows(final String key, final String expectedValueOnWindows, final MpConfig config) { - if (WINDOWS) { - assertValue(key, expectedValueOnWindows, config); - } else { - assertThrows(NoSuchElementException.class, () -> config.getValue(key, String.class)); - } - } - - static ConfigSource toConfigSource(final Map map) { - return new ConfigSource() { - @Override - public Map getProperties() { - return map; - } - - @Override - public String getValue(String propertyName) { - return map.get(propertyName); - } - - @Override - public String getName() { - return "helidon:unit-test"; - } - }; - } -} diff --git a/microprofile/config/pom.xml b/microprofile/config/pom.xml index 1c3717d0c..be15a66f4 100644 --- a/microprofile/config/pom.xml +++ b/microprofile/config/pom.xml @@ -26,15 +26,48 @@ helidon-microprofile-project 2.0-SNAPSHOT - pom io.helidon.microprofile.config - helidon-microprofile-config-project + helidon-microprofile-config Helidon Microprofile Config - Microprofile config implementation Project + + Microprofile config implementation. + - - config-cdi - config - + + + io.helidon.config + helidon-config + + + org.eclipse.microprofile.config + microprofile-config-api + + + io.helidon.config + helidon-config-object-mapping + + + javax.enterprise + cdi-api + + provided + + + io.helidon.microprofile.bundles + internal-test-libs + test + + + io.helidon.config + helidon-config-test-infrastructure + ${project.version} + test + + + org.jboss.weld.se + weld-se-core + test + + diff --git a/microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/ConfigCdiExtension.java b/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java similarity index 88% rename from microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/ConfigCdiExtension.java rename to microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java index 47f51080d..6095afe03 100644 --- a/microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/ConfigCdiExtension.java +++ b/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.helidon.microprofile.config.cdi; +package io.helidon.microprofile.config; import java.io.IOException; import java.io.ObjectInputStream; @@ -66,7 +66,6 @@ import io.helidon.common.CollectionsHelper; import io.helidon.config.Config; import io.helidon.config.ConfigException; import io.helidon.config.MissingValueException; -import io.helidon.microprofile.config.MpConfig; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -171,7 +170,7 @@ public class ConfigCdiExtension implements Extension { if (annotatedParameters != null && annotatedParameters.size() > 1) { for (AnnotatedParameter annotatedParameter : annotatedParameters) { if (annotatedParameter != null - && !annotatedParameter.isAnnotationPresent(Observes.class)) { + && !annotatedParameter.isAnnotationPresent(Observes.class)) { InjectionPoint injectionPoint = beanManager.createInjectionPoint(annotatedParameter); Type type = injectionPoint.getType(); Set qualifiers = injectionPoint.getQualifiers(); @@ -227,13 +226,11 @@ public class ConfigCdiExtension implements Extension { // we also must support injection of Config itself abd.addBean() .addType(Config.class) - .createWith(creationalContext -> ((MpConfig) ConfigProvider.getConfig()).helidonConfig()); + .createWith(creationalContext -> (Config) ConfigProvider.getConfig()); abd.addBean() .addType(org.eclipse.microprofile.config.Config.class) - .createWith(creationalContext -> { - return new SerializableConfig(); - }); + .createWith(creationalContext -> new SerializableConfig()); } /** @@ -258,8 +255,8 @@ public class ConfigCdiExtension implements Extension { Object configValue = beanManager.getInjectableReference(new InjectionPointTemplate(ipc.type, ipc.qualifier), cc); VALUE_LOGGER.finest(() -> "Config value for " + ipc.qualifier.key() - + " (" + ipc.qualifier.fullPath() + "), is " - + configValue); + + " (" + ipc.qualifier.fullPath() + "), is " + + configValue); } catch (Exception e) { add.addDeploymentProblem(e); } @@ -384,8 +381,12 @@ public class ConfigCdiExtension implements Extension { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } IpConfig ipConfig = (IpConfig) o; return Objects.equals(qualifier, ipConfig.qualifier) && Objects.equals(type, ipConfig.type); } @@ -394,6 +395,11 @@ public class ConfigCdiExtension implements Extension { public int hashCode() { return Objects.hash(qualifier, type); } + + @Override + public String toString() { + return qualifier + " (" + type + ")"; + } } /** @@ -440,17 +446,28 @@ public class ConfigCdiExtension implements Extension { this.type = actualType; } - private static Object getConfigValue(MpConfig config, ConfigQualifier q) { + @SuppressWarnings({"CheckStyle", "ObjectEquality", "unchecked"}) + private static Object getConfigValue(Config helidonConfig, ConfigQualifier q) { try { String configKey = q.key().isEmpty() ? q.fullPath().replace('$', '.') : q.key(); String defaultValue = ConfigProperty.UNCONFIGURED_VALUE.equals(q.defaultValue()) ? null : q.defaultValue(); if (q.rawType() == q.typeArg()) { // not a generic - return config.valueWithDefault(configKey, q.rawType(), defaultValue); + Optional configValue = ((org.eclipse.microprofile.config.Config) helidonConfig) + .getOptionalValue(configKey, (Class) q.rawType()); + + return configValue + .orElseGet(() -> { + if (null == defaultValue) { + return null; + } + return helidonConfig.get(configKey) + .convert(q.rawType(), defaultValue); + }); } // generic declaration - return getParameterizedConfigValue(config, + return getParameterizedConfigValue(helidonConfig, configKey, defaultValue, q.rawType(), @@ -458,25 +475,36 @@ public class ConfigCdiExtension implements Extension { q.typeArg2()); } catch (IllegalArgumentException e) { if (e.getCause() instanceof ConfigException) { - throw new DeploymentException("Config value for " + q.key() + "(" + q.fullPath() + ") is not defined"); + throw new DeploymentException("Config value for " + q.key() + "(" + q.fullPath() + ") is not defined", e); } else { throw e; } } } - private static Object getParameterizedConfigValue(MpConfig mpConfig, + @SuppressWarnings({"ObjectEquality", "unchecked", "rawtypes"}) + private static Object getParameterizedConfigValue(Config helidonConfig, String configKey, String defaultValue, Class rawType, Class typeArg, Class typeArg2) { + + org.eclipse.microprofile.config.Config mpConfig = (org.eclipse.microprofile.config.Config) helidonConfig; + if (Optional.class.isAssignableFrom(rawType)) { if (typeArg == typeArg2) { - return Optional.ofNullable(mpConfig.valueWithDefault(configKey, typeArg, defaultValue)); + Optional optionalValue = mpConfig.getOptionalValue(configKey, typeArg); + if (optionalValue.isPresent()) { + return optionalValue; + } + if (null == defaultValue) { + return Optional.empty(); + } + return helidonConfig.get(configKey).convert(typeArg, defaultValue); } else { return Optional - .ofNullable(getParameterizedConfigValue(mpConfig, + .ofNullable(getParameterizedConfigValue(helidonConfig, configKey, defaultValue, typeArg, @@ -485,7 +513,9 @@ public class ConfigCdiExtension implements Extension { } } else if (List.class.isAssignableFrom(rawType)) { try { - return mpConfig.asList(configKey, typeArg); + return helidonConfig.get(configKey) + .asList(typeArg) + .get(); } catch (MissingValueException e) { // if default if (null == defaultValue) { @@ -499,7 +529,7 @@ public class ConfigCdiExtension implements Extension { String[] values = defaultValue.split(","); for (String value : values) { - result.add(mpConfig.convert(typeArg, value)); + result.add(helidonConfig.convert(typeArg, value)); } return result; @@ -507,19 +537,27 @@ public class ConfigCdiExtension implements Extension { } } else if (Supplier.class.isAssignableFrom(rawType)) { if (typeArg == typeArg2) { - return (Supplier) () -> mpConfig.valueWithDefault(configKey, typeArg, defaultValue); + return (Supplier) () -> { + Optional opt = mpConfig.getOptionalValue(configKey, typeArg); + if (opt.isPresent()) { + return opt; + } + return helidonConfig.get(configKey) + .convert(typeArg, defaultValue); + }; } else { - return (Supplier) () -> getParameterizedConfigValue(mpConfig, - configKey, - defaultValue, - typeArg, - typeArg2, - typeArg2); + return (Supplier) () -> getParameterizedConfigValue(helidonConfig, + configKey, + defaultValue, + typeArg, + typeArg2, + typeArg2); } } else if (Map.class.isAssignableFrom(rawType)) { - return mpConfig.helidonConfig().get(configKey).detach().asMap(); + return helidonConfig.get(configKey).detach().asMap(); } else if (Set.class.isAssignableFrom(rawType)) { - return mpConfig.asSet(configKey, typeArg); + return new HashSet(helidonConfig.get(configKey).asList(typeArg) + .get()); } else { throw new DeploymentException("Cannot create config property for " + rawType + "<" + typeArg + ">, key: " + configKey); @@ -537,7 +575,7 @@ public class ConfigCdiExtension implements Extension { } private Object getConfigValue(CreationalContext context) { - return getConfigValue((MpConfig) ConfigProvider.getConfig(), qualifier); + return getConfigValue((Config) ConfigProvider.getConfig(), qualifier); } @Override @@ -713,7 +751,7 @@ public class ConfigCdiExtension implements Extension { } TypedField that = (TypedField) o; return Objects.equals(rawType, that.rawType) - && Objects.equals(paramType, that.paramType); + && Objects.equals(paramType, that.paramType); } @Override diff --git a/microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/package-info.java b/microprofile/config/src/main/java/io/helidon/microprofile/config/package-info.java similarity index 83% rename from microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/package-info.java rename to microprofile/config/src/main/java/io/helidon/microprofile/config/package-info.java index fbafbe183..3012b0c95 100644 --- a/microprofile/config/config-cdi/src/main/java/io/helidon/microprofile/config/cdi/package-info.java +++ b/microprofile/config/src/main/java/io/helidon/microprofile/config/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,4 +17,4 @@ /** * Helidon implementation of microprofile config. */ -package io.helidon.microprofile.config.cdi; +package io.helidon.microprofile.config; diff --git a/microprofile/config/config-cdi/src/main/java9/module-info.java b/microprofile/config/src/main/java9/module-info.java similarity index 77% rename from microprofile/config/config-cdi/src/main/java9/module-info.java rename to microprofile/config/src/main/java9/module-info.java index afc4f3486..1e44f584c 100644 --- a/microprofile/config/config-cdi/src/main/java9/module-info.java +++ b/microprofile/config/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,15 @@ /** * CDI extension for microprofile config implementation. */ -module io.helidon.microprofile.config.cdi { +module io.helidon.microprofile.config { requires java.logging; requires cdi.api; requires javax.inject; requires io.helidon.common; requires io.helidon.config; - requires io.helidon.microprofile.config; requires microprofile.config.api; - exports io.helidon.microprofile.config.cdi; + exports io.helidon.microprofile.config; - provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.config.cdi.ConfigCdiExtension; + provides javax.enterprise.inject.spi.Extension with io.helidon.microprofile.config.ConfigCdiExtension; } diff --git a/microprofile/config/config-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/microprofile/config/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension similarity index 81% rename from microprofile/config/config-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension rename to microprofile/config/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index 42a7b443a..c6295f897 100644 --- a/microprofile/config/config-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/microprofile/config/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -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. @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.microprofile.config.cdi.ConfigCdiExtension +io.helidon.microprofile.config.ConfigCdiExtension diff --git a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/Converters.java b/microprofile/config/src/test/java/io/helidon/microprofile/config/Converters.java similarity index 90% rename from microprofile/config/config/src/test/java/io/helidon/microprofile/config/Converters.java rename to microprofile/config/src/test/java/io/helidon/microprofile/config/Converters.java index 6614849fa..7c1cf0d74 100644 --- a/microprofile/config/config/src/test/java/io/helidon/microprofile/config/Converters.java +++ b/microprofile/config/src/test/java/io/helidon/microprofile/config/Converters.java @@ -26,6 +26,7 @@ public class Converters { public static class Of { private final String value; + // method to make sure the "of" is used to map to this instance public static Of of(final String value) { return new Of(value); } @@ -46,6 +47,7 @@ public class Converters { public static class ValueOf { private final String value; + // method to make sure the "valueOf" is used to map to this instance public static ValueOf valueOf(final String value) { return new ValueOf(value); } @@ -66,6 +68,7 @@ public class Converters { public static class Parse { private final String value; + // method to make sure the "parse" is used to map to this instance public static Parse parse(final String value) { return new Parse(value); } @@ -86,6 +89,7 @@ public class Converters { public static class Ctor { private final String value; + // method to make sure the constructor is used to map to this instance public Ctor(final String value) { this.value = requireNonNull(value); } diff --git a/microprofile/config/config-cdi/src/test/java/io/helidon/microprofile/config/cdi/MpConfigInjectionTest.java b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java similarity index 95% rename from microprofile/config/config-cdi/src/test/java/io/helidon/microprofile/config/cdi/MpConfigInjectionTest.java rename to microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java index d9410cb6b..1b5679dbb 100644 --- a/microprofile/config/config-cdi/src/test/java/io/helidon/microprofile/config/cdi/MpConfigInjectionTest.java +++ b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package io.helidon.microprofile.config.cdi; +package io.helidon.microprofile.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import javax.enterprise.context.Dependent; import javax.enterprise.inject.se.SeContainer; @@ -23,14 +26,13 @@ import javax.enterprise.inject.spi.CDI; import javax.enterprise.util.AnnotationLiteral; import javax.inject.Inject; import javax.inject.Qualifier; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import io.helidon.microprofile.config.Converters.Ctor; import io.helidon.microprofile.config.Converters.Of; import io.helidon.microprofile.config.Converters.Parse; import io.helidon.microprofile.config.Converters.ValueOf; + import org.eclipse.microprofile.config.inject.ConfigProperty; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -49,7 +51,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; */ @ExtendWith(RestoreSystemPropertiesExt.class) class MpConfigInjectionTest { - private static SeContainer CONTAINER; + private static SeContainer container; @BeforeAll static void initClass() { @@ -65,12 +67,14 @@ class MpConfigInjectionTest { final SeContainerInitializer initializer = SeContainerInitializer.newInstance(); assertThat(initializer, is(notNullValue())); - CONTAINER = initializer.initialize(); + container = initializer.initialize(); } @AfterAll static void destroyClass() { - CONTAINER.close(); + if (null != container) { + container.close(); + } } @Test diff --git a/microprofile/config/config-cdi/src/test/resources/META-INF/beans.xml b/microprofile/config/src/test/resources/META-INF/beans.xml similarity index 100% rename from microprofile/config/config-cdi/src/test/resources/META-INF/beans.xml rename to microprofile/config/src/test/resources/META-INF/beans.xml diff --git a/microprofile/grpc/server/pom.xml b/microprofile/grpc/server/pom.xml index 4b04d9726..5e3c8a0cb 100644 --- a/microprofile/grpc/server/pom.xml +++ b/microprofile/grpc/server/pom.xml @@ -60,10 +60,6 @@ io.helidon.microprofile.config helidon-microprofile-config - - io.helidon.microprofile.config - helidon-microprofile-config-cdi - io.helidon.microprofile.server helidon-microprofile-server diff --git a/microprofile/jwt-auth/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java b/microprofile/jwt-auth/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java index 5410e8483..14b87df7a 100644 --- a/microprofile/jwt-auth/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java +++ b/microprofile/jwt-auth/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java @@ -46,7 +46,6 @@ import io.helidon.security.jwt.jwk.JwkEC; import io.helidon.security.jwt.jwk.JwkKeys; import io.helidon.security.jwt.jwk.JwkOctet; import io.helidon.security.jwt.jwk.JwkRSA; -import io.helidon.security.providers.jwt.JwtProvider; import org.eclipse.microprofile.auth.LoginConfig; import org.eclipse.microprofile.jwt.Claims; diff --git a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java index 4cde9053d..66e7439ec 100644 --- a/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java +++ b/microprofile/metrics/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java @@ -19,9 +19,7 @@ package io.helidon.microprofile.metrics; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; -import io.helidon.config.Config; import io.helidon.metrics.RegistryFactory; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; import org.eclipse.microprofile.metrics.Counter; @@ -47,14 +45,13 @@ public class MetricsMpServiceTest { public static void initializeServer() throws Exception { server = Server.builder() .addResourceClass(HelloWorldResource.class) - .config(MpConfig.builder().config(Config.create()).build()) .host("localhost") // choose a random available port .port(-1) .build(); server.start(); - registry = registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION); + registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION); port = server.port(); baseUri = "http://localhost:" + port; diff --git a/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java b/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java index a0daab5ce..7e5ed754d 100644 --- a/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java +++ b/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/MetricsMpServiceTest.java @@ -19,10 +19,8 @@ package io.helidon.microprofile.metrics; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; -import io.helidon.config.Config; import io.helidon.metrics.HelidonMetadata; import io.helidon.metrics.RegistryFactory; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; import org.eclipse.microprofile.metrics.Counter; @@ -48,7 +46,6 @@ public class MetricsMpServiceTest { public static void initializeServer() throws Exception { server = Server.builder() .addResourceClass(HelloWorldResource.class) - .config(MpConfig.builder().config(Config.create()).build()) .host("localhost") // choose a random available port .port(-1) diff --git a/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/ReusabilityMpServiceTest.java b/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/ReusabilityMpServiceTest.java index 505016b76..bedba168f 100644 --- a/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/ReusabilityMpServiceTest.java +++ b/microprofile/metrics2/src/test/java/io/helidon/microprofile/metrics/ReusabilityMpServiceTest.java @@ -16,17 +16,9 @@ */ package io.helidon.microprofile.metrics; -import io.helidon.config.Config; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -import org.jboss.weld.exceptions.DefinitionException; - import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; /** * @@ -36,7 +28,6 @@ public class ReusabilityMpServiceTest { private Server initServer(Class resourceClass) { return Server.builder() .addResourceClass(resourceClass) - .config(MpConfig.builder().config(Config.create()).build()) .host("localhost") // choose a random available port .port(-1) diff --git a/microprofile/server/pom.xml b/microprofile/server/pom.xml index 70e8dd0dc..01115699b 100644 --- a/microprofile/server/pom.xml +++ b/microprofile/server/pom.xml @@ -50,6 +50,10 @@ io.helidon.microprofile.config helidon-microprofile-config + + io.helidon.config + helidon-config-yaml + org.jboss.weld.se weld-se-core diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java index 079fb8f52..67edc4723 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/Server.java @@ -42,7 +42,7 @@ import io.helidon.common.configurable.ServerThreadPoolSupplier; import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.serviceloader.HelidonServiceLoader; -import io.helidon.microprofile.config.MpConfig; +import io.helidon.config.MetaConfig; import io.helidon.microprofile.server.spi.MpService; import org.eclipse.microprofile.config.Config; @@ -164,7 +164,7 @@ public interface Server { private HelidonServiceLoader.Builder extensionBuilder; private ResourceConfig resourceConfig; private SeContainer cdiContainer; - private MpConfig config; + private Config config; private String host; private String basePath; private int port = -1; @@ -215,16 +215,24 @@ public interface Server { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (null == config) { - config = (MpConfig) ConfigProviderResolver.instance().getConfig(classLoader); - } else { - ConfigProviderResolver.instance().registerConfig(config, classLoader); + Optional metaConfigured = MetaConfig.metaConfig() + .map(metaConfig -> io.helidon.config.Config.builder().config(metaConfig).build()); + + // if we have a meta configured config, let's use it + // otherwise use the default + this.config = metaConfigured.map(value -> (Config) value) + .orElseGet(() -> ConfigProviderResolver.instance().getConfig(classLoader)); } + // make sure the config is available to application + ConfigProviderResolver.instance().registerConfig(config, classLoader); + if (null == defaultExecutorService) { defaultExecutorService = ServerThreadPoolSupplier.builder() - .name("server") - .config(config.helidonConfig().get("server.executor-service")) - .build(); + .name("server") + .config(((io.helidon.config.Config) config) + .get("server.executor-service")) + .build(); } STARTUP_LOGGER.finest("Configuration obtained"); @@ -317,7 +325,7 @@ public interface Server { Weld initializer = new Weld(); initializer.addBeanDefiningAnnotations(Path.class); initializer.setClassLoader(classLoader); - Map props = new HashMap<>(config.helidonConfig() + Map props = new HashMap<>(((io.helidon.config.Config) config) .get("cdi") .detach() .asMap() @@ -437,7 +445,7 @@ public interface Server { * @return modified builder */ public Builder config(io.helidon.config.Config config) { - this.config = (MpConfig) MpConfig.builder().config(config).build(); + this.config = (Config) config; return this; } @@ -448,7 +456,7 @@ public interface Server { * @return modified builder */ public Builder config(Config config) { - this.config = (MpConfig) config; + this.config = config; return this; } diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java index 24d59cf30..5bc53c07b 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerImpl.java @@ -51,7 +51,6 @@ import io.helidon.common.Prioritized; import io.helidon.common.context.Context; import io.helidon.common.http.Http; import io.helidon.config.Config; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.spi.MpServiceContext; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerConfiguration; @@ -101,8 +100,9 @@ public class ServerImpl implements Server { } ServerImpl(Builder builder) { - MpConfig mpConfig = (MpConfig) builder.config(); - Config config = mpConfig.helidonConfig(); + org.eclipse.microprofile.config.Config mpConfig = builder.config(); + Config config = (Config) mpConfig; + this.container = builder.cdiContainer(); this.containerCreated = builder.containerCreated(); this.context = builder.context(); @@ -353,7 +353,7 @@ public class ServerImpl implements Server { } private void loadExtensions(Builder builder, - MpConfig mpConfig, + org.eclipse.microprofile.config.Config mpConfig, Config config, List apps, Routing.Builder routingBuilder, @@ -379,7 +379,7 @@ public class ServerImpl implements Server { }); } - private MpServiceContext createExtensionContext(MpConfig mpConfig, + private MpServiceContext createExtensionContext(org.eclipse.microprofile.config.Config mpConfig, Config config, List apps, Routing.Builder routingBuilder, diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java index 668db9874..7f9fe577d 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018,2019 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. @@ -192,7 +192,7 @@ public class HelidonDeployableContainer implements DeployableContainer> configSources = new LinkedList<>(); + List> configSources = new LinkedList<>(); configSources.add(ConfigSources.file(context.deployDir.resolve("META-INF/microprofile-config.properties").toString()) .optional()); // The following line supports MP OpenAPI, which allows an alternate diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java index baeb7ada1..c24d04798 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java @@ -26,7 +26,6 @@ import javax.ws.rs.Path; import javax.ws.rs.core.Application; import io.helidon.config.Config; -import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; import org.glassfish.jersey.server.ResourceConfig; @@ -52,13 +51,10 @@ class ServerRunner { } void start(Config config, HelidonContainerConfiguration containerConfig, Set classNames, ClassLoader cl) { - //cl.getResources("beans.xml") Server.Builder builder = Server.builder() .port(containerConfig.getPort()) - .config(MpConfig.builder() - .config(config) - .addDiscoveredSources() - .build()); + .config(config); + handleClasses(cl, classNames, builder, containerConfig.getAddResourcesToApps()); diff --git a/microprofile/tests/tck/tck-config/pom.xml b/microprofile/tests/tck/tck-config/pom.xml index 19df91d64..3c896c33d 100644 --- a/microprofile/tests/tck/tck-config/pom.xml +++ b/microprofile/tests/tck/tck-config/pom.xml @@ -32,7 +32,7 @@ io.helidon.microprofile.config - helidon-microprofile-config-cdi + helidon-microprofile-config test @@ -55,6 +55,10 @@ weld-se-core test + + io.helidon.config + helidon-config-object-mapping + diff --git a/microprofile/tests/tck/tck-config/src/test/tck-suite.xml b/microprofile/tests/tck/tck-config/src/test/tck-suite.xml index 715278d82..153740411 100644 --- a/microprofile/tests/tck/tck-config/src/test/tck-suite.xml +++ b/microprofile/tests/tck/tck-config/src/test/tck-suite.xml @@ -1,7 +1,7 @@ - - - - - - - - - + + + + + + + diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml index 067cd8ebb..67eea7196 100644 --- a/microprofile/tracing/pom.xml +++ b/microprofile/tracing/pom.xml @@ -48,6 +48,10 @@ io.helidon.microprofile.server helidon-microprofile-server + + io.helidon.microprofile.config + helidon-microprofile-config + io.helidon.jersey helidon-jersey-common @@ -62,11 +66,6 @@ javax.activation-api test - - io.helidon.microprofile.config - helidon-microprofile-config-cdi - test - io.helidon.microprofile.bundles internal-test-libs diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java index 3c02b5298..473c15821 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java @@ -21,7 +21,7 @@ import javax.ws.rs.client.ClientBuilder; import io.helidon.common.configurable.ThreadPoolSupplier; import io.helidon.common.context.Contexts; -import io.helidon.microprofile.config.MpConfig; +import io.helidon.config.Config; import io.helidon.tracing.jersey.client.ClientTracingFilter; import org.eclipse.microprofile.config.ConfigProvider; @@ -35,8 +35,8 @@ public class MpTracingClientRegistrar implements ClientTracingRegistrarProvider static final ThreadPoolSupplier EXECUTOR_SERVICE; static { - MpConfig config = (MpConfig) ConfigProvider.getConfig(); - EXECUTOR_SERVICE = ThreadPoolSupplier.create(config.helidonConfig().get("tracing.executor-service")); + Config config = (Config) ConfigProvider.getConfig(); + EXECUTOR_SERVICE = ThreadPoolSupplier.create(config.get("tracing.executor-service")); } @Override diff --git a/microprofile/tracing/src/main/java9/module-info.java b/microprofile/tracing/src/main/java9/module-info.java index 2a9c5bfb0..686279ed6 100644 --- a/microprofile/tracing/src/main/java9/module-info.java +++ b/microprofile/tracing/src/main/java9/module-info.java @@ -30,6 +30,7 @@ module io.helidon.microprofile.tracing { requires static javax.interceptor.api; requires io.helidon.microprofile.server; + requires transitive io.helidon.microprofile.config; requires io.helidon.common; requires io.helidon.webserver; requires io.helidon.jersey.common; diff --git a/webserver/jersey/src/test/resources/logging-test.properties b/webserver/jersey/src/test/resources/logging-test.properties index 182869cc5..6ec70d25e 100644 --- a/webserver/jersey/src/test/resources/logging-test.properties +++ b/webserver/jersey/src/test/resources/logging-test.properties @@ -18,7 +18,7 @@ #All attributes details handlers=io.helidon.common.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n -.level=WARNING +.level=SEVERE -io.helidon.webserver.level=FINE -org.glassfish.jersey.internal.Errors.level=SEVERE +#io.helidon.webserver.level=FINE +#org.glassfish.jersey.internal.Errors.level=SEVERE