mirror of
https://github.com/jlengrand/helidon.git
synced 2026-03-10 08:21:17 +00:00
MP Config fixes (#1721)
MP Config implementation to correctly support mutable config sources.
This commit is contained in:
@@ -248,6 +248,11 @@
|
||||
<artifactId>helidon-config-object-mapping</artifactId>
|
||||
<version>${helidon.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
<version>${helidon.version}</version>
|
||||
</dependency>
|
||||
<!-- security -->
|
||||
<dependency>
|
||||
<groupId>io.helidon.security</groupId>
|
||||
|
||||
@@ -39,14 +39,7 @@
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.annotation.versioning</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -89,12 +89,26 @@ public final class Priorities {
|
||||
* @param defaultPriority default priority for elements that do not have it
|
||||
*/
|
||||
public static void sort(List<?> list, int defaultPriority) {
|
||||
list.sort(Comparator.comparingInt(it -> {
|
||||
list.sort(priorityComparator(defaultPriority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comparator for two objects, the classes for which are implementations of
|
||||
* {@link io.helidon.common.Prioritized}, and/or optionally annotated with {@link javax.annotation.Priority}
|
||||
* and which applies a specified default priority if either or both classes lack the annotation.
|
||||
*
|
||||
* @param <S> type of object being compared
|
||||
* @param defaultPriority used if the classes for either or both objects
|
||||
* lack the {@code Priority} annotation
|
||||
* @return comparator
|
||||
*/
|
||||
public static <S> Comparator<S> priorityComparator(int defaultPriority) {
|
||||
return Comparator.comparingInt(it -> {
|
||||
if (it instanceof Class) {
|
||||
return find((Class<?>) it, defaultPriority);
|
||||
} else {
|
||||
return find(it, defaultPriority);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import io.helidon.common.Prioritized;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class PrioritiesTest {
|
||||
@Test
|
||||
void testSort() {
|
||||
List<Service> services = new ArrayList<>(5);
|
||||
services.add(new DefaultPriority());
|
||||
services.add(new VeryLowPriority());
|
||||
services.add(new LowestPriority());
|
||||
services.add(new HigherPriority());
|
||||
services.add(new LowerPriority());
|
||||
|
||||
Priorities.sort(services, 100);
|
||||
|
||||
validate(services);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComparator() {
|
||||
List<Service> services = new ArrayList<>(5);
|
||||
// intentionally different order than in other methods, to make sure it is not working "by accident"
|
||||
services.add(new LowestPriority());
|
||||
services.add(new LowerPriority());
|
||||
services.add(new VeryLowPriority());
|
||||
services.add(new HigherPriority());
|
||||
services.add(new DefaultPriority());
|
||||
|
||||
services.sort(Priorities.priorityComparator(100));
|
||||
|
||||
validate(services);
|
||||
}
|
||||
|
||||
private void validate(List<Service> services) {
|
||||
assertThat("There must be 5 services in the list", services.size(), is(5));
|
||||
assertThat(services.get(0).getIt(), is(HigherPriority.IT));
|
||||
assertThat(services.get(1).getIt(), is(LowerPriority.IT));
|
||||
assertThat(services.get(2).getIt(), is(DefaultPriority.IT));
|
||||
assertThat(services.get(3).getIt(), is(VeryLowPriority.IT));
|
||||
assertThat(services.get(4).getIt(), is(LowestPriority.IT));
|
||||
}
|
||||
|
||||
private interface Service {
|
||||
String getIt();
|
||||
}
|
||||
|
||||
@Priority(1)
|
||||
private static class HigherPriority implements Service {
|
||||
private static final String IT = "higher";
|
||||
|
||||
@Override
|
||||
public String getIt() {
|
||||
return IT;
|
||||
}
|
||||
}
|
||||
|
||||
@Priority(2)
|
||||
private static class LowerPriority implements Service {
|
||||
private static final String IT = "lower";
|
||||
|
||||
@Override
|
||||
public String getIt() {
|
||||
return IT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultPriority implements Service {
|
||||
private static final String IT = "default";
|
||||
|
||||
@Override
|
||||
public String getIt() {
|
||||
return IT;
|
||||
}
|
||||
}
|
||||
|
||||
@Priority(101)
|
||||
private static class VeryLowPriority implements Service {
|
||||
private static final String IT = "veryLow";
|
||||
|
||||
@Override
|
||||
public String getIt() {
|
||||
return IT;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LowestPriority implements Service, Prioritized {
|
||||
private static final String IT = "lowest";
|
||||
|
||||
@Override
|
||||
public String getIt() {
|
||||
return IT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,33 +16,40 @@
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.helidon.config.tests</groupId>
|
||||
<artifactId>helidon-config-tests-project</artifactId>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-project</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>helidon-config-test-mp-reference</artifactId>
|
||||
<name>Helidon Config Tests MP Reference</name>
|
||||
|
||||
<description>
|
||||
Integration tests of reference in MP
|
||||
</description>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
<name>Helidon Config MP</name>
|
||||
<description>Core of the implementation of MicroProfile Config specification</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-service-loader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.helidon.config.ConfigSources;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
|
||||
/**
|
||||
* Utilities for Helidon MicroProfile Config implementation.
|
||||
*/
|
||||
public final class MpConfig {
|
||||
private MpConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows use to use Helidon Config on top of an MP config.
|
||||
* There is a limitation - the converters configured with MP config will not be available, unless
|
||||
* the implementation is coming from Helidon.
|
||||
* <p>
|
||||
* If you want to use the Helidon {@link io.helidon.config.Config} API instead of the MicroProfile
|
||||
* {@link org.eclipse.microprofile.config.Config} one, this method will create a Helidon config
|
||||
* instance that is based on the provided configuration instance.
|
||||
*
|
||||
* @param mpConfig MP Config instance
|
||||
* @return a new Helidon config using only the mpConfig as its config source
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static io.helidon.config.Config toHelidonConfig(Config mpConfig) {
|
||||
if (mpConfig instanceof io.helidon.config.Config) {
|
||||
return (io.helidon.config.Config) mpConfig;
|
||||
}
|
||||
|
||||
io.helidon.config.Config.Builder builder = io.helidon.config.Config.builder()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.disableMapperServices()
|
||||
.disableCaching()
|
||||
.disableParserServices()
|
||||
.disableFilterServices();
|
||||
|
||||
if (mpConfig instanceof MpConfigImpl) {
|
||||
((MpConfigImpl) mpConfig).converters()
|
||||
.forEach((clazz, converter) -> {
|
||||
Class<Object> cl = (Class<Object>) clazz;
|
||||
builder.addStringMapper(cl, converter::convert);
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, String> allConfig = new HashMap<>();
|
||||
mpConfig.getPropertyNames()
|
||||
.forEach(it -> {
|
||||
// covering the condition where a config key disappears between getting the property names and requesting
|
||||
// the value
|
||||
Optional<String> optionalValue = mpConfig.getOptionalValue(it, String.class);
|
||||
optionalValue.ifPresent(value -> allConfig.put(it, value));
|
||||
});
|
||||
|
||||
return builder.addSource(ConfigSources.create(allConfig))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
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;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.common.serviceloader.Priorities;
|
||||
import io.helidon.config.ConfigMappers;
|
||||
import io.helidon.config.mp.spi.MpConfigFilter;
|
||||
|
||||
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 boolean useDefaultSources = false;
|
||||
private boolean useDiscoveredSources = false;
|
||||
private boolean useDiscoveredConverters = false;
|
||||
|
||||
private final List<OrdinalSource> sources = new LinkedList<>();
|
||||
private final List<OrdinalConverter> converters = new LinkedList<>();
|
||||
|
||||
private ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
MpConfigBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDefaultSources() {
|
||||
useDefaultSources = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredSources() {
|
||||
useDiscoveredSources = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredConverters() {
|
||||
useDiscoveredConverters = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder forClassLoader(ClassLoader loader) {
|
||||
this.classLoader = loader;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withSources(ConfigSource... sources) {
|
||||
for (ConfigSource source : sources) {
|
||||
this.sources.add(new OrdinalSource(source));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigBuilder withConverter(Class<T> aClass, int ordinal, Converter<T> converter) {
|
||||
this.converters.add(new OrdinalConverter(converter, aClass, ordinal));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withConverters(Converter<?>... converters) {
|
||||
for (Converter<?> converter : converters) {
|
||||
this.converters.add(new OrdinalConverter(converter));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config build() {
|
||||
if (useDefaultSources) {
|
||||
sources.add(new OrdinalSource(MpConfigSources.systemProperties(), 400));
|
||||
sources.add(new OrdinalSource(MpConfigSources.environmentVariables(), 300));
|
||||
// microprofile-config.properties
|
||||
MpConfigSources.classPath(classLoader, "META-INF/microprofile-config.properties")
|
||||
.stream()
|
||||
.map(OrdinalSource::new)
|
||||
.forEach(sources::add);
|
||||
}
|
||||
// built-in converters - required by specification
|
||||
addBuiltIn(converters, Boolean.class, ConfigMappers::toBoolean);
|
||||
addBuiltIn(converters, Boolean.TYPE, ConfigMappers::toBoolean);
|
||||
addBuiltIn(converters, Byte.class, Byte::parseByte);
|
||||
addBuiltIn(converters, Byte.TYPE, Byte::parseByte);
|
||||
addBuiltIn(converters, Short.class, Short::parseShort);
|
||||
addBuiltIn(converters, Short.TYPE, Short::parseShort);
|
||||
addBuiltIn(converters, Integer.class, Integer::parseInt);
|
||||
addBuiltIn(converters, Integer.TYPE, Integer::parseInt);
|
||||
addBuiltIn(converters, Long.class, Long::parseLong);
|
||||
addBuiltIn(converters, Long.TYPE, Long::parseLong);
|
||||
addBuiltIn(converters, Float.class, Float::parseFloat);
|
||||
addBuiltIn(converters, Float.TYPE, Float::parseFloat);
|
||||
addBuiltIn(converters, Double.class, Double::parseDouble);
|
||||
addBuiltIn(converters, Double.TYPE, Double::parseDouble);
|
||||
addBuiltIn(converters, Character.class, MpConfigBuilder::toChar);
|
||||
addBuiltIn(converters, Character.TYPE, MpConfigBuilder::toChar);
|
||||
addBuiltIn(converters, Class.class, MpConfigBuilder::toClass);
|
||||
|
||||
// built-in converters - Helidon
|
||||
//javax.math
|
||||
addBuiltIn(converters, BigDecimal.class, ConfigMappers::toBigDecimal);
|
||||
addBuiltIn(converters, BigInteger.class, ConfigMappers::toBigInteger);
|
||||
//java.time
|
||||
addBuiltIn(converters, Duration.class, ConfigMappers::toDuration);
|
||||
addBuiltIn(converters, Period.class, ConfigMappers::toPeriod);
|
||||
addBuiltIn(converters, LocalDate.class, ConfigMappers::toLocalDate);
|
||||
addBuiltIn(converters, LocalDateTime.class, ConfigMappers::toLocalDateTime);
|
||||
addBuiltIn(converters, LocalTime.class, ConfigMappers::toLocalTime);
|
||||
addBuiltIn(converters, ZonedDateTime.class, ConfigMappers::toZonedDateTime);
|
||||
addBuiltIn(converters, ZoneId.class, ConfigMappers::toZoneId);
|
||||
addBuiltIn(converters, ZoneOffset.class, ConfigMappers::toZoneOffset);
|
||||
addBuiltIn(converters, Instant.class, ConfigMappers::toInstant);
|
||||
addBuiltIn(converters, OffsetTime.class, ConfigMappers::toOffsetTime);
|
||||
addBuiltIn(converters, OffsetDateTime.class, ConfigMappers::toOffsetDateTime);
|
||||
addBuiltIn(converters, YearMonth.class, YearMonth::parse);
|
||||
//java.io
|
||||
addBuiltIn(converters, File.class, MpConfigBuilder::toFile);
|
||||
//java.nio
|
||||
addBuiltIn(converters, Path.class, MpConfigBuilder::toPath);
|
||||
addBuiltIn(converters, Charset.class, ConfigMappers::toCharset);
|
||||
//java.net
|
||||
addBuiltIn(converters, URI.class, ConfigMappers::toUri);
|
||||
addBuiltIn(converters, URL.class, ConfigMappers::toUrl);
|
||||
//java.util
|
||||
addBuiltIn(converters, Pattern.class, ConfigMappers::toPattern);
|
||||
addBuiltIn(converters, UUID.class, ConfigMappers::toUUID);
|
||||
|
||||
// obsolete stuff
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
addBuiltIn(converters, Date.class, ConfigMappers::toDate);
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
addBuiltIn(converters, Calendar.class, ConfigMappers::toCalendar);
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
addBuiltIn(converters, GregorianCalendar.class, ConfigMappers::toGregorianCalendar);
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
addBuiltIn(converters, TimeZone.class, ConfigMappers::toTimeZone);
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
addBuiltIn(converters, SimpleTimeZone.class, ConfigMappers::toSimpleTimeZone);
|
||||
|
||||
if (useDiscoveredConverters) {
|
||||
ServiceLoader.load(Converter.class)
|
||||
.forEach(it -> converters.add(new OrdinalConverter(it)));
|
||||
}
|
||||
|
||||
if (useDiscoveredSources) {
|
||||
ServiceLoader.load(ConfigSource.class)
|
||||
.forEach(it -> sources.add(new OrdinalSource(it)));
|
||||
|
||||
ServiceLoader.load(ConfigSourceProvider.class)
|
||||
.forEach(it -> {
|
||||
it.getConfigSources(classLoader)
|
||||
.forEach(source -> sources.add(new OrdinalSource(source)));
|
||||
});
|
||||
}
|
||||
|
||||
// now it is from lowest to highest
|
||||
sources.sort(Comparator.comparingInt(o -> o.ordinal));
|
||||
converters.sort(Comparator.comparingInt(o -> o.ordinal));
|
||||
|
||||
// revert to have the first one the most significant
|
||||
Collections.reverse(sources);
|
||||
Collections.reverse(converters);
|
||||
|
||||
List<ConfigSource> sources = new LinkedList<>();
|
||||
HashMap<Class<?>, Converter<?>> converters = new HashMap<>();
|
||||
|
||||
this.sources.forEach(ordinal -> sources.add(ordinal.source));
|
||||
this.converters.forEach(ordinal -> converters.putIfAbsent(ordinal.type, ordinal.converter));
|
||||
|
||||
List<MpConfigFilter> filters = HelidonServiceLoader.create(ServiceLoader.load(MpConfigFilter.class))
|
||||
.asList();
|
||||
|
||||
return new MpConfigImpl(sources, converters, filters);
|
||||
}
|
||||
|
||||
private <T> void addBuiltIn(List<OrdinalConverter> converters, Class<T> clazz, Converter<T> converter) {
|
||||
converters.add(new OrdinalConverter(converter, clazz, 1));
|
||||
}
|
||||
|
||||
ConfigBuilder metaConfig(io.helidon.config.Config metaConfig) {
|
||||
io.helidon.config.Config helidonConfig = io.helidon.config.Config.builder()
|
||||
.config(metaConfig)
|
||||
.build();
|
||||
this.sources.add(new OrdinalSource(MpConfigSources.create(helidonConfig)));
|
||||
return this;
|
||||
}
|
||||
|
||||
private static File toFile(String value) {
|
||||
return new File(value);
|
||||
}
|
||||
|
||||
private static Path toPath(String value) {
|
||||
return Paths.get(value);
|
||||
}
|
||||
|
||||
private static Class<?> toClass(String stringValue) {
|
||||
try {
|
||||
return Class.forName(stringValue);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Failed to convert property " + stringValue + " to class", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static char toChar(String stringValue) {
|
||||
if (stringValue.length() != 1) {
|
||||
throw new IllegalArgumentException("The string to map must be a single character, but is: " + stringValue);
|
||||
}
|
||||
return stringValue.charAt(0);
|
||||
}
|
||||
|
||||
private static class OrdinalSource {
|
||||
private final int ordinal;
|
||||
private final ConfigSource source;
|
||||
|
||||
private OrdinalSource(ConfigSource source) {
|
||||
this.source = source;
|
||||
this.ordinal = findOrdinal(source);
|
||||
}
|
||||
|
||||
private OrdinalSource(ConfigSource source, int ordinal) {
|
||||
this.ordinal = ordinal;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
private static int findOrdinal(ConfigSource source) {
|
||||
int ordinal = source.getOrdinal();
|
||||
if (ordinal == ConfigSource.DEFAULT_ORDINAL) {
|
||||
return Priorities.find(source, ConfigSource.DEFAULT_ORDINAL);
|
||||
}
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ordinal + " " + source.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrdinalConverter {
|
||||
private final int ordinal;
|
||||
private final Class<?> type;
|
||||
private final Converter<?> converter;
|
||||
|
||||
private OrdinalConverter(Converter<?> converter, Class<?> aClass, int ordinal) {
|
||||
this.ordinal = ordinal;
|
||||
this.type = aClass;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
private OrdinalConverter(Converter<?> converter) {
|
||||
this(converter, getConverterType(converter.getClass()), Priorities.find(converter, 100));
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> getConverterType(Class<?> converterClass) {
|
||||
Class<?> type = doGetType(converterClass);
|
||||
if (null == type) {
|
||||
throw new IllegalArgumentException("Converter " + converterClass + " must be a ParameterizedType.");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static Class<?> doGetType(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.");
|
||||
}
|
||||
Type typeArgument = typeArguments[0];
|
||||
if (typeArgument instanceof Class) {
|
||||
return (Class<?>) typeArgument;
|
||||
}
|
||||
throw new IllegalStateException("Converter " + clazz + " must convert to a class, not " + typeArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doGetType(clazz.getSuperclass());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.helidon.config.mp.spi.MpConfigFilter;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
public class MpConfigImpl implements Config {
|
||||
private static final Logger LOGGER = Logger.getLogger(MpConfigImpl.class.getName());
|
||||
// for references resolving
|
||||
// matches string between ${ } with a negative lookbehind if there is not backslash
|
||||
private static final String REGEX_REFERENCE = "(?<!\\\\)\\$\\{([^}]+)\\}";
|
||||
private static final Pattern PATTERN_REFERENCE = Pattern.compile(REGEX_REFERENCE);
|
||||
// for encoding backslashes
|
||||
// matches a backslash with a positive lookahead if it is the backslash that encodes ${}
|
||||
private static final String REGEX_BACKSLASH = "\\\\(?=\\$\\{([^}]+)\\})";
|
||||
private static final Pattern PATTERN_BACKSLASH = Pattern.compile(REGEX_BACKSLASH);
|
||||
// I only care about unresolved key happening within the same thread
|
||||
private static final ThreadLocal<Set<String>> UNRESOLVED_KEYS = ThreadLocal.withInitial(HashSet::new);
|
||||
|
||||
private static final Pattern SPLIT_PATTERN = Pattern.compile("(?<!\\\\),");
|
||||
private static final Pattern ESCAPED_COMMA_PATTERN = Pattern.compile("\\,", Pattern.LITERAL);
|
||||
|
||||
private final List<ConfigSource> sources = new LinkedList<>();
|
||||
private final HashMap<Class<?>, Converter<?>> converters = new LinkedHashMap<>();
|
||||
private final boolean valueResolving;
|
||||
private final List<MpConfigFilter> filters = new ArrayList<>();
|
||||
|
||||
MpConfigImpl(List<ConfigSource> sources,
|
||||
HashMap<Class<?>, Converter<?>> converters,
|
||||
List<MpConfigFilter> filters) {
|
||||
this.sources.addAll(sources);
|
||||
this.converters.putAll(converters);
|
||||
this.converters.putIfAbsent(String.class, value -> value);
|
||||
|
||||
this.valueResolving = getOptionalValue("helidon.config.value-resolving.enabled", Boolean.class)
|
||||
.orElse(true);
|
||||
|
||||
// we need to initialize the filters first, before we set up filters
|
||||
filters.forEach(it -> {
|
||||
// initialize filter with filters with higher priority already in place
|
||||
it.init(this);
|
||||
// and then add it to the list of active filters
|
||||
// do not do this first, as we would end up in using an uninitialized filter
|
||||
this.filters.add(it);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(String propertyName, Class<T> propertyType) {
|
||||
return getOptionalValue(propertyName, propertyType)
|
||||
.orElseThrow(() -> new NoSuchElementException("Property \"" + propertyName + "\" is not available in "
|
||||
+ "configuration"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType) {
|
||||
// let's resolve arrays
|
||||
if (propertyType.isArray()) {
|
||||
Class<?> componentType = propertyType.getComponentType();
|
||||
// first try to see if we have a direct value
|
||||
Optional<String> optionalValue = getOptionalValue(propertyName, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
return Optional.of((T) toArray(propertyName, optionalValue.get(), componentType));
|
||||
}
|
||||
|
||||
/*
|
||||
we also support indexed value
|
||||
e.g. for key "my.list" you can have both:
|
||||
my.list=12,13,14
|
||||
or (not and):
|
||||
my.list.0=12
|
||||
my.list.1=13
|
||||
*/
|
||||
|
||||
String indexedConfigKey = propertyName + ".0";
|
||||
optionalValue = getOptionalValue(indexedConfigKey, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
List<Object> result = new LinkedList<>();
|
||||
|
||||
// first element is already in
|
||||
result.add(convert(indexedConfigKey, componentType, optionalValue.get()));
|
||||
|
||||
// hardcoded limit to lists of 1000 elements
|
||||
for (int i = 1; i < 1000; i++) {
|
||||
indexedConfigKey = propertyName + "." + i;
|
||||
optionalValue = getOptionalValue(indexedConfigKey, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
result.add(convert(indexedConfigKey, componentType, optionalValue.get()));
|
||||
} else {
|
||||
// finish the iteration on first missing index
|
||||
break;
|
||||
}
|
||||
}
|
||||
Object array = Array.newInstance(componentType, result.size());
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
Object component = result.get(i);
|
||||
Array.set(array, i, component);
|
||||
}
|
||||
return Optional.of((T) array);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return getStringValue(propertyName)
|
||||
.flatMap(it -> applyFilters(propertyName, it))
|
||||
.map(it -> convert(propertyName, propertyType, it));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getPropertyNames() {
|
||||
Set<String> names = new LinkedHashSet<>();
|
||||
for (ConfigSource source : sources) {
|
||||
names.addAll(source.getPropertyNames());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigSource> getConfigSources() {
|
||||
return Collections.unmodifiableList(sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Converter} used by this instance to produce instances of the specified type from string values.
|
||||
*
|
||||
* This method is from a future version of MP Config specification and may changed before it
|
||||
* is released. It is nevertheless needed to process annotations with default values.
|
||||
*
|
||||
* @param <T> the conversion type
|
||||
* @param forType the type to be produced by the converter
|
||||
* @return an {@link java.util.Optional} containing the converter, or empty if no converter is available for the specified type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<Converter<T>> getConverter(Class<T> forType) {
|
||||
return converters.entrySet()
|
||||
.stream()
|
||||
.filter(it -> forType.isAssignableFrom(it.getKey()))
|
||||
.findFirst()
|
||||
.map(Map.Entry::getValue)
|
||||
.map(it -> (Converter<T>) it)
|
||||
.or(() -> findImplicit(forType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a String to a specific type.
|
||||
* This is a helper method to allow for processing of default values that cannot be typed (e.g. in annotations).
|
||||
*
|
||||
* @param propertyName name of the property, used for error messages
|
||||
* @param type type of the property
|
||||
* @param value String value (may be null)
|
||||
* @param <T> type
|
||||
* @return instance of the correct type, may return null in case null was provided and converter did not do this
|
||||
* @throws IllegalArgumentException in case the String provided cannot be converted to the type expected
|
||||
*/
|
||||
private <T> T convert(String propertyName, Class<T> type, String value) {
|
||||
try {
|
||||
return findConverter(type)
|
||||
.convert(value);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to convert property \""
|
||||
+ propertyName
|
||||
+ "\" from its value \""
|
||||
+ value
|
||||
+ "\" to "
|
||||
+ type.getName(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getStringValue(String propertyName) {
|
||||
for (ConfigSource source : sources) {
|
||||
String value = source.getValue(propertyName);
|
||||
|
||||
if (null == value) {
|
||||
// not in this one
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGGER.finest("Found property " + propertyName + " in source " + source.getName());
|
||||
return Optional.of(resolveReferences(propertyName, value));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<String> applyFilters(String propertyName, String stringValue) {
|
||||
String result = stringValue;
|
||||
|
||||
for (MpConfigFilter filter : filters) {
|
||||
result = filter.apply(propertyName, result);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(result);
|
||||
}
|
||||
|
||||
private Object toArray(String propertyName, String stringValue, Class<?> componentType) {
|
||||
String[] values = toArray(stringValue);
|
||||
|
||||
Object array = Array.newInstance(componentType, values.length);
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
String value = values[i];
|
||||
Array.set(array, i, convert(propertyName, componentType, value));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private String resolveReferences(String key, String value) {
|
||||
if (!valueResolving) {
|
||||
return value;
|
||||
}
|
||||
if (!UNRESOLVED_KEYS.get().add(key)) {
|
||||
UNRESOLVED_KEYS.get().clear();
|
||||
throw new IllegalStateException("Recursive resolving of references for key " + key + ", value: " + value);
|
||||
}
|
||||
try {
|
||||
return format(value);
|
||||
} catch (NoSuchElementException e) {
|
||||
LOGGER.log(Level.FINER, e, () -> String.format("Reference for key %s not found. Value: %s", key, value));
|
||||
return value;
|
||||
} finally {
|
||||
UNRESOLVED_KEYS.get().remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private String format(String value) {
|
||||
Matcher m = PATTERN_REFERENCE.matcher(value);
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String propertyName = m.group(1);
|
||||
m.appendReplacement(sb,
|
||||
Matcher.quoteReplacement(getOptionalValue(propertyName, String.class)
|
||||
.orElseGet(() -> "${" + propertyName + "}")));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
// remove all backslash that encodes ${...}
|
||||
m = PATTERN_BACKSLASH.matcher(sb.toString());
|
||||
|
||||
return m.replaceAll("");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> Converter<T> findConverter(Class<T> type) {
|
||||
Converter<?> converter = converters.get(type);
|
||||
if (null != converter) {
|
||||
return (Converter<T>) converter;
|
||||
}
|
||||
|
||||
return getConverter(type)
|
||||
.orElseGet(() -> new FailingConverter<>(type));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Optional<Converter<T>> findImplicit(Class<T> type) {
|
||||
// enums must be explicitly supported
|
||||
if (Enum.class.isAssignableFrom(type)) {
|
||||
return Optional.of(value -> {
|
||||
Class<? extends Enum> enumClass = (Class<? extends Enum>) type;
|
||||
return (T) Enum.valueOf(enumClass, value);
|
||||
});
|
||||
}
|
||||
// any class that has a "public static T method()"
|
||||
Optional<Method> method = findMethod(type, "of", String.class)
|
||||
.or(() -> findMethod(type, "valueOf", String.class))
|
||||
.or(() -> findMethod(type, "parse", CharSequence.class))
|
||||
.or(() -> findMethod(type, "parse", String.class));
|
||||
|
||||
if (method.isPresent()) {
|
||||
Method m = method.get();
|
||||
return Optional.of(value -> {
|
||||
try {
|
||||
return (T) m.invoke(null, value);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to convert to " + type.getName() + " using a static method", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// constructor with a single string parameter
|
||||
try {
|
||||
Constructor<T> constructor = type.getConstructor(String.class);
|
||||
if (constructor.canAccess(null)) {
|
||||
return Optional.of(value -> {
|
||||
try {
|
||||
return constructor.newInstance(value);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to convert to " + type.getName() + " using a constructor", e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOGGER.finest("Constructor with String parameter is not accessible on type " + type);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.log(Level.FINEST, "There is no public constructor with string parameter on class " + type.getName(), e);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Method> findMethod(Class<?> type, String name, Class<?>... parameterTypes) {
|
||||
try {
|
||||
Method result = type.getDeclaredMethod(name, parameterTypes);
|
||||
if (!result.canAccess(null)) {
|
||||
LOGGER.finest(() -> "Method " + name + "(" + Arrays
|
||||
.toString(parameterTypes) + ") is not accessible on class " + type.getName());
|
||||
return Optional.empty();
|
||||
}
|
||||
if (!Modifier.isStatic(result.getModifiers())) {
|
||||
LOGGER.finest(() -> "Method " + name + "(" + Arrays
|
||||
.toString(parameterTypes) + ") is not static on class " + type.getName());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(result);
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.log(Level.FINEST,
|
||||
"Method " + name + "(" + Arrays.toString(parameterTypes) + ") is not avilable on class " + type.getName(),
|
||||
e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<Class<?>, Converter<?>> converters() {
|
||||
return converters;
|
||||
}
|
||||
|
||||
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] = ESCAPED_COMMA_PATTERN.matcher(value).replaceAll(Matcher.quoteReplacement(","));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static class FailingConverter<T> implements Converter<T> {
|
||||
private final Class<T> type;
|
||||
|
||||
private FailingConverter(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(String value) {
|
||||
throw new IllegalArgumentException("Cannot convert \"" + value + "\" to type " + type.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config;
|
||||
package io.helidon.config.mp;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.IdentityHashMap;
|
||||
@@ -31,6 +31,8 @@ import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.helidon.common.GenericType;
|
||||
import io.helidon.config.ConfigValue;
|
||||
import io.helidon.config.MetaConfig;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
@@ -115,7 +117,7 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
*
|
||||
* @param config configuration to use
|
||||
*/
|
||||
public static void runtimeStart(io.helidon.config.Config config) {
|
||||
public static void runtimeStart(Config config) {
|
||||
if (BUILD_CONFIG.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -144,13 +146,11 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
config = ((ConfigDelegate) config).delegate();
|
||||
}
|
||||
|
||||
io.helidon.config.Config helidonConfig = (io.helidon.config.Config) config;
|
||||
|
||||
if (null != currentConfig) {
|
||||
currentConfig.set(helidonConfig);
|
||||
currentConfig.set(config);
|
||||
}
|
||||
|
||||
ConfigDelegate newConfig = new ConfigDelegate(helidonConfig);
|
||||
ConfigDelegate newConfig = new ConfigDelegate(config);
|
||||
CONFIGS.put(classLoader, newConfig);
|
||||
|
||||
return newConfig;
|
||||
@@ -192,18 +192,24 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
* that hold a reference to configuration obtained at build time.
|
||||
*/
|
||||
public static final class ConfigDelegate implements io.helidon.config.Config, Config {
|
||||
private AtomicReference<io.helidon.config.Config> delegate;
|
||||
private final AtomicReference<Config> delegate = new AtomicReference<>();
|
||||
private final AtomicReference<io.helidon.config.Config> helidonDelegate = new AtomicReference<>();
|
||||
|
||||
private ConfigDelegate(io.helidon.config.Config delegate) {
|
||||
this.delegate = new AtomicReference<>(delegate);
|
||||
private ConfigDelegate(Config delegate) {
|
||||
set(delegate);
|
||||
}
|
||||
|
||||
private void set(io.helidon.config.Config newDelegate) {
|
||||
this.delegate.set(newDelegate);
|
||||
void set(Config delegate) {
|
||||
this.delegate.set(delegate);
|
||||
if (delegate instanceof io.helidon.config.Config) {
|
||||
this.helidonDelegate.set((io.helidon.config.Config) delegate);
|
||||
} else {
|
||||
this.helidonDelegate.set(MpConfig.toHelidonConfig(delegate));
|
||||
}
|
||||
}
|
||||
|
||||
private io.helidon.config.Config getCurrent() {
|
||||
return delegate.get().context().last();
|
||||
return helidonDelegate.get().context().last();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -242,7 +248,7 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T convert(Class<T> type, String value) throws ConfigMappingException {
|
||||
public <T> T convert(Class<T> type, String value) {
|
||||
return getCurrent().convert(type, value);
|
||||
}
|
||||
|
||||
@@ -262,43 +268,43 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigValue<List<T>> asList(Class<T> type) throws ConfigMappingException {
|
||||
public <T> ConfigValue<List<T>> asList(Class<T> type) {
|
||||
return getCurrent().asList(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigValue<List<T>> asList(Function<io.helidon.config.Config, T> mapper) throws ConfigMappingException {
|
||||
public <T> ConfigValue<List<T>> asList(Function<io.helidon.config.Config, T> mapper) {
|
||||
return getCurrent().asList(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigValue<List<io.helidon.config.Config>> asNodeList() throws ConfigMappingException {
|
||||
public ConfigValue<List<io.helidon.config.Config>> asNodeList() {
|
||||
return getCurrent().asNodeList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigValue<Map<String, String>> asMap() throws MissingValueException {
|
||||
public ConfigValue<Map<String, String>> asMap() {
|
||||
return getCurrent().asMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(String propertyName, Class<T> propertyType) {
|
||||
return ((Config) getCurrent()).getValue(propertyName, propertyType);
|
||||
return delegate.get().getValue(propertyName, propertyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType) {
|
||||
return ((Config) getCurrent()).getOptionalValue(propertyName, propertyType);
|
||||
return delegate.get().getOptionalValue(propertyName, propertyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getPropertyNames() {
|
||||
return ((Config) getCurrent()).getPropertyNames();
|
||||
return delegate.get().getPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigSource> getConfigSources() {
|
||||
return ((Config) getCurrent()).getConfigSources();
|
||||
return delegate.get().getConfigSources();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,7 +313,7 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
|
||||
* @return the instance backing this config delegate
|
||||
*/
|
||||
public Config delegate() {
|
||||
return (Config) getCurrent();
|
||||
return delegate.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigException;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
/**
|
||||
* Utilities for MicroProfile Config {@link org.eclipse.microprofile.config.spi.ConfigSource}.
|
||||
* <p>
|
||||
* The following methods create MicroProfile config sources to help with manual setup of Config
|
||||
* from {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver#getBuilder()}:
|
||||
* <ul>
|
||||
* <li>{@link #systemProperties()} - system properties config source</li>
|
||||
* <li>{@link #environmentVariables()} - environment variables config source</li>
|
||||
* <li>{@link #create(java.nio.file.Path)} - load a properties file from file system</li>
|
||||
* <li>{@link #create(String, java.nio.file.Path)} - load a properties file from file system with custom name</li>
|
||||
* <li>{@link #create(java.util.Map)} - create an in-memory source from map</li>
|
||||
* <li>{@link #create(String, java.util.Map)} - create an in-memory source from map with custom name</li>
|
||||
* <li>{@link #create(java.util.Properties)} - create an in-memory source from properties</li>
|
||||
* <li>{@link #create(String, java.util.Properties)} - create an in-memory source from properties with custom name</li>
|
||||
* </ul>
|
||||
* The following methods add integration with Helidon SE Config:
|
||||
* <ul>
|
||||
* <li>{@link #create(io.helidon.config.spi.ConfigSource)} - create a MicroProfile config source from Helidon SE config
|
||||
* source</li>
|
||||
* <li>{@link #create(io.helidon.config.Config)} - create a MicroProfile config source from Helidon SE Config instance</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class MpConfigSources {
|
||||
private MpConfigSources() {
|
||||
}
|
||||
|
||||
/**
|
||||
* In memory config source based on the provided map.
|
||||
* The config source queries the map each time {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)}
|
||||
* is called.
|
||||
*
|
||||
* @param name name of the source
|
||||
* @param theMap map serving as configuration data
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(String name, Map<String, String> theMap) {
|
||||
return new MpMapSource(name, theMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* In memory config source based on the provided map.
|
||||
* The config source queries the map each time {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)}
|
||||
* is called.
|
||||
*
|
||||
* @param theMap map serving as configuration data
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(Map<String, String> theMap) {
|
||||
return create("Map", theMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link java.util.Properties} config source based on a file on file system.
|
||||
* The file is read just once, when the source is created and further changes to the underlying file are
|
||||
* ignored.
|
||||
*
|
||||
* @param path path of the properties file on the file system
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(Path path) {
|
||||
return create(path.toString(), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link java.util.Properties} config source based on a URL.
|
||||
* The URL is read just once, when the source is created and further changes to the underlying resource are
|
||||
* ignored.
|
||||
*
|
||||
* @param url url of the properties file (any URL scheme supported by JVM can be used)
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(URL url) {
|
||||
String name = url.toString();
|
||||
|
||||
try {
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||
Properties properties = new Properties();
|
||||
properties.load(inputStream);
|
||||
|
||||
return create(name, properties);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ConfigException("Failed to load ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link java.util.Properties} config source based on a file on file system.
|
||||
* The file is read just once, when the source is created and further changes to the underlying file are
|
||||
* ignored.
|
||||
*
|
||||
* @param name name of the config source
|
||||
* @param path path of the properties file on the file system
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(String name, Path path) {
|
||||
Properties props = new Properties();
|
||||
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
props.load(in);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigException("Failed to read properties from " + path.toAbsolutePath());
|
||||
}
|
||||
|
||||
return create(name, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* In memory config source based on the provided properties.
|
||||
* The config source queries the properties each time
|
||||
* {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)}
|
||||
* is called.
|
||||
*
|
||||
* @param properties serving as configuration data
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(Properties properties) {
|
||||
return create("Properties", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* In memory config source based on the provided properties.
|
||||
* The config source queries the properties each time
|
||||
* {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)}
|
||||
* is called.
|
||||
*
|
||||
* @param name name of the config source
|
||||
* @param properties serving as configuration data
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource create(String name, Properties properties) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
result.put(key, properties.getProperty(key));
|
||||
}
|
||||
return new MpMapSource(name, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variables config source.
|
||||
* This source takes care of replacement of properties by environment variables as defined
|
||||
* in MicroProfile Config specification.
|
||||
* This config source is immutable and caching.
|
||||
*
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource environmentVariables() {
|
||||
return new MpEnvironmentVariablesSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* In memory config source based on system properties.
|
||||
* The config source queries the properties each time
|
||||
* {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)}
|
||||
* is called.
|
||||
*
|
||||
* @return a new config source
|
||||
*/
|
||||
public static ConfigSource systemProperties() {
|
||||
return new MpSystemPropertiesSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all resources on classpath and return a config source for each.
|
||||
* Order is kept as provided by class loader.
|
||||
*
|
||||
* @param resource resource to find
|
||||
* @return a config source for each resource on classpath, empty if none found
|
||||
*/
|
||||
public static List<ConfigSource> classPath(String resource) {
|
||||
return classPath(Thread.currentThread().getContextClassLoader(), resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all resources on classpath and return a config source for each.
|
||||
* Order is kept as provided by class loader.
|
||||
*
|
||||
* @param classLoader class loader to use to locate the resources
|
||||
* @param resource resource to find
|
||||
* @return a config source for each resource on classpath, empty if none found
|
||||
*/
|
||||
public static List<ConfigSource> classPath(ClassLoader classLoader, String resource) {
|
||||
List<ConfigSource> sources = new LinkedList<>();
|
||||
try {
|
||||
classLoader.getResources(resource)
|
||||
.asIterator()
|
||||
.forEachRemaining(it -> sources.add(create(it)));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to read \"" + resource + "\" from classpath");
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Config source based on a Helidon SE config source.
|
||||
* This is to support Helidon SE features in Helidon MP.
|
||||
*
|
||||
* The config source will be immutable regardless of configured polling strategy or change watchers.
|
||||
*
|
||||
* @param helidonConfigSource config source to use
|
||||
* @return a new MicroProfile Config config source
|
||||
*/
|
||||
public static ConfigSource create(io.helidon.config.spi.ConfigSource helidonConfigSource) {
|
||||
return MpHelidonSource.create(helidonConfigSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Config source base on a Helidon SE config instance.
|
||||
* This is to support advanced Helidon SE features in Helidon MP.
|
||||
*
|
||||
* The config source will be mutable if the config uses polling strategy and/or change watchers.
|
||||
* Each time the {@link org.eclipse.microprofile.config.spi.ConfigSource#getValue(String)} is called,
|
||||
* the latest config version will be queried.
|
||||
*
|
||||
* @param config Helidon SE configuration
|
||||
* @return a new MicroProfile Config config source
|
||||
*/
|
||||
public static ConfigSource create(Config config) {
|
||||
return new MpHelidonConfigSource(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
@Priority(300)
|
||||
class MpEnvironmentVariablesSource implements ConfigSource {
|
||||
private static final Pattern DISALLOWED_CHARS = Pattern.compile("[^a-zA-Z0-9_]");
|
||||
private static final String UNDERSCORE = "_";
|
||||
|
||||
private final Map<String, String> env;
|
||||
private final Map<String, Cached> cache = new ConcurrentHashMap<>();
|
||||
|
||||
MpEnvironmentVariablesSource() {
|
||||
this.env = System.getenv();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
// environment variable config source is immutable - we can safely cache all requested keys, so we
|
||||
// do not execute the regular expression on every get
|
||||
return cache.computeIfAbsent(propertyName, theKey -> {
|
||||
// According to the spec, we have three ways of looking for a property
|
||||
// 1. Exact match
|
||||
String result = env.get(propertyName);
|
||||
if (null != result) {
|
||||
return new Cached(result);
|
||||
}
|
||||
// 2. replace non alphanumeric characters with _
|
||||
String rule2 = rule2(propertyName);
|
||||
result = env.get(rule2);
|
||||
if (null != result) {
|
||||
return new Cached(result);
|
||||
}
|
||||
// 3. replace same as above, but uppercase
|
||||
String rule3 = rule2.toUpperCase();
|
||||
result = env.get(rule3);
|
||||
return new Cached(result);
|
||||
}).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Environment Variables";
|
||||
}
|
||||
|
||||
/**
|
||||
* Rule #2 states: Replace each character that is neither alphanumeric nor _ with _ (i.e. com_ACME_size).
|
||||
*
|
||||
* @param propertyName name of property as requested by user
|
||||
* @return name of environment variable we look for
|
||||
*/
|
||||
private static String rule2(String propertyName) {
|
||||
return DISALLOWED_CHARS.matcher(propertyName).replaceAll(UNDERSCORE);
|
||||
}
|
||||
|
||||
private static final class Cached {
|
||||
private final String value;
|
||||
|
||||
private Cached(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
final class MpHelidonConfigSource implements ConfigSource {
|
||||
private final io.helidon.config.Config helidonConfig;
|
||||
|
||||
MpHelidonConfigSource(io.helidon.config.Config helidonConfig) {
|
||||
this.helidonConfig = helidonConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return helidonConfig.context()
|
||||
.last()
|
||||
.asMap()
|
||||
.orElseGet(Map::of);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String s) {
|
||||
return helidonConfig.context()
|
||||
.last()
|
||||
.get(s)
|
||||
.asString()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Helidon Config";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.helidon.common.serviceloader.HelidonServiceLoader;
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigHelper;
|
||||
import io.helidon.config.spi.ConfigContent;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.LazyConfigSource;
|
||||
import io.helidon.config.spi.NodeConfigSource;
|
||||
import io.helidon.config.spi.ParsableSource;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
final class MpHelidonSource {
|
||||
private MpHelidonSource() {
|
||||
}
|
||||
|
||||
static ConfigSource create(io.helidon.config.spi.ConfigSource source) {
|
||||
source.init(it -> {
|
||||
throw new UnsupportedOperationException(
|
||||
"Source runtimes are not available in MicroProfile Config implementation");
|
||||
});
|
||||
|
||||
if (!source.exists() && !source.optional()) {
|
||||
throw new ConfigException("Config source " + source + " is mandatory, yet it does not exist.");
|
||||
}
|
||||
|
||||
if (source instanceof NodeConfigSource) {
|
||||
Optional<ConfigContent.NodeContent> load = ((NodeConfigSource) source).load();
|
||||
// load the data, create a map from it
|
||||
return MpConfigSources.create(source.description(),
|
||||
load.map(ConfigContent.NodeContent::data)
|
||||
.map(ConfigHelper::flattenNodes)
|
||||
.orElseGet(Map::of));
|
||||
}
|
||||
|
||||
if (source instanceof ParsableSource) {
|
||||
return HelidonParsableSource.create((ParsableSource) source);
|
||||
}
|
||||
|
||||
if (source instanceof LazyConfigSource) {
|
||||
return new HelidonLazySource(source, (LazyConfigSource) source);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Helidon config source must be one of: node source, parsable source, or lazy source. Provided is neither: "
|
||||
+ source.getClass().getName());
|
||||
}
|
||||
|
||||
private static class HelidonParsableSource {
|
||||
public static ConfigSource create(ParsableSource source) {
|
||||
Optional<ConfigParser.Content> load = source.load();
|
||||
if (load.isEmpty()) {
|
||||
return MpConfigSources.create(source.description(), Map.of());
|
||||
}
|
||||
ConfigParser.Content content = load.get();
|
||||
|
||||
String mediaType = content.mediaType()
|
||||
.or(source::mediaType)
|
||||
.orElseThrow(() -> new ConfigException("Source " + source + " does not provide media type, cannot use it."));
|
||||
|
||||
ConfigParser parser = source.parser()
|
||||
.or(() -> findParser(mediaType))
|
||||
.orElseThrow(() -> new ConfigException("Could not locate config parser for media type: \""
|
||||
+ mediaType + "\""));
|
||||
|
||||
// create a map from parsed node
|
||||
return MpConfigSources.create(source.description(),
|
||||
ConfigHelper.flattenNodes(parser.parse(content)));
|
||||
|
||||
}
|
||||
|
||||
private static Optional<ConfigParser> findParser(String mediaType) {
|
||||
return HelidonServiceLoader.create(ServiceLoader.load(ConfigParser.class))
|
||||
.asList()
|
||||
.stream()
|
||||
.filter(it -> it.supportedMediaTypes().contains(mediaType))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private static class HelidonLazySource implements ConfigSource {
|
||||
private final Map<String, String> loadedProperties = new ConcurrentHashMap<>();
|
||||
private final LazyConfigSource lazy;
|
||||
private final io.helidon.config.spi.ConfigSource source;
|
||||
|
||||
private HelidonLazySource(io.helidon.config.spi.ConfigSource source, LazyConfigSource lazy) {
|
||||
this.lazy = lazy;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.unmodifiableMap(loadedProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
String value = lazy.node(propertyName)
|
||||
.flatMap(ConfigNode::value)
|
||||
.orElse(null);
|
||||
|
||||
if (null == value) {
|
||||
loadedProperties.remove(propertyName);
|
||||
} else {
|
||||
loadedProperties.put(propertyName, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return source.description();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
/**
|
||||
* Map based config source.
|
||||
*/
|
||||
class MpMapSource implements ConfigSource {
|
||||
private final Map<String, String> map;
|
||||
private final String name;
|
||||
|
||||
MpMapSource(String name, Map<String, String> map) {
|
||||
this.name = name;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
return map.get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
@Priority(400)
|
||||
class MpSystemPropertiesSource implements ConfigSource {
|
||||
private final Properties props;
|
||||
|
||||
MpSystemPropertiesSource() {
|
||||
this.props = System.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
Set<String> strings = props.stringPropertyNames();
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
strings.forEach(it -> result.put(it, props.getProperty(it)));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
return props.getProperty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "System Properties";
|
||||
}
|
||||
}
|
||||
@@ -14,19 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
abstract class ConfigSourceRuntimeBase implements ConfigSourceRuntime {
|
||||
|
||||
boolean isSystemProperties() {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isEnvironmentVariables() {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean changesSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helidon implementation of microprofile config.
|
||||
*/
|
||||
package io.helidon.config.mp;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp.spi;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
|
||||
/**
|
||||
* Filtering support for MicroProfile config implementation.
|
||||
* The current specification does not have a way to intercept values as they are
|
||||
* delivered.
|
||||
* As we want to support filtering capabilities (such as for configuration encryption),
|
||||
* this is a temporary solution (or permanent if the MP spec does not add any similar feature).
|
||||
*/
|
||||
public interface MpConfigFilter {
|
||||
/**
|
||||
* Initialize this filter from configuration.
|
||||
* The config instance provided only has filters with higher priority than this filter.
|
||||
*
|
||||
* @param config configuration to set this filter up.
|
||||
*/
|
||||
default void init(Config config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this filter on the provided value.
|
||||
*
|
||||
* @param propertyName name of the property (its key)
|
||||
* @param value the current value of the property as retrieved from the config source, or from previous
|
||||
* filters
|
||||
* @return value as processed by this filter
|
||||
*/
|
||||
String apply(String propertyName, String value);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon specific extension support for MicroProfile Config.
|
||||
*/
|
||||
package io.helidon.config.mp.spi;
|
||||
38
config/config-mp/src/main/java/module-info.java
Normal file
38
config/config-mp/src/main/java/module-info.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of the non-CDI parts of Eclipse MicroProfile Config specification.
|
||||
*/
|
||||
module io.helidon.config.mp {
|
||||
requires java.logging;
|
||||
requires io.helidon.common;
|
||||
requires io.helidon.config;
|
||||
requires transitive microprofile.config.api;
|
||||
requires java.annotation;
|
||||
requires io.helidon.common.serviceloader;
|
||||
|
||||
exports io.helidon.config.mp;
|
||||
exports io.helidon.config.mp.spi;
|
||||
|
||||
uses org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
uses org.eclipse.microprofile.config.spi.ConfigSourceProvider;
|
||||
uses org.eclipse.microprofile.config.spi.Converter;
|
||||
uses io.helidon.config.mp.spi.MpConfigFilter;
|
||||
uses io.helidon.config.spi.ConfigParser;
|
||||
|
||||
provides org.eclipse.microprofile.config.spi.ConfigProviderResolver with io.helidon.config.mp.MpConfigProviderResolver;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -14,4 +14,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
io.helidon.config.MpConfigProviderResolver
|
||||
io.helidon.config.mp.MpConfigProviderResolver
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.helidon.config.tests.mpref;
|
||||
package io.helidon.config.mp;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.ConfigProvider;
|
||||
@@ -53,6 +53,17 @@ public class MpConfigReferenceTest {
|
||||
test("3", "1", VALUE_1 + "-" + VALUE_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingRefs() {
|
||||
String key = "referencing4-1";
|
||||
String actual = config.getValue(key, String.class);
|
||||
assertThat(actual, is("${missing}"));
|
||||
|
||||
key = "referencing4-2";
|
||||
actual = config.getValue(key, String.class);
|
||||
assertThat(actual, is("${missing}-value"));
|
||||
}
|
||||
|
||||
private void test(String prefix, String value) {
|
||||
test(prefix, "1", value);
|
||||
test(prefix, "2", value + "-ref");
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.helidon.config.ConfigException;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.PropertiesConfigParser;
|
||||
import io.helidon.config.spi.ConfigContent;
|
||||
import io.helidon.config.spi.ConfigContext;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigParser;
|
||||
import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.LazyConfigSource;
|
||||
import io.helidon.config.spi.NodeConfigSource;
|
||||
import io.helidon.config.spi.ParsableSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class MpConfigSourcesTest {
|
||||
@Test
|
||||
void testHelidonMap() {
|
||||
Map<String, String> values = Map.of(
|
||||
"key.first", "first",
|
||||
"key.second", "second"
|
||||
);
|
||||
org.eclipse.microprofile.config.spi.ConfigSource mpSource = MpConfigSources.create(ConfigSources.create(values).build());
|
||||
|
||||
assertThat(mpSource.getValue("key.first"), is("first"));
|
||||
assertThat(mpSource.getValue("key.second"), is("second"));
|
||||
}
|
||||
@Test
|
||||
void testHelidonParsable() {
|
||||
ParsableImpl helidonSource = new ParsableImpl();
|
||||
org.eclipse.microprofile.config.spi.ConfigSource mpSource = MpConfigSources.create(helidonSource);
|
||||
|
||||
assertThat(mpSource.getValue(ParsableImpl.KEY + ".notThere"), nullValue());
|
||||
assertThat(mpSource.getValue(ParsableImpl.KEY), is(ParsableImpl.VALUE));
|
||||
|
||||
assertThat(mpSource.getName(), is(ParsableImpl.DESCRIPTION));
|
||||
assertThat("init called exactly once", helidonSource.inits.get(), is(1));
|
||||
assertThat("exists called exactly once", helidonSource.exists.get(), is(1));
|
||||
}
|
||||
@Test
|
||||
void testHelidonNode() {
|
||||
NodeImpl helidonSource = new NodeImpl();
|
||||
org.eclipse.microprofile.config.spi.ConfigSource mpSource = MpConfigSources.create(helidonSource);
|
||||
|
||||
assertThat(mpSource.getValue(NodeImpl.KEY + ".notThere"), nullValue());
|
||||
assertThat(mpSource.getValue(NodeImpl.KEY), is(NodeImpl.VALUE));
|
||||
|
||||
assertThat(mpSource.getName(), is(NodeImpl.DESCRIPTION));
|
||||
assertThat("init called exactly once", helidonSource.inits.get(), is(1));
|
||||
assertThat("exists called exactly once", helidonSource.exists.get(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHelidonLazy() {
|
||||
LazyImpl lazy = new LazyImpl();
|
||||
|
||||
org.eclipse.microprofile.config.spi.ConfigSource mpSource = MpConfigSources.create(lazy);
|
||||
assertThat(mpSource.getValue("key-1"), nullValue());
|
||||
|
||||
lazy.put("key-1", "value-1");
|
||||
assertThat(mpSource.getValue("key-1"), is("value-1"));
|
||||
|
||||
lazy.remove("key-1");
|
||||
assertThat(mpSource.getValue("key-1"), nullValue());
|
||||
|
||||
assertThat(mpSource.getName(), is(LazyImpl.DESCRIPTION));
|
||||
assertThat("init called exactly once", lazy.inits.get(), is(1));
|
||||
assertThat("exists called exactly once", lazy.exists.get(), is(1));
|
||||
}
|
||||
|
||||
private static final class NodeImpl implements ConfigSource, NodeConfigSource {
|
||||
private static final String DESCRIPTION = "node-unit-test";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
|
||||
private final AtomicInteger inits = new AtomicInteger();
|
||||
private final AtomicInteger exists = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public Optional<ConfigContent.NodeContent> load() throws ConfigException {
|
||||
return Optional.of(ConfigContent.NodeContent.builder()
|
||||
.node(ConfigNode.ObjectNode.builder()
|
||||
.addValue(KEY, VALUE)
|
||||
.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ConfigContext context) {
|
||||
inits.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
exists.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LazyImpl implements ConfigSource, LazyConfigSource {
|
||||
private static final String DESCRIPTION = "lazy-unit-test";
|
||||
|
||||
private final Map<String, String> values = new ConcurrentHashMap<>();
|
||||
private final AtomicInteger inits = new AtomicInteger();
|
||||
private final AtomicInteger exists = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void init(ConfigContext context) {
|
||||
inits.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
exists.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConfigNode> node(String key) {
|
||||
return Optional.ofNullable(values.get(key))
|
||||
.map(ConfigNode.ValueNode::create);
|
||||
}
|
||||
|
||||
private void put(String key, String value) {
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
private void remove(String key) {
|
||||
values.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ParsableImpl implements ConfigSource, ParsableSource {
|
||||
private static final String DESCRIPTION = "parsable-unit-test";
|
||||
private static final String KEY = "parsable.key";
|
||||
private static final String VALUE = "parsableValue";
|
||||
private static final String CONTENT = KEY + "=" + VALUE;
|
||||
|
||||
private final AtomicInteger inits = new AtomicInteger();
|
||||
private final AtomicInteger exists = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public Optional<ConfigParser.Content> load() throws ConfigException {
|
||||
return Optional.of(content());
|
||||
}
|
||||
|
||||
private ConfigParser.Content content() {
|
||||
return ConfigParser.Content.builder()
|
||||
.charset(StandardCharsets.UTF_8)
|
||||
.data(new ByteArrayInputStream(CONTENT.getBytes(StandardCharsets.UTF_8)))
|
||||
.mediaType(PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConfigParser> parser() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> mediaType() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ConfigContext context) {
|
||||
inits.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
exists.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.mp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
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.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.arrayWithSize;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
/**
|
||||
* Test MicroProfile config implementation.
|
||||
*/
|
||||
public class MpConfigTest {
|
||||
private static Config config;
|
||||
|
||||
@BeforeAll
|
||||
static void initClass() {
|
||||
config = ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources.create(Map.of("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")),
|
||||
MpConfigSources.create(Map.of("app.storageEnabled", "true",
|
||||
ConfigSource.CONFIG_ORDINAL, "1000")))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigSources() {
|
||||
Iterable<ConfigSource> configSources = config.getConfigSources();
|
||||
List<ConfigSource> asList = new ArrayList<>();
|
||||
for (ConfigSource configSource : configSources) {
|
||||
asList.add(configSource);
|
||||
}
|
||||
|
||||
assertThat(asList, hasSize(2));
|
||||
|
||||
assertThat(asList.get(0), instanceOf(MpMapSource.class));
|
||||
assertThat(asList.get(1), instanceOf(MpMapSource.class));
|
||||
|
||||
// first is the one with higher config ordinal
|
||||
ConfigSource map = asList.get(0);
|
||||
assertThat(map.getValue("app.storageEnabled"), is("true"));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mutableTest() {
|
||||
// THIS MUST WORK - the spec says the sources can be mutable and config must use the latest values
|
||||
var mutable = new MutableConfigSource();
|
||||
|
||||
Config config = ConfigProviderResolver.instance().getBuilder()
|
||||
.withSources(mutable)
|
||||
.build();
|
||||
|
||||
String value = config.getValue("key", String.class);
|
||||
assertThat(value, is("initial"));
|
||||
|
||||
String updated = "updated";
|
||||
mutable.set(updated);
|
||||
value = config.getValue("key", String.class);
|
||||
assertThat(value, is(updated));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayTest() {
|
||||
MutableConfigSource cs = new MutableConfigSource();
|
||||
cs.set("large:cheese\\,mushroom,medium:chicken,small:pepperoni");
|
||||
Config config = ConfigProviderResolver.instance().getBuilder()
|
||||
.withConverter(Pizza.class, 10, value -> {
|
||||
String[] parts = value.split(":");
|
||||
if (parts.length == 2) {
|
||||
String size = parts[0];
|
||||
String flavor = parts[1];
|
||||
return new Pizza(flavor, size);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.withSources(cs)
|
||||
.build();
|
||||
|
||||
Pizza[] value = config.getValue("key",
|
||||
Pizza[].class);
|
||||
|
||||
assertThat(value, notNullValue());
|
||||
assertThat(value, arrayWithSize(3));
|
||||
assertThat(value, is(new Pizza[] {
|
||||
new Pizza("cheese,mushroom", "large"),
|
||||
new Pizza("chicken", "medium"),
|
||||
new Pizza("pepperoni", "small")
|
||||
}));
|
||||
}
|
||||
|
||||
private static class MutableConfigSource implements ConfigSource {
|
||||
private final AtomicReference<String> value = new AtomicReference<>("initial");
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return Map.of("key", value.get());
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReturnOfNull")
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
if ("key".equals(propertyName)) {
|
||||
return value.get();
|
||||
}
|
||||
// this is required by the specification (null returns if not found)
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
private void set(String value) {
|
||||
this.value.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Pizza {
|
||||
private final String flavor;
|
||||
private final String size;
|
||||
|
||||
private Pizza(String flavour, String size) {
|
||||
this.flavor = flavour;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return flavor + ":" + size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Pizza pizza = (Pizza) o;
|
||||
return flavor.equals(pizza.flavor) &&
|
||||
size.equals(pizza.size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(flavor, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,3 +27,7 @@ referencing2-3=ref-${value2}
|
||||
referencing2-4=ref-${value2}-ref
|
||||
|
||||
referencing3-1=${value1}-${value2}
|
||||
|
||||
referencing4-1=${missing}
|
||||
referencing4-2=${missing}-${value1}
|
||||
|
||||
@@ -54,10 +54,6 @@
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-media-type</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
@@ -17,24 +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.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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, org.eclipse.microprofile.config.Config {
|
||||
abstract class AbstractConfigImpl implements Config {
|
||||
|
||||
public static final Logger LOGGER = Logger.getLogger(AbstractConfigImpl.class.getName());
|
||||
|
||||
@@ -45,8 +38,6 @@ abstract class AbstractConfigImpl implements Config, org.eclipse.microprofile.co
|
||||
private final Type type;
|
||||
private final Context context;
|
||||
private final ConfigMapperManager mapperManager;
|
||||
private final boolean useSystemProperties;
|
||||
private final boolean useEnvironmentVariables;
|
||||
|
||||
/**
|
||||
* Initializes Config implementation.
|
||||
@@ -73,26 +64,6 @@ abstract class AbstractConfigImpl implements Config, org.eclipse.microprofile.co
|
||||
this.type = type;
|
||||
|
||||
context = new NodeContextImpl();
|
||||
|
||||
boolean sysProps = false;
|
||||
boolean envVars = false;
|
||||
int index = 0;
|
||||
for (ConfigSourceRuntimeBase configSource : factory.configSources()) {
|
||||
if (index == 0 && configSource.isSystemProperties()) {
|
||||
sysProps = true;
|
||||
}
|
||||
if (configSource.isEnvironmentVariables()) {
|
||||
envVars = true;
|
||||
}
|
||||
|
||||
if (sysProps && envVars) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
this.useEnvironmentVariables = envVars;
|
||||
this.useSystemProperties = sysProps;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,117 +135,6 @@ abstract class AbstractConfigImpl implements Config, org.eclipse.microprofile.co
|
||||
return asList(Config.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* MicroProfile Config methods
|
||||
*/
|
||||
@Override
|
||||
public <T> T getValue(String propertyName, Class<T> propertyType) {
|
||||
Config config = factory.context().last();
|
||||
try {
|
||||
return mpFindValue(config, propertyName, propertyType);
|
||||
} catch (MissingValueException e) {
|
||||
throw new NoSuchElementException(e.getMessage());
|
||||
} catch (ConfigMappingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType) {
|
||||
try {
|
||||
return Optional.of(getValue(propertyName, propertyType));
|
||||
} catch (NoSuchElementException e) {
|
||||
return Optional.empty();
|
||||
} catch (ConfigMappingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getPropertyNames() {
|
||||
Set<String> keys = new HashSet<>(factory.context().last()
|
||||
.asMap()
|
||||
.orElseGet(Collections::emptyMap)
|
||||
.keySet());
|
||||
|
||||
if (useSystemProperties) {
|
||||
keys.addAll(System.getProperties().stringPropertyNames());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigSource> getConfigSources() {
|
||||
Config config = factory.context().last();
|
||||
if (null == config) {
|
||||
// maybe we are in progress of initializing this config (e.g. filter processing)
|
||||
config = this;
|
||||
}
|
||||
|
||||
if (config instanceof AbstractConfigImpl) {
|
||||
return ((AbstractConfigImpl) config).mpConfigSources();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Iterable<ConfigSource> mpConfigSources() {
|
||||
return new LinkedList<>(factory.mpConfigSources());
|
||||
}
|
||||
|
||||
private <T> T mpFindValue(Config config, String propertyName, Class<T> propertyType) {
|
||||
// this is a workaround TCK tests that expect system properties to be mutable
|
||||
// Helidon config does the same, yet with a slight delay (polling reasons)
|
||||
// we need to check if system properties are enabled and first - if so, do this
|
||||
|
||||
String property = null;
|
||||
if (useSystemProperties) {
|
||||
property = System.getProperty(propertyName);
|
||||
}
|
||||
|
||||
if (null == property) {
|
||||
ConfigValue<T> value = config
|
||||
.get(propertyName)
|
||||
.as(propertyType);
|
||||
|
||||
if (value.isPresent()) {
|
||||
return value.get();
|
||||
}
|
||||
|
||||
// try to find in env vars
|
||||
if (useEnvironmentVariables) {
|
||||
T envVar = mpFindEnvVar(config, propertyName, propertyType);
|
||||
if (null != envVar) {
|
||||
return envVar;
|
||||
}
|
||||
}
|
||||
|
||||
return value.get();
|
||||
} else {
|
||||
return config.get(propertyName).convert(propertyType, property);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T mpFindEnvVar(Config config, String propertyName, Class<T> propertyType) {
|
||||
String result = System.getenv(propertyName);
|
||||
|
||||
// now let's resolve all variants required by the specification
|
||||
if (null == result) {
|
||||
for (String alias : EnvironmentVariableAliases.aliasesOf(propertyName)) {
|
||||
result = System.getenv(alias);
|
||||
if (null != result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null != result) {
|
||||
return config.convert(propertyType, result);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Config contextConfig(Config rootConfig) {
|
||||
return rootConfig
|
||||
.get(AbstractConfigImpl.this.prefix)
|
||||
@@ -312,7 +172,5 @@ abstract class AbstractConfigImpl implements Config, org.eclipse.microprofile.co
|
||||
public Config reload() {
|
||||
return AbstractConfigImpl.this.contextConfig(AbstractConfigImpl.this.factory.context().reload());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract class AbstractConfigSourceBuilder<B extends AbstractConfigSource
|
||||
private Function<Config.Key, Optional<ConfigParser>> parserMapping;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private B me = (B) this;
|
||||
private final B me = (B) this;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
||||
@@ -34,7 +34,7 @@ import io.helidon.config.spi.ConfigNode.ValueNode;
|
||||
public abstract class AbstractNodeBuilderImpl<ID, B> {
|
||||
|
||||
private final B thisBuilder;
|
||||
private Function<String, String> tokenResolver;
|
||||
private final Function<String, String> tokenResolver;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
AbstractNodeBuilderImpl(Function<String, String> tokenResolver) {
|
||||
@@ -80,13 +80,6 @@ public abstract class AbstractNodeBuilderImpl<ID, B> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Human readable description of current builder implementation to be used in logs and exception messages.
|
||||
*
|
||||
* @return builder description
|
||||
*/
|
||||
protected abstract String typeDescription();
|
||||
|
||||
/**
|
||||
* Returns id computed from key.
|
||||
*
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract class AbstractSourceBuilder<B extends AbstractSourceBuilder<B, U
|
||||
private boolean optional = false;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private B me = (B) this;
|
||||
private final B me = (B) this;
|
||||
|
||||
/**
|
||||
* Configure builder from meta configuration.
|
||||
|
||||
@@ -16,11 +16,8 @@
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -51,11 +48,6 @@ import io.helidon.config.spi.ConfigSource;
|
||||
import io.helidon.config.spi.MergingStrategy;
|
||||
import io.helidon.config.spi.OverrideSource;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
import static org.eclipse.microprofile.config.spi.ConfigSource.CONFIG_ORDINAL;
|
||||
|
||||
/**
|
||||
* {@link Config} Builder implementation.
|
||||
*/
|
||||
@@ -69,11 +61,9 @@ class BuilderImpl implements Config.Builder {
|
||||
*/
|
||||
// sources to be sorted by priority
|
||||
private final List<HelidonSourceWithPriority> prioritizedSources = new ArrayList<>();
|
||||
private final List<PrioritizedMpSource> prioritizedMpSources = new ArrayList<>();
|
||||
// sources "pre-sorted" - all user defined sources without priority will be ordered
|
||||
// as added, as well as config sources from meta configuration
|
||||
private final List<ConfigSource> sources = new LinkedList<>();
|
||||
private boolean configSourceServicesEnabled;
|
||||
// to use when more than one source is configured
|
||||
private MergingStrategy mergingStrategy = MergingStrategy.fallback();
|
||||
private boolean hasSystemPropertiesSource;
|
||||
@@ -84,7 +74,6 @@ class BuilderImpl implements Config.Builder {
|
||||
private final List<PrioritizedMapperProvider> prioritizedMappers = new ArrayList<>();
|
||||
private final MapperProviders mapperProviders;
|
||||
private boolean mapperServicesEnabled;
|
||||
private boolean mpMapperServicesEnabled;
|
||||
/*
|
||||
* Config parsers
|
||||
*/
|
||||
@@ -104,7 +93,7 @@ class BuilderImpl implements Config.Builder {
|
||||
* Other configuration.
|
||||
*/
|
||||
private OverrideSource overrideSource;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
/*
|
||||
* Other switches
|
||||
*/
|
||||
@@ -114,15 +103,11 @@ class BuilderImpl implements Config.Builder {
|
||||
private boolean systemPropertiesSourceEnabled;
|
||||
private boolean environmentVariablesSourceEnabled;
|
||||
private boolean envVarAliasGeneratorEnabled;
|
||||
private boolean mpDiscoveredSourcesAdded;
|
||||
private boolean mpDiscoveredConvertersAdded;
|
||||
|
||||
BuilderImpl() {
|
||||
configSourceServicesEnabled = true;
|
||||
overrideSource = OverrideSources.empty();
|
||||
mapperProviders = MapperProviders.create();
|
||||
mapperServicesEnabled = true;
|
||||
mpMapperServicesEnabled = true;
|
||||
parsers = new ArrayList<>();
|
||||
parserServicesEnabled = true;
|
||||
filterProviders = new ArrayList<>();
|
||||
@@ -135,12 +120,6 @@ class BuilderImpl implements Config.Builder {
|
||||
envVarAliasGeneratorEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Builder disableSourceServices() {
|
||||
this.configSourceServicesEnabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Builder sources(List<Supplier<? extends ConfigSource>> sourceSuppliers) {
|
||||
// replace current config sources with the ones provided
|
||||
@@ -178,15 +157,14 @@ class BuilderImpl implements Config.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
void disableMpMapperServices() {
|
||||
this.mpMapperServicesEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Config.Builder addStringMapper(Class<T> type, Function<String, T> mapper) {
|
||||
Objects.requireNonNull(type);
|
||||
Objects.requireNonNull(mapper);
|
||||
|
||||
if (String.class.equals(type)) {
|
||||
return this;
|
||||
}
|
||||
addMapper(type, config -> mapper.apply(config.asString().get()));
|
||||
|
||||
return this;
|
||||
@@ -319,14 +297,6 @@ class BuilderImpl implements Config.Builder {
|
||||
if (null == changesExecutor) {
|
||||
changesExecutor = Executors.newCachedThreadPool(new ConfigThreadFactory("config-changes"));
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/*
|
||||
* Now prepare the config runtime.
|
||||
@@ -390,7 +360,6 @@ class BuilderImpl implements Config.Builder {
|
||||
metaConfig.get("key-resolving.enabled").asBoolean().ifPresent(this::keyResolvingEnabled);
|
||||
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);
|
||||
|
||||
disableSystemPropertiesSource();
|
||||
disableEnvironmentVariablesSource();
|
||||
@@ -418,129 +387,6 @@ class BuilderImpl implements Config.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void configSourceServicesEnabled(boolean enabled) {
|
||||
this.configSourceServicesEnabled = enabled;
|
||||
}
|
||||
|
||||
void mpWithConverters(Converter<?>... converters) {
|
||||
for (Converter<?> converter : converters) {
|
||||
addMpConverter(converter);
|
||||
}
|
||||
}
|
||||
|
||||
<T> void mpWithConverter(Class<T> type, int ordinal, Converter<T> converter) {
|
||||
// priority 1 is highest, 100 is default
|
||||
// MP ordinal 1 is lowest, 100 is default
|
||||
|
||||
// 100 - priority 1
|
||||
// 101 - priority 0
|
||||
int priority = 101 - ordinal;
|
||||
prioritizedMappers.add(new MpConverterWrapper(type, converter, priority));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> void addMpConverter(Converter<T> converter) {
|
||||
Class<T> type = (Class<T>) getTypeOfMpConverter(converter.getClass());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("Converter " + converter.getClass() + " must be a ParameterizedType");
|
||||
}
|
||||
|
||||
mpWithConverter(type,
|
||||
Priorities.find(converter.getClass(), 100),
|
||||
converter);
|
||||
}
|
||||
|
||||
void mpAddDefaultSources() {
|
||||
hasEnvVarSource = true;
|
||||
hasSystemPropertiesSource = true;
|
||||
|
||||
prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.systemProperties().build(), 100));
|
||||
prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.environmentVariables(), 100));
|
||||
prioritizedSources.add(new HelidonSourceWithPriority(ConfigSources.classpath("application.yaml")
|
||||
.optional(true)
|
||||
.build(), 100));
|
||||
|
||||
ConfigSources.classpathAll("META-INF/microprofile-config.properties")
|
||||
.stream()
|
||||
.map(io.helidon.common.Builder::build)
|
||||
.map(source -> new HelidonSourceWithPriority(source, 100))
|
||||
.forEach(prioritizedSources::add);
|
||||
}
|
||||
|
||||
void mpAddDiscoveredSources() {
|
||||
if (mpDiscoveredSourcesAdded) {
|
||||
return;
|
||||
}
|
||||
this.mpDiscoveredSourcesAdded = true;
|
||||
this.configSourceServicesEnabled = true;
|
||||
final ClassLoader usedCl = ((null == classLoader) ? Thread.currentThread().getContextClassLoader() : classLoader);
|
||||
|
||||
List<org.eclipse.microprofile.config.spi.ConfigSource> mpSources = new LinkedList<>();
|
||||
|
||||
// service loader MP sources
|
||||
HelidonServiceLoader
|
||||
.create(ServiceLoader.load(org.eclipse.microprofile.config.spi.ConfigSource.class, usedCl))
|
||||
.forEach(mpSources::add);
|
||||
|
||||
// config source providers
|
||||
HelidonServiceLoader.create(ServiceLoader.load(ConfigSourceProvider.class, usedCl))
|
||||
.forEach(csp -> csp.getConfigSources(usedCl)
|
||||
.forEach(mpSources::add));
|
||||
|
||||
for (org.eclipse.microprofile.config.spi.ConfigSource source : mpSources) {
|
||||
prioritizedMpSources.add(new PrioritizedMpSource(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) {
|
||||
if (source instanceof AbstractConfigSource) {
|
||||
prioritizedSources.add(new HelidonSourceWithPriority((ConfigSource) source, null));
|
||||
} else {
|
||||
prioritizedMpSources.add(new PrioritizedMpSource(source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -558,7 +404,7 @@ class BuilderImpl implements Config.Builder {
|
||||
}
|
||||
|
||||
private ConfigSourcesRuntime buildConfigSources(ConfigContextImpl context) {
|
||||
List<ConfigSourceRuntimeBase> targetSources = new LinkedList<>();
|
||||
List<ConfigSourceRuntimeImpl> targetSources = new LinkedList<>();
|
||||
|
||||
if (systemPropertiesSourceEnabled && !hasSystemPropertiesSource) {
|
||||
hasSystemPropertiesSource = true;
|
||||
@@ -574,7 +420,7 @@ class BuilderImpl implements Config.Builder {
|
||||
envVarAliasGeneratorEnabled = true;
|
||||
}
|
||||
|
||||
boolean nothingConfigured = sources.isEmpty() && prioritizedSources.isEmpty() && prioritizedMpSources.isEmpty();
|
||||
boolean nothingConfigured = sources.isEmpty() && prioritizedSources.isEmpty();
|
||||
|
||||
if (nothingConfigured) {
|
||||
// use meta configuration to load all sources
|
||||
@@ -598,10 +444,10 @@ class BuilderImpl implements Config.Builder {
|
||||
return new ConfigSourcesRuntime(targetSources, mergingStrategy);
|
||||
}
|
||||
|
||||
private List<ConfigSourceRuntimeBase> mergePrioritized(ConfigContextImpl context) {
|
||||
List<PrioritizedConfigSource> allPrioritized = new ArrayList<>(this.prioritizedMpSources);
|
||||
private List<ConfigSourceRuntimeImpl> mergePrioritized(ConfigContextImpl context) {
|
||||
List<PrioritizedConfigSource> allPrioritized = new ArrayList<>();
|
||||
prioritizedSources.stream()
|
||||
.map(it -> new PrioritizedHelidonSource(it, context))
|
||||
.map(it -> new PrioritizedConfigSource(it, context))
|
||||
.forEach(allPrioritized::add);
|
||||
|
||||
Priorities.sort(allPrioritized);
|
||||
@@ -708,7 +554,7 @@ class BuilderImpl implements Config.Builder {
|
||||
.build()
|
||||
.asList()
|
||||
.stream()
|
||||
.map(filter -> (Function<Config, ConfigFilter>) (Config t) -> filter)
|
||||
.map(LoadedFilterProvider::new)
|
||||
.forEach(this::addFilter);
|
||||
}
|
||||
|
||||
@@ -716,9 +562,7 @@ class BuilderImpl implements Config.Builder {
|
||||
* {@link ConfigContext} implementation.
|
||||
*/
|
||||
static class ConfigContextImpl implements ConfigContext {
|
||||
private final Map<ConfigSource, ConfigSourceRuntimeBase> runtimes = new IdentityHashMap<>();
|
||||
private final Map<org.eclipse.microprofile.config.spi.ConfigSource, ConfigSourceRuntimeBase> mpRuntimes
|
||||
= new IdentityHashMap<>();
|
||||
private final Map<ConfigSource, ConfigSourceRuntimeImpl> runtimes = new IdentityHashMap<>();
|
||||
|
||||
private final Executor changesExecutor;
|
||||
private final List<ConfigParser> configParsers;
|
||||
@@ -733,7 +577,7 @@ class BuilderImpl implements Config.Builder {
|
||||
return sourceRuntimeBase(source);
|
||||
}
|
||||
|
||||
private ConfigSourceRuntimeBase sourceRuntimeBase(ConfigSource source) {
|
||||
private ConfigSourceRuntimeImpl sourceRuntimeBase(ConfigSource source) {
|
||||
return runtimes.computeIfAbsent(source, it -> new ConfigSourceRuntimeImpl(this, source));
|
||||
}
|
||||
|
||||
@@ -745,10 +589,6 @@ class BuilderImpl implements Config.Builder {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
ConfigSourceRuntimeBase sourceRuntime(org.eclipse.microprofile.config.spi.ConfigSource source) {
|
||||
return mpRuntimes.computeIfAbsent(source, it -> new ConfigSourceMpRuntimeImpl(source));
|
||||
}
|
||||
|
||||
Executor changesExecutor() {
|
||||
return changesExecutor;
|
||||
}
|
||||
@@ -767,7 +607,6 @@ class BuilderImpl implements Config.Builder {
|
||||
// config sources
|
||||
.sources(ConfigSources.empty())
|
||||
.overrides(OverrideSources.empty())
|
||||
.disableSourceServices()
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.disableParserServices()
|
||||
@@ -800,35 +639,6 @@ class BuilderImpl implements Config.Builder {
|
||||
ConfigMapperProvider {
|
||||
}
|
||||
|
||||
private static final class MpConverterWrapper implements PrioritizedMapperProvider {
|
||||
private final Map<Class<?>, Function<Config, ?>> converterMap = new HashMap<>();
|
||||
private final Converter<?> converter;
|
||||
private final int priority;
|
||||
|
||||
private MpConverterWrapper(Class<?> theClass,
|
||||
Converter<?> converter,
|
||||
int priority) {
|
||||
this.converter = converter;
|
||||
this.priority = priority;
|
||||
this.converterMap.put(theClass, config -> config.asString().as(converter::convert).get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, Function<Config, ?>> mappers() {
|
||||
return converterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return converter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HelidonMapperWrapper implements PrioritizedMapperProvider {
|
||||
private final ConfigMapperProvider delegate;
|
||||
private final int priority;
|
||||
@@ -869,56 +679,16 @@ class BuilderImpl implements Config.Builder {
|
||||
}
|
||||
}
|
||||
|
||||
private interface PrioritizedConfigSource extends Prioritized {
|
||||
ConfigSourceRuntimeBase runtime(ConfigContextImpl context);
|
||||
}
|
||||
|
||||
private static final class PrioritizedMpSource implements PrioritizedConfigSource {
|
||||
private final org.eclipse.microprofile.config.spi.ConfigSource delegate;
|
||||
|
||||
private PrioritizedMpSource(org.eclipse.microprofile.config.spi.ConfigSource delegate) {
|
||||
this.delegate = delegate;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSourceRuntimeBase runtime(ConfigContextImpl context) {
|
||||
return context.sourceRuntime(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
// MP config is using "ordinals" - the higher the number, the more important it is
|
||||
// We are using "priorities" - the lower the number, the more important it is
|
||||
String value = delegate.getValue(CONFIG_ORDINAL);
|
||||
|
||||
int priority;
|
||||
|
||||
if (null != value) {
|
||||
priority = Integer.parseInt(value);
|
||||
} else {
|
||||
priority = Priorities.find(delegate, 100);
|
||||
}
|
||||
|
||||
// priority from Prioritized and annotation (MP has it reversed)
|
||||
// it is a tough call how to merge priorities and ordinals
|
||||
// now we use a "101" as a constant, so components with ordinal 100 will have
|
||||
// priority of 1
|
||||
return 101 - priority;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PrioritizedHelidonSource implements PrioritizedConfigSource {
|
||||
private static final class PrioritizedConfigSource implements Prioritized {
|
||||
private final HelidonSourceWithPriority source;
|
||||
private final ConfigContext context;
|
||||
|
||||
private PrioritizedHelidonSource(HelidonSourceWithPriority source, ConfigContext context) {
|
||||
private PrioritizedConfigSource(HelidonSourceWithPriority source, ConfigContext context) {
|
||||
this.source = source;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSourceRuntimeBase runtime(ConfigContextImpl context) {
|
||||
private ConfigSourceRuntimeImpl runtime(ConfigContextImpl context) {
|
||||
return context.sourceRuntimeBase(source.unwrap());
|
||||
}
|
||||
|
||||
@@ -949,7 +719,7 @@ class BuilderImpl implements Config.Builder {
|
||||
|
||||
// ordinal from data
|
||||
return context.sourceRuntime(configSource)
|
||||
.node(CONFIG_ORDINAL)
|
||||
.node("config_priority")
|
||||
.flatMap(node -> node.value()
|
||||
.map(Integer::parseInt))
|
||||
.orElseGet(() -> {
|
||||
@@ -959,4 +729,21 @@ class BuilderImpl implements Config.Builder {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LoadedFilterProvider implements Function<Config, ConfigFilter> {
|
||||
private final ConfigFilter filter;
|
||||
|
||||
private LoadedFilterProvider(ConfigFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigFilter apply(Config config) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return filter.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import io.helidon.config.spi.OverrideSource;
|
||||
* <h1>Configuration</h1>
|
||||
* Immutable tree-structured configuration.
|
||||
* <h2>Loading Configuration</h2>
|
||||
* Load the default configuration using the {@link #create} method.
|
||||
* Load the default configuration using the {@link Config#create} method.
|
||||
* <pre>{@code
|
||||
* Config config = Config.create();
|
||||
* }</pre> Use {@link Config.Builder} to construct a new {@code Config} instance
|
||||
@@ -233,7 +233,7 @@ import io.helidon.config.spi.OverrideSource;
|
||||
* <h2><a id="multipleSources">Handling Multiple Configuration
|
||||
* Sources</a></h2>
|
||||
* A {@code Config} instance, including the default {@code Config} returned by
|
||||
* {@link #create}, might be associated with multiple {@link ConfigSource}s. The
|
||||
* {@link Config#create}, might be associated with multiple {@link ConfigSource}s. The
|
||||
* config system merges these together so that values from config sources with higher priority have
|
||||
* precedence over values from config sources with lower priority.
|
||||
*/
|
||||
@@ -809,6 +809,7 @@ public interface Config {
|
||||
//
|
||||
// config changes
|
||||
//
|
||||
|
||||
/**
|
||||
* Register a {@link Consumer} that is invoked each time a change occurs on whole Config or on a particular Config node.
|
||||
* <p>
|
||||
@@ -976,8 +977,8 @@ public interface Config {
|
||||
*/
|
||||
MISSING(false, false);
|
||||
|
||||
private boolean exists;
|
||||
private boolean isLeaf;
|
||||
private final boolean exists;
|
||||
private final boolean isLeaf;
|
||||
|
||||
Type(boolean exists, boolean isLeaf) {
|
||||
this.exists = exists;
|
||||
@@ -1114,14 +1115,6 @@ 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
|
||||
* to be wrapped into {@link Config} API.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -56,7 +56,6 @@ class ConfigDiff {
|
||||
.map(Config::key)
|
||||
.distinct()
|
||||
.flatMap(ConfigDiff::expandKey)
|
||||
.distinct()
|
||||
.collect(toSet());
|
||||
|
||||
return new ConfigDiff(newConfig, changedKeys);
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.helidon.config.spi.ConfigFilter;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
@@ -44,8 +43,6 @@ final class ConfigFactory {
|
||||
private final Function<String, List<String>> aliasGenerator;
|
||||
private final ConcurrentMap<PrefixedKey, AbstractConfigImpl> configCache;
|
||||
private final Instant timestamp;
|
||||
private final List<ConfigSourceRuntimeBase> configSources;
|
||||
private final List<org.eclipse.microprofile.config.spi.ConfigSource> mpConfigSources;
|
||||
|
||||
/**
|
||||
* Create new instance of the factory operating on specified {@link ConfigSource}.
|
||||
@@ -60,8 +57,7 @@ final class ConfigFactory {
|
||||
ObjectNode node,
|
||||
ConfigFilter filter,
|
||||
ProviderImpl provider,
|
||||
Function<String, List<String>> aliasGenerator,
|
||||
List<ConfigSourceRuntimeBase> configSources) {
|
||||
Function<String, List<String>> aliasGenerator) {
|
||||
|
||||
Objects.requireNonNull(mapperManager, "mapperManager argument is null.");
|
||||
Objects.requireNonNull(node, "node argument is null.");
|
||||
@@ -73,14 +69,9 @@ final class ConfigFactory {
|
||||
this.filter = filter;
|
||||
this.provider = provider;
|
||||
this.aliasGenerator = aliasGenerator;
|
||||
this.configSources = configSources;
|
||||
|
||||
configCache = new ConcurrentHashMap<>();
|
||||
timestamp = Instant.now();
|
||||
|
||||
this.mpConfigSources = configSources.stream()
|
||||
.map(ConfigSourceRuntime::asMpSource)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Instant timestamp() {
|
||||
@@ -161,14 +152,6 @@ final class ConfigFactory {
|
||||
return provider;
|
||||
}
|
||||
|
||||
List<ConfigSourceRuntimeBase> configSources() {
|
||||
return configSources;
|
||||
}
|
||||
|
||||
List<org.eclipse.microprofile.config.spi.ConfigSource> mpConfigSources() {
|
||||
return mpConfigSources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix represents detached roots.
|
||||
*/
|
||||
|
||||
@@ -18,10 +18,6 @@ package io.helidon.config;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
@@ -37,26 +33,18 @@ public final class ConfigHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ConfigHelper#subscriber(Function) Flow.Subscriber} that
|
||||
* will delegate {@link Flow.Subscriber#onNext(Object)} to the specified
|
||||
* {@code onNextFunction} function.
|
||||
* <p>
|
||||
* The new subscriber's
|
||||
* {@link Flow.Subscriber#onSubscribe(Flow.Subscription)} method
|
||||
* automatically invokes {@link Flow.Subscription#request(long)} to request
|
||||
* all events that are available in the subscription.
|
||||
* <p>
|
||||
* The caller-provided {@code onNextFunction} should return {@code false} in
|
||||
* order to {@link Flow.Subscription#cancel() cancel} current subscription.
|
||||
* Create a map of keys to string values from an object node.
|
||||
*
|
||||
* @param onNextFunction function to be invoked during {@code onNext}
|
||||
* processing
|
||||
* @param <T> the type of the items provided by the subscription
|
||||
* @return {@code Subscriber} that delegates its {@code onNext} to the
|
||||
* caller-provided function
|
||||
* @param objectNode node to flatten
|
||||
* @return a map of all nodes
|
||||
*/
|
||||
public static <T> Flow.Subscriber<T> subscriber(Function<T, Boolean> onNextFunction) {
|
||||
return new OnNextFunctionSubscriber<>(onNextFunction);
|
||||
public static Map<String, String> flattenNodes(ConfigNode.ObjectNode objectNode) {
|
||||
return ConfigHelper.flattenNodes(ConfigKeyImpl.of(), objectNode)
|
||||
.filter(e -> e.getValue() instanceof ValueNodeImpl)
|
||||
.collect(Collectors.toMap(
|
||||
e -> e.getKey().toString(),
|
||||
e -> Config.Key.escapeName(((ValueNodeImpl) e.getValue()).get())
|
||||
));
|
||||
}
|
||||
|
||||
static Map<ConfigKeyImpl, ConfigNode> createFullKeyToNodeMap(ConfigNode.ObjectNode objectNode) {
|
||||
@@ -89,55 +77,4 @@ public final class ConfigHelper {
|
||||
throw new IllegalArgumentException("Invalid node type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link ConfigHelper#subscriber(Function)}.
|
||||
*
|
||||
* @param <T> the subscribed item type
|
||||
* @see ConfigHelper#subscriber(Function)
|
||||
*/
|
||||
private static class OnNextFunctionSubscriber<T> implements Flow.Subscriber<T> {
|
||||
private final Function<T, Boolean> onNextFunction;
|
||||
private final Logger logger;
|
||||
private Flow.Subscription subscription;
|
||||
|
||||
private OnNextFunctionSubscriber(Function<T, Boolean> onNextFunction) {
|
||||
this.onNextFunction = onNextFunction;
|
||||
this.logger = Logger.getLogger(OnNextFunctionSubscriber.class.getName() + "."
|
||||
+ Integer.toHexString(System.identityHashCode(onNextFunction)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
logger.finest(() -> "onSubscribe: " + subscription);
|
||||
|
||||
this.subscription = subscription;
|
||||
subscription.request(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T item) {
|
||||
boolean cancel = !onNextFunction.apply(item);
|
||||
|
||||
logger.finest(() -> "onNext: " + item + " => " + (cancel ? "CANCEL" : "FOLLOW"));
|
||||
|
||||
if (cancel) {
|
||||
subscription.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
logger.log(Level.WARNING,
|
||||
throwable,
|
||||
() -> "Config Changes support failed. " + throwable.getLocalizedMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
logger.config("Config Changes support finished. There will no other Config reload.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class ConfigLeafImpl extends ConfigExistingImpl<ValueNode> {
|
||||
}
|
||||
|
||||
Optional<String> value = value();
|
||||
if (!value.isPresent()) {
|
||||
if (value.isEmpty()) {
|
||||
return ConfigValues.create(this, Optional::empty, aConfig -> aConfig.asList(type));
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class ConfigLeafImpl extends ConfigExistingImpl<ValueNode> {
|
||||
@Override
|
||||
public <T> ConfigValue<List<T>> asList(Function<Config, T> mapper) throws ConfigMappingException {
|
||||
Optional<String> value = value();
|
||||
if (!value.isPresent()) {
|
||||
if (value.isEmpty()) {
|
||||
return ConfigValues.create(this, Optional::empty, aConfig -> aConfig.asList(mapper));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,10 +44,8 @@ import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -81,100 +79,96 @@ public final class ConfigMappers {
|
||||
}
|
||||
|
||||
private static Map<Class<?>, Function<Config, ?>> initEssentialMappers() {
|
||||
Map<Class<?>, Function<Config, ?>> essentials = new HashMap<>();
|
||||
|
||||
essentials.put(Config.class, (node) -> node);
|
||||
|
||||
essentials.put(String.class, wrap(value -> value));
|
||||
|
||||
essentials.put(OptionalInt.class, (node) -> {
|
||||
if (!node.exists()) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
return OptionalInt.of(wrap(Integer::parseInt).apply(node));
|
||||
});
|
||||
|
||||
essentials.put(OptionalLong.class, (node) -> {
|
||||
if (!node.exists()) {
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
|
||||
return OptionalLong.of(wrap(Long::parseLong).apply(node));
|
||||
});
|
||||
|
||||
essentials.put(OptionalDouble.class, (node) -> {
|
||||
if (!node.exists()) {
|
||||
return OptionalDouble.empty();
|
||||
}
|
||||
|
||||
return OptionalDouble.of(wrap(Double::parseDouble).apply(node));
|
||||
});
|
||||
|
||||
return Collections.unmodifiableMap(essentials);
|
||||
return Map.of(Config.class, (node) -> node,
|
||||
String.class, wrap(value -> value),
|
||||
OptionalInt.class, ConfigMappers::optionalIntEssential,
|
||||
OptionalLong.class, ConfigMappers::optionalLongEssential,
|
||||
OptionalDouble.class, ConfigMappers::optionalDoubleEssential);
|
||||
}
|
||||
|
||||
static Map<Class<?>, Function<Config, ?>> essentialMappers() {
|
||||
return ESSENTIAL_MAPPERS;
|
||||
}
|
||||
|
||||
private static OptionalDouble optionalDoubleEssential(Config node) {
|
||||
if (!node.exists()) {
|
||||
return OptionalDouble.empty();
|
||||
}
|
||||
|
||||
return OptionalDouble.of(wrap(Double::parseDouble).apply(node));
|
||||
}
|
||||
|
||||
private static OptionalLong optionalLongEssential(Config node) {
|
||||
if (!node.exists()) {
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
|
||||
return OptionalLong.of(wrap(Long::parseLong).apply(node));
|
||||
}
|
||||
|
||||
private static OptionalInt optionalIntEssential(Config node) {
|
||||
if (!node.exists()) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
return OptionalInt.of(wrap(Integer::parseInt).apply(node));
|
||||
}
|
||||
|
||||
private static Map<Class<?>, Function<Config, ?>> initBuiltInMappers() {
|
||||
Map<Class<?>, Function<Config, ?>> builtIns = new HashMap<>();
|
||||
|
||||
//primitive types
|
||||
builtIns.put(Byte.class, wrap(ConfigMappers::toByte));
|
||||
builtIns.put(Short.class, wrap(ConfigMappers::toShort));
|
||||
builtIns.put(Integer.class, wrap(ConfigMappers::toInt));
|
||||
builtIns.put(Long.class, wrap(ConfigMappers::toLong));
|
||||
builtIns.put(Float.class, wrap(ConfigMappers::toFloat));
|
||||
builtIns.put(Double.class, wrap(ConfigMappers::toDouble));
|
||||
builtIns.put(Boolean.class, wrap(ConfigMappers::toBoolean));
|
||||
builtIns.put(Character.class, wrap(ConfigMappers::toChar));
|
||||
//java.lang
|
||||
builtIns.put(Class.class, wrap(ConfigMappers::toClass));
|
||||
//javax.math
|
||||
builtIns.put(BigDecimal.class, wrap(ConfigMappers::toBigDecimal));
|
||||
builtIns.put(BigInteger.class, wrap(ConfigMappers::toBigInteger));
|
||||
//java.time
|
||||
builtIns.put(Duration.class, wrap(ConfigMappers::toDuration));
|
||||
builtIns.put(Period.class, wrap(ConfigMappers::toPeriod));
|
||||
builtIns.put(LocalDate.class, wrap(ConfigMappers::toLocalDate));
|
||||
builtIns.put(LocalDateTime.class, wrap(ConfigMappers::toLocalDateTime));
|
||||
builtIns.put(LocalTime.class, wrap(ConfigMappers::toLocalTime));
|
||||
builtIns.put(ZonedDateTime.class, wrap(ConfigMappers::toZonedDateTime));
|
||||
builtIns.put(ZoneId.class, wrap(ConfigMappers::toZoneId));
|
||||
builtIns.put(ZoneOffset.class, wrap(ConfigMappers::toZoneOffset));
|
||||
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
|
||||
builtIns.put(Path.class, wrap(ConfigMappers::toPath));
|
||||
builtIns.put(Charset.class, wrap(ConfigMappers::toCharset));
|
||||
//java.net
|
||||
builtIns.put(URI.class, wrap(ConfigMappers::toUri));
|
||||
builtIns.put(URL.class, wrap(ConfigMappers::toUrl));
|
||||
//java.util
|
||||
builtIns.put(Pattern.class, wrap(ConfigMappers::toPattern));
|
||||
builtIns.put(UUID.class, wrap(ConfigMappers::toUUID));
|
||||
builtIns.put(Map.class, wrapMapper(ConfigMappers::toMap));
|
||||
builtIns.put(Properties.class, wrapMapper(ConfigMappers::toProperties));
|
||||
return Map.ofEntries(Map.entry(Byte.class, wrap(ConfigMappers::toByte)),
|
||||
Map.entry(Short.class, wrap(ConfigMappers::toShort)),
|
||||
Map.entry(Integer.class, wrap(ConfigMappers::toInt)),
|
||||
Map.entry(Long.class, wrap(ConfigMappers::toLong)),
|
||||
Map.entry(Float.class, wrap(ConfigMappers::toFloat)),
|
||||
Map.entry(Double.class, wrap(ConfigMappers::toDouble)),
|
||||
Map.entry(Boolean.class, wrap(ConfigMappers::toBoolean)),
|
||||
Map.entry(Character.class, wrap(ConfigMappers::toChar)),
|
||||
//java.lang
|
||||
Map.entry(Class.class, wrap(ConfigMappers::toClass)),
|
||||
//javax.math
|
||||
Map.entry(BigDecimal.class, wrap(ConfigMappers::toBigDecimal)),
|
||||
Map.entry(BigInteger.class, wrap(ConfigMappers::toBigInteger)),
|
||||
//java.time
|
||||
Map.entry(Duration.class, wrap(ConfigMappers::toDuration)),
|
||||
Map.entry(Period.class, wrap(ConfigMappers::toPeriod)),
|
||||
Map.entry(LocalDate.class, wrap(ConfigMappers::toLocalDate)),
|
||||
Map.entry(LocalDateTime.class, wrap(ConfigMappers::toLocalDateTime)),
|
||||
Map.entry(LocalTime.class, wrap(ConfigMappers::toLocalTime)),
|
||||
Map.entry(ZonedDateTime.class, wrap(ConfigMappers::toZonedDateTime)),
|
||||
Map.entry(ZoneId.class, wrap(ConfigMappers::toZoneId)),
|
||||
Map.entry(ZoneOffset.class, wrap(ConfigMappers::toZoneOffset)),
|
||||
Map.entry(Instant.class, wrap(ConfigMappers::toInstant)),
|
||||
Map.entry(OffsetTime.class, wrap(ConfigMappers::toOffsetTime)),
|
||||
Map.entry(OffsetDateTime.class, wrap(ConfigMappers::toOffsetDateTime)),
|
||||
Map.entry(YearMonth.class, wrap(YearMonth::parse)),
|
||||
//java.io
|
||||
Map.entry(File.class, wrap(ConfigMappers::toFile)),
|
||||
//java.nio
|
||||
Map.entry(Path.class, wrap(ConfigMappers::toPath)),
|
||||
Map.entry(Charset.class, wrap(ConfigMappers::toCharset)),
|
||||
//java.net
|
||||
Map.entry(URI.class, wrap(ConfigMappers::toUri)),
|
||||
Map.entry(URL.class, wrap(ConfigMappers::toUrl)),
|
||||
//java.util
|
||||
Map.entry(Pattern.class, wrap(ConfigMappers::toPattern)),
|
||||
Map.entry(UUID.class, wrap(ConfigMappers::toUUID)),
|
||||
Map.entry(Map.class, wrapMapper(ConfigMappers::toMap)),
|
||||
Map.entry(Properties.class, wrapMapper(ConfigMappers::toProperties)),
|
||||
|
||||
// obsolete stuff
|
||||
//noinspection UseOfObsoleteDateTimeApi,deprecation
|
||||
builtIns.put(Date.class, wrap(ConfigMappers::toDate));
|
||||
//noinspection UseOfObsoleteDateTimeApi,deprecation
|
||||
builtIns.put(Calendar.class, wrap(ConfigMappers::toCalendar));
|
||||
//noinspection UseOfObsoleteDateTimeApi,deprecation
|
||||
builtIns.put(GregorianCalendar.class, wrap(ConfigMappers::toGregorianCalendar));
|
||||
//noinspection UseOfObsoleteDateTimeApi,deprecation
|
||||
builtIns.put(TimeZone.class, wrap(ConfigMappers::toTimeZone));
|
||||
//noinspection UseOfObsoleteDateTimeApi,deprecation
|
||||
builtIns.put(SimpleTimeZone.class, wrap(ConfigMappers::toSimpleTimeZone));
|
||||
|
||||
return Collections.unmodifiableMap(builtIns);
|
||||
// obsolete stuff
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
Map.entry(Date.class, wrap(ConfigMappers::toDate)),
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
Map.entry(Calendar.class, wrap(ConfigMappers::toCalendar)),
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
Map.entry(GregorianCalendar.class, wrap(ConfigMappers::toGregorianCalendar)),
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
Map.entry(TimeZone.class, wrap(ConfigMappers::toTimeZone)),
|
||||
// noinspection UseOfObsoleteDateTimeApi
|
||||
Map.entry(SimpleTimeZone.class, wrap(ConfigMappers::toSimpleTimeZone)));
|
||||
}
|
||||
|
||||
static Map<Class<?>, Function<Config, ?>> builtInMappers() {
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
import static io.helidon.config.AbstractConfigImpl.LOGGER;
|
||||
|
||||
class ConfigSourceMpRuntimeImpl extends ConfigSourceRuntimeBase {
|
||||
private final ConfigSource source;
|
||||
|
||||
ConfigSourceMpRuntimeImpl(ConfigSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazy() {
|
||||
// MP config sources are considered eager
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(BiConsumer<String, ConfigNode> change) {
|
||||
try {
|
||||
// this is not a documented feature
|
||||
// it is to enable MP config sources to be "mutable" in Helidon
|
||||
// this requires some design decisions (and clarification of the MP Config Specification), as this
|
||||
// is open to different interpretations for now
|
||||
Method method = source.getClass().getMethod("registerChangeListener", BiConsumer.class);
|
||||
BiConsumer<String, String> mpListener = (key, value) -> change.accept(key, ConfigNode.ValueNode.create(value));
|
||||
|
||||
method.invoke(source, mpListener);
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.finest("No registerChangeListener(BiConsumer) method found on " + source.getClass() + ", change"
|
||||
+ " support not enabled for this config source (" + source.getName() + ")");
|
||||
} catch (IllegalAccessException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot invoke registerChangeListener(BiConsumer) method on " + source.getClass() + ", "
|
||||
+ "change support not enabled for this config source ("
|
||||
+ source.getName() + ")", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
LOGGER.log(Level.WARNING, "Invocation of registerChangeListener(BiConsumer) method on " + source.getClass()
|
||||
+ " failed with an exception, "
|
||||
+ "change support not enabled for this config source ("
|
||||
+ source.getName() + ")", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConfigNode.ObjectNode> load() {
|
||||
return Optional.of(ConfigUtils.mapToObjectNode(source.getProperties(), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ConfigNode> node(String key) {
|
||||
String value = source.getValue(key);
|
||||
|
||||
if (null == value) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(ConfigNode.ValueNode.create(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigSource asMpSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return source.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean changesSupported() {
|
||||
// supported through a known method signature
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,8 +20,6 @@ import java.util.function.BiConsumer;
|
||||
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
/**
|
||||
* The runtime of a config source. For a single {@link Config}, there is one source runtime for each configured
|
||||
* config source.
|
||||
@@ -54,13 +52,6 @@ public interface ConfigSourceRuntime {
|
||||
*/
|
||||
Optional<ConfigNode> node(String key);
|
||||
|
||||
/**
|
||||
* Get the underlying config source as a MicroProfile {@link org.eclipse.microprofile.config.spi.ConfigSource}.
|
||||
*
|
||||
* @return MP Config source
|
||||
*/
|
||||
ConfigSource asMpSource();
|
||||
|
||||
/**
|
||||
* Description of the underlying config source.
|
||||
* @return description of the source
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
@@ -50,7 +49,7 @@ import io.helidon.config.spi.WatchableSource;
|
||||
* config source.
|
||||
*
|
||||
*/
|
||||
public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements org.eclipse.microprofile.config.spi.ConfigSource {
|
||||
class ConfigSourceRuntimeImpl implements ConfigSourceRuntime {
|
||||
private static final Logger LOGGER = Logger.getLogger(ConfigSourceRuntimeImpl.class.getName());
|
||||
|
||||
private final List<BiConsumer<String, ConfigNode>> listeners = new LinkedList<>();
|
||||
@@ -72,7 +71,6 @@ public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements
|
||||
// for eager sources, this is the data we get initially, everything else is handled through change listeners
|
||||
private Optional<ObjectNode> initialData;
|
||||
private Map<String, ConfigNode> loadedData;
|
||||
private Map<String, String> mpData;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ConfigSourceRuntimeImpl(BuilderImpl.ConfigContextImpl configContext, ConfigSource source) {
|
||||
@@ -182,7 +180,6 @@ public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements
|
||||
return isLazy;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean changesSupported() {
|
||||
return changesSupported;
|
||||
}
|
||||
@@ -233,16 +230,11 @@ public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements
|
||||
this.initialData = loadedData;
|
||||
|
||||
this.loadedData = new HashMap<>();
|
||||
mpData = new HashMap<>();
|
||||
|
||||
initialData.ifPresent(data -> {
|
||||
Map<ConfigKeyImpl, ConfigNode> keyToNodeMap = ConfigHelper.createFullKeyToNodeMap(data);
|
||||
|
||||
keyToNodeMap.forEach((key, value) -> {
|
||||
Optional<String> directValue = value.value();
|
||||
directValue.ifPresent(stringValue -> mpData.put(key.toString(), stringValue));
|
||||
this.loadedData.put(key.toString(), value);
|
||||
});
|
||||
keyToNodeMap.forEach((key, value) -> this.loadedData.put(key.toString(), value));
|
||||
});
|
||||
|
||||
dataLoaded = true;
|
||||
@@ -254,61 +246,16 @@ public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements
|
||||
return singleNodeFunction.apply(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.microprofile.config.spi.ConfigSource asMpSource() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return configSource.description();
|
||||
}
|
||||
|
||||
/*
|
||||
* MP Config related methods
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
if (isSystemProperties()) {
|
||||
// this is a "hack" for MP TCK tests
|
||||
// System properties act as a mutable source for the purpose of MicroProfile
|
||||
Map<String, String> result = new HashMap<>();
|
||||
Properties properties = System.getProperties();
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
result.put(key, properties.getProperty(key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
initialLoad();
|
||||
return new HashMap<>(mpData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
initialLoad();
|
||||
return mpData.get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return configSource.description();
|
||||
}
|
||||
|
||||
/*
|
||||
Runtime impl base
|
||||
*/
|
||||
|
||||
@Override
|
||||
boolean isSystemProperties() {
|
||||
return configSource instanceof ConfigSources.SystemPropertiesConfigSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isEnvironmentVariables() {
|
||||
return configSource instanceof ConfigSources.EnvironmentVariablesConfigSource;
|
||||
}
|
||||
|
||||
private Function<String, Optional<ConfigNode>> objectNodeToSingleNode() {
|
||||
return key -> {
|
||||
if (null == loadedData) {
|
||||
@@ -340,10 +287,6 @@ public class ConfigSourceRuntimeImpl extends ConfigSourceRuntimeBase implements
|
||||
|
||||
}
|
||||
|
||||
ConfigSource unwrap() {
|
||||
return configSource;
|
||||
}
|
||||
|
||||
private static final class PollingStrategyStarter implements Runnable {
|
||||
private final PollingStrategy pollingStrategy;
|
||||
private final PollingStrategyListener listener;
|
||||
|
||||
@@ -37,11 +37,11 @@ import io.helidon.config.spi.MergingStrategy;
|
||||
final class ConfigSourcesRuntime {
|
||||
private final List<RuntimeWithData> loadedData = new LinkedList<>();
|
||||
|
||||
private List<ConfigSourceRuntimeBase> allSources;
|
||||
private MergingStrategy mergingStrategy;
|
||||
private Consumer<Optional<ObjectNode>> changeListener;
|
||||
private final List<ConfigSourceRuntimeImpl> allSources;
|
||||
private final MergingStrategy mergingStrategy;
|
||||
private volatile Consumer<Optional<ObjectNode>> changeListener;
|
||||
|
||||
ConfigSourcesRuntime(List<ConfigSourceRuntimeBase> allSources,
|
||||
ConfigSourcesRuntime(List<ConfigSourceRuntimeImpl> allSources,
|
||||
MergingStrategy mergingStrategy) {
|
||||
this.allSources = allSources;
|
||||
this.mergingStrategy = mergingStrategy;
|
||||
@@ -53,10 +53,6 @@ final class ConfigSourcesRuntime {
|
||||
MergingStrategy.fallback());
|
||||
}
|
||||
|
||||
List<ConfigSourceRuntimeBase> allSources() {
|
||||
return allSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -137,7 +133,7 @@ final class ConfigSourcesRuntime {
|
||||
|
||||
synchronized Optional<ObjectNode> load() {
|
||||
|
||||
for (ConfigSourceRuntimeBase source : allSources) {
|
||||
for (ConfigSourceRuntimeImpl source : allSources) {
|
||||
if (source.isLazy()) {
|
||||
loadedData.add(new RuntimeWithData(source, Optional.empty()));
|
||||
} else {
|
||||
@@ -197,10 +193,10 @@ final class ConfigSourcesRuntime {
|
||||
}
|
||||
|
||||
private static final class RuntimeWithData {
|
||||
private final ConfigSourceRuntimeBase runtime;
|
||||
private final ConfigSourceRuntimeImpl runtime;
|
||||
private Optional<ObjectNode> data;
|
||||
|
||||
private RuntimeWithData(ConfigSourceRuntimeBase runtime, Optional<ObjectNode> data) {
|
||||
private RuntimeWithData(ConfigSourceRuntimeImpl runtime, Optional<ObjectNode> data) {
|
||||
this.runtime = runtime;
|
||||
this.data = data;
|
||||
}
|
||||
@@ -209,7 +205,7 @@ final class ConfigSourcesRuntime {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
private ConfigSourceRuntimeBase runtime() {
|
||||
private ConfigSourceRuntimeImpl runtime() {
|
||||
return runtime;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,24 +19,14 @@ package io.helidon.config;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.time.Duration;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
|
||||
@@ -51,68 +41,6 @@ final class ConfigUtils {
|
||||
throw new AssertionError("Instantiation not allowed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert iterable items to an ordered serial stream.
|
||||
*
|
||||
* @param items items to be streamed.
|
||||
* @param <S> expected streamed item type.
|
||||
* @return stream of items.
|
||||
*/
|
||||
static <S> Stream<S> asStream(Iterable<? extends S> items) {
|
||||
return asStream(items.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an iterator to a stream.
|
||||
*
|
||||
* @param <S> type of the base items
|
||||
* @param iterator iterator over the items
|
||||
* @return stream of the items
|
||||
*/
|
||||
static <S> Stream<S> asStream(Iterator<? extends S> iterator) {
|
||||
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts items represented by an {@link Iterable} instance based on a {@code Priority} annotation attached to each
|
||||
* item's Java type and return the sorted items as an ordered serial {@link Stream}.
|
||||
* <p>
|
||||
* Instances are sorted by {@code Priority.value()} attached <em>directly</em> to the instance of each item's class.
|
||||
* If there is no {@code Priority} annotation attached to an item's class the {@code defaultPriority} value is used
|
||||
* instead. Items with higher priority values have higher priority and take precedence (are returned sooner from
|
||||
* the stream).
|
||||
*
|
||||
* @param items items to be ordered by priority and streamed.
|
||||
* @param defaultPriority default priority to be used in case an item does not have a priority defined.
|
||||
* @param <S> item type.
|
||||
* @return prioritized stream of items.
|
||||
*/
|
||||
static <S> Stream<? extends S> asPrioritizedStream(Iterable<? extends S> items, int defaultPriority) {
|
||||
return asStream(items).sorted(priorityComparator(defaultPriority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comparator for two objects, the classes for which are
|
||||
* optionally annotated with {@link Priority} and which applies a specified
|
||||
* default priority if either or both classes lack the annotation.
|
||||
*
|
||||
* @param <S> type of object being compared
|
||||
* @param defaultPriority used if the classes for either or both objects
|
||||
* lack the {@code Priority} annotation
|
||||
* @return comparator
|
||||
*/
|
||||
static <S> Comparator<S> priorityComparator(int defaultPriority) {
|
||||
return (service1, service2) -> {
|
||||
int service1Priority = Optional.ofNullable(service1.getClass().getAnnotation(Priority.class))
|
||||
.map(Priority::value)
|
||||
.orElse(defaultPriority);
|
||||
int service2Priority = Optional.ofNullable(service2.getClass().getAnnotation(Priority.class))
|
||||
.map(Priority::value)
|
||||
.orElse(defaultPriority);
|
||||
return service2Priority - service1Priority;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds map into object node.
|
||||
* <p>
|
||||
@@ -187,55 +115,4 @@ final class ConfigUtils {
|
||||
throw new ConfigException("Unsupported response content-encoding '" + contentEncoding + "'.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to {@link #schedule()} execution of specified {@code command} using specified {@link ScheduledExecutorService}.
|
||||
* Task is not executed immediately but scheduled with specified {@code delay}.
|
||||
* It is possible to postpone an execution of the command by calling {@link #schedule()} again before the command is finished.
|
||||
* <p>
|
||||
* It can be used to implement Rx Debounce operator (http://reactivex.io/documentation/operators/debounce.html).
|
||||
*/
|
||||
static class ScheduledTask {
|
||||
private final ScheduledExecutorService executorService;
|
||||
private final Runnable command;
|
||||
private final Duration delay;
|
||||
private volatile ScheduledFuture<?> scheduled;
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Initialize task.
|
||||
*
|
||||
* @param executorService service to be used to schedule {@code command} execution on
|
||||
* @param command the command to be executed on {@code executorService}
|
||||
* @param delay the {@code command} is scheduled with specified delay
|
||||
*/
|
||||
ScheduledTask(ScheduledExecutorService executorService, Runnable command, Duration delay) {
|
||||
this.executorService = executorService;
|
||||
this.command = command;
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule execution of {@code command} on specified {@code executorService} with initial {@code delay}.
|
||||
* <p>
|
||||
* Scheduling can be repeated. Not finished task is canceled.
|
||||
*
|
||||
* @return whether a previously-scheduled action was canceled in scheduling this new new action
|
||||
*/
|
||||
public boolean schedule() {
|
||||
boolean result = false;
|
||||
synchronized (lock) {
|
||||
if (scheduled != null) {
|
||||
if (!scheduled.isCancelled() && !scheduled.isDone()) {
|
||||
scheduled.cancel(false);
|
||||
LOGGER.log(Level.FINER, String.format("Cancelling and rescheduling %s task.", command));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
scheduled = executorService.schedule(command, delay.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -172,7 +172,7 @@ public interface ConfigValue<T> {
|
||||
Objects.requireNonNull(supplier);
|
||||
|
||||
Optional<T> optional = asOptional();
|
||||
if (!optional.isPresent()) {
|
||||
if (optional.isEmpty()) {
|
||||
Optional<T> supplied = supplier.get();
|
||||
Objects.requireNonNull(supplied);
|
||||
optional = supplied;
|
||||
@@ -348,6 +348,6 @@ public interface ConfigValue<T> {
|
||||
* @return the optional value as a {@code Stream}
|
||||
*/
|
||||
default Stream<T> stream() {
|
||||
return asOptional().map(Stream::of).orElseGet(Stream::empty);
|
||||
return asOptional().stream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class ConfigValues {
|
||||
* @return a config value that is empty
|
||||
*/
|
||||
public static <T> ConfigValue<T> empty() {
|
||||
return new ConfigValueBase<T>(Config.Key.create("")) {
|
||||
return new ConfigValueBase<>(Config.Key.create("")) {
|
||||
@Override
|
||||
public Optional<T> asOptional() {
|
||||
return Optional.empty();
|
||||
@@ -83,7 +83,7 @@ public final class ConfigValues {
|
||||
* @return a config value that uses the value provided
|
||||
*/
|
||||
public static <T> ConfigValue<T> simpleValue(T value) {
|
||||
return new ConfigValueBase<T>(Config.Key.create("")) {
|
||||
return new ConfigValueBase<>(Config.Key.create("")) {
|
||||
@Override
|
||||
public Optional<T> asOptional() {
|
||||
return Optional.ofNullable(value);
|
||||
|
||||
@@ -104,11 +104,6 @@ public class ListNodeBuilderImpl extends AbstractNodeBuilderImpl<Integer, ListNo
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String typeDescription() {
|
||||
return "a LIST node";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer id(MergingKey key) {
|
||||
String name = key.first();
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2020 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDefaultSources() {
|
||||
delegate.mpAddDefaultSources();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredSources() {
|
||||
delegate.mpAddDiscoveredSources();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder addDiscoveredConverters() {
|
||||
delegate.mpAddDiscoveredConverters();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder forClassLoader(ClassLoader loader) {
|
||||
delegate.mpForClassLoader(loader);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withSources(ConfigSource... sources) {
|
||||
delegate.mpWithSources(sources);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ConfigBuilder withConverter(Class<T> aClass, int ordinal, Converter<T> converter) {
|
||||
delegate.mpWithConverter(aClass, ordinal, converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigBuilder withConverters(Converter<?>... converters) {
|
||||
delegate.mpWithConverters(converters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config build() {
|
||||
return delegate.build();
|
||||
}
|
||||
|
||||
ConfigBuilder metaConfig(io.helidon.config.Config metaConfig) {
|
||||
delegate.config(metaConfig);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -106,11 +106,6 @@ public class ObjectNodeBuilderImpl extends AbstractNodeBuilderImpl<String, Objec
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String typeDescription() {
|
||||
return "an OBJECT node";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String id(MergingKey key) {
|
||||
return key.first();
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.helidon.config.spi.ConfigFilter;
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
import io.helidon.config.spi.ConfigNode.ObjectNode;
|
||||
|
||||
/**
|
||||
@@ -133,8 +132,7 @@ class ProviderImpl implements Config.Context {
|
||||
rootNode.orElseGet(ObjectNode::empty),
|
||||
targetFilter,
|
||||
this,
|
||||
aliasGenerator,
|
||||
configSource.allSources());
|
||||
aliasGenerator);
|
||||
AbstractConfigImpl config = factory.config();
|
||||
// initialize filters
|
||||
initializeFilters(config, targetFilter);
|
||||
@@ -148,7 +146,7 @@ class ProviderImpl implements Config.Context {
|
||||
private ObjectNode resolveKeys(ObjectNode rootNode) {
|
||||
Function<String, String> resolveTokenFunction = Function.identity();
|
||||
if (keyResolving) {
|
||||
Map<String, String> flattenValueNodes = flattenNodes(rootNode);
|
||||
Map<String, String> flattenValueNodes = ConfigHelper.flattenNodes(rootNode);
|
||||
|
||||
if (flattenValueNodes.isEmpty()) {
|
||||
return rootNode;
|
||||
@@ -166,15 +164,6 @@ class ProviderImpl implements Config.Context {
|
||||
return ObjectNodeBuilderImpl.create(rootNode, resolveTokenFunction).build();
|
||||
}
|
||||
|
||||
private Map<String, String> flattenNodes(ConfigNode node) {
|
||||
return ConfigHelper.flattenNodes(ConfigKeyImpl.of(), node)
|
||||
.filter(e -> e.getValue() instanceof ValueNodeImpl)
|
||||
.collect(Collectors.toMap(
|
||||
e -> e.getKey().toString(),
|
||||
e -> Config.Key.escapeName(((ValueNodeImpl) e.getValue()).get())
|
||||
));
|
||||
}
|
||||
|
||||
private Map<String, String> tokenToValueMap(Map<String, String> flattenValueNodes) {
|
||||
return flattenValueNodes.keySet()
|
||||
.stream()
|
||||
@@ -253,7 +242,6 @@ class ProviderImpl implements Config.Context {
|
||||
|
||||
filterProviders.stream()
|
||||
.map(providerFunction -> providerFunction.apply(config))
|
||||
.sorted(ConfigUtils.priorityComparator(ConfigFilter.PRIORITY))
|
||||
.forEachOrdered(chain::addFilter);
|
||||
|
||||
chain.filterProviders.stream()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -141,7 +141,7 @@ public class ValueResolvingFilter implements ConfigFilter {
|
||||
* either case save the result in a simple boolean for efficiency in
|
||||
* #apply.
|
||||
*/
|
||||
if (!failOnMissingReferenceSetting.isPresent()) {
|
||||
if (failOnMissingReferenceSetting.isEmpty()) {
|
||||
failOnMissingReferenceSetting = Optional.of(
|
||||
config
|
||||
.get(ConfigFilters.ValueResolvingBuilder.FAIL_ON_MISSING_REFERENCE_KEY_NAME)
|
||||
|
||||
@@ -18,5 +18,5 @@ package io.helidon.config.spi;
|
||||
/**
|
||||
* Java service loader service to create a polling strategy factory based on meta configuration.
|
||||
*/
|
||||
public interface ChangeWatcherProvider extends MetaConfigurableProvider<ChangeWatcher> {
|
||||
public interface ChangeWatcherProvider extends MetaConfigurableProvider<ChangeWatcher<?>> {
|
||||
}
|
||||
|
||||
@@ -50,14 +50,17 @@ final class FallbackMergingStrategy implements MergingStrategy {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static ObjectNode.Builder addNode(ObjectNode.Builder builder, String key, ConfigNode node) {
|
||||
private static void addNode(ObjectNode.Builder builder, String key, ConfigNode node) {
|
||||
switch (node.nodeType()) {
|
||||
case OBJECT:
|
||||
return builder.addObject(key, (ObjectNode) node);
|
||||
builder.addObject(key, (ObjectNode) node);
|
||||
return;
|
||||
case LIST:
|
||||
return builder.addList(key, (ListNode) node);
|
||||
builder.addList(key, (ListNode) node);
|
||||
return;
|
||||
case VALUE:
|
||||
return builder.addValue(key, (ValueNode) node);
|
||||
builder.addValue(key, (ValueNode) node);
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported node type: " + node.getClass().getName());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,9 +26,9 @@ import java.util.Properties;
|
||||
*/
|
||||
final class OrderedProperties {
|
||||
|
||||
private LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
private Properties properties = new Properties() {
|
||||
private final Properties properties = new Properties() {
|
||||
@Override
|
||||
public synchronized Object put(Object key, Object value) {
|
||||
return map.put(key.toString(), value.toString());
|
||||
|
||||
@@ -25,7 +25,7 @@ final class SpiHelper {
|
||||
*
|
||||
* @see io.helidon.config.spi.ConfigNode.ObjectNode#empty()
|
||||
*/
|
||||
public static final class EmptyObjectNodeHolder {
|
||||
static final class EmptyObjectNodeHolder {
|
||||
|
||||
private EmptyObjectNodeHolder() {
|
||||
throw new AssertionError("Instantiation not allowed.");
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import io.helidon.config.PropertiesConfigParser;
|
||||
|
||||
/**
|
||||
* config module.
|
||||
* Helidon SE Config module.
|
||||
*/
|
||||
module io.helidon.config {
|
||||
|
||||
@@ -27,9 +27,9 @@ module io.helidon.config {
|
||||
|
||||
requires transitive io.helidon.common;
|
||||
requires transitive io.helidon.common.reactive;
|
||||
requires io.helidon.common.serviceloader;
|
||||
requires transitive io.helidon.common.media.type;
|
||||
requires transitive microprofile.config.api;
|
||||
|
||||
requires io.helidon.common.serviceloader;
|
||||
|
||||
exports io.helidon.config;
|
||||
exports io.helidon.config.spi;
|
||||
@@ -41,13 +41,8 @@ module io.helidon.config {
|
||||
uses io.helidon.config.spi.OverrideSourceProvider;
|
||||
uses io.helidon.config.spi.RetryPolicyProvider;
|
||||
uses io.helidon.config.spi.PollingStrategyProvider;
|
||||
|
||||
uses org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
uses org.eclipse.microprofile.config.spi.ConfigSourceProvider;
|
||||
uses org.eclipse.microprofile.config.spi.Converter;
|
||||
uses io.helidon.config.spi.ChangeWatcherProvider;
|
||||
|
||||
provides io.helidon.config.spi.ConfigParser with PropertiesConfigParser;
|
||||
provides org.eclipse.microprofile.config.spi.ConfigProviderResolver with io.helidon.config.MpConfigProviderResolver;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package io.helidon.config;
|
||||
|
||||
import io.helidon.config.spi.ConfigFilter;
|
||||
|
||||
import static io.helidon.config.FilterLoadingTest.ORIGINAL_VALUE_SUBJECT_TO_AUTO_FILTERING;
|
||||
|
||||
/**
|
||||
* Abstract superclass for making sure simple priority works.
|
||||
* <p>
|
||||
@@ -34,7 +36,11 @@ public abstract class AutoLoadedConfigPriority implements ConfigFilter {
|
||||
|
||||
@Override
|
||||
public String apply(Config.Key key, String stringValue) {
|
||||
if (key.toString().equals(KEY_SUBJECT_TO_AUTO_FILTERING)) {
|
||||
// the original implementation was wrong (priorities were inversed and this test was wrong)
|
||||
// the new approach makes sure the filter with higher priority modifies the value, and
|
||||
// any filter down the filter chain sees the modified value, and ignores it
|
||||
if (key.toString().equals(KEY_SUBJECT_TO_AUTO_FILTERING)
|
||||
&& stringValue.equals(ORIGINAL_VALUE_SUBJECT_TO_AUTO_FILTERING)) {
|
||||
return getExpectedValue();
|
||||
}
|
||||
return stringValue;
|
||||
|
||||
@@ -16,51 +16,36 @@
|
||||
|
||||
package io.helidon.config;
|
||||
|
||||
import java.util.concurrent.Flow;
|
||||
import java.util.function.Function;
|
||||
import java.util.Map;
|
||||
|
||||
import io.helidon.config.spi.ConfigNode;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests {@link ConfigHelper}.
|
||||
*/
|
||||
public class ConfigHelperTest {
|
||||
class ConfigHelperTest {
|
||||
@Test
|
||||
public void testSubscriber() {
|
||||
//mocks
|
||||
Function<Long, Boolean> onNextFunction = mock(Function.class);
|
||||
Flow.Subscription subscription = mock(Flow.Subscription.class);
|
||||
void testFlattenNodes() {
|
||||
ConfigNode.ObjectNode node = ConfigNode.ObjectNode.builder()
|
||||
.addValue("simple", "value")
|
||||
.addList("list", ConfigNode.ListNode.builder()
|
||||
.addValue("first")
|
||||
.addValue("second")
|
||||
.build())
|
||||
.addObject("object", ConfigNode.ObjectNode.builder()
|
||||
.addValue("value", "value2")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
//create Subscriber
|
||||
Flow.Subscriber<Long> subscriber = ConfigHelper.subscriber(onNextFunction);
|
||||
|
||||
//onSubscribe
|
||||
subscriber.onSubscribe(subscription);
|
||||
// request(Long.MAX_VALUE) has been invoked
|
||||
verify(subscription).request(Long.MAX_VALUE);
|
||||
|
||||
//MOCK onNext
|
||||
when(onNextFunction.apply(1L)).thenReturn(true);
|
||||
when(onNextFunction.apply(2L)).thenReturn(true);
|
||||
when(onNextFunction.apply(3L)).thenReturn(false);
|
||||
// 2x onNext -> true
|
||||
subscriber.onNext(1L);
|
||||
subscriber.onNext(2L);
|
||||
// function invoked 2x, cancel never
|
||||
verify(onNextFunction, times(2)).apply(any());
|
||||
verify(subscription, never()).cancel();
|
||||
// 1x onNext -> false
|
||||
subscriber.onNext(3L);
|
||||
// function invoked 2+1x, cancel 1x
|
||||
verify(onNextFunction, times(2 + 1)).apply(any());
|
||||
verify(subscription, times(1)).cancel();
|
||||
Map<String, String> map = ConfigHelper.flattenNodes(node);
|
||||
Map<String, String> expected = Map.of(
|
||||
"simple", "value",
|
||||
"list.0", "first",
|
||||
"list.1", "second",
|
||||
"object.value", "value2"
|
||||
);
|
||||
assertThat(map, is(expected));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
|
||||
import io.helidon.config.ConfigUtils.ScheduledTask;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* Tests {@link io.helidon.config.ConfigUtils}.
|
||||
*/
|
||||
public class ConfigUtilsTest {
|
||||
|
||||
private void testAsStream(Iterable<Integer> integers) {
|
||||
List<Integer> list = ConfigUtils.asStream(integers).sorted(Integer::compare).collect(Collectors.toList());
|
||||
assertThat(list, Matchers.hasSize(4));
|
||||
assertThat(list.get(0), equalTo(0));
|
||||
assertThat(list.get(1), equalTo(10));
|
||||
assertThat(list.get(2), equalTo(20));
|
||||
assertThat(list.get(3), equalTo(30));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsStream() {
|
||||
testAsStream(Arrays.asList(20, 0, 30, 10));
|
||||
testAsStream(Arrays.asList(10, 30, 0, 20));
|
||||
testAsStream(Arrays.asList(0, 10, 20, 30));
|
||||
}
|
||||
|
||||
private void testAsPrioritizedStream(Iterable<Provider> providers) {
|
||||
List<Provider> list = ConfigUtils.asPrioritizedStream(providers, 0).collect(Collectors.toList());
|
||||
assertThat(list, Matchers.hasSize(4));
|
||||
assertThat(list.get(0), IsInstanceOf.instanceOf(Provider3.class));
|
||||
assertThat(list.get(1), IsInstanceOf.instanceOf(Provider1.class));
|
||||
assertThat(list.get(2), IsInstanceOf.instanceOf(Provider4.class));
|
||||
assertThat(list.get(3), IsInstanceOf.instanceOf(Provider2.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsPrioritizedStream() {
|
||||
testAsPrioritizedStream(Arrays.asList(new Provider1(), new Provider2(), new Provider3(), new Provider4()));
|
||||
testAsPrioritizedStream(Arrays.asList(new Provider4(), new Provider3(), new Provider2(), new Provider1()));
|
||||
testAsPrioritizedStream(Arrays.asList(new Provider2(), new Provider4(), new Provider1(), new Provider3()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScheduledTaskInterruptedRepeatedly() throws InterruptedException {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
ScheduledTask task = new ScheduledTask(Executors.newSingleThreadScheduledExecutor(),
|
||||
counter::incrementAndGet,
|
||||
Duration.ofMillis(80));
|
||||
task.schedule();
|
||||
task.schedule();
|
||||
task.schedule();
|
||||
task.schedule();
|
||||
task.schedule();
|
||||
|
||||
//not yet finished
|
||||
assertThat(counter.get(), is(0));
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(120);
|
||||
assertThat(counter.get(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScheduledTaskExecutedRepeatedly() throws InterruptedException {
|
||||
CountDownLatch execLatch = new CountDownLatch(5);
|
||||
ScheduledTask task = new ScheduledTask(Executors.newSingleThreadScheduledExecutor(),
|
||||
execLatch::countDown,
|
||||
Duration.ZERO);
|
||||
/*
|
||||
Because invoking 'schedule' can cancel an existing action, keep track
|
||||
of cancelations in case the latch expires without reaching 0.
|
||||
*/
|
||||
final long RESCHEDULE_DELAY_MS = 5;
|
||||
final int ACTIONS_TO_SCHEDULE = 5;
|
||||
int cancelations = 0;
|
||||
|
||||
for (int i = 0; i < ACTIONS_TO_SCHEDULE; i++ ) {
|
||||
if (task.schedule()) {
|
||||
cancelations++;
|
||||
}
|
||||
TimeUnit.MILLISECONDS.sleep(RESCHEDULE_DELAY_MS);
|
||||
}
|
||||
/*
|
||||
The latch can either complete -- because all the scheduled actions finished --
|
||||
or it can expire at the timeout because at least one action did not finish, in
|
||||
which case the remaining latch value should not exceed the number of actions
|
||||
canceled. (Do not check for exact equality; some attempts to cancel
|
||||
an action might occur after the action was deemed to be not-yet-run or in-progress
|
||||
but actually runs to completion before the cancel is actually invoked.
|
||||
*/
|
||||
assertThat(
|
||||
"Current execLatch count: " + execLatch.getCount() + ", cancelations: "
|
||||
+ "" + cancelations,
|
||||
execLatch.await(3000, TimeUnit.MILLISECONDS) || execLatch.getCount() <= cancelations,
|
||||
is(true));
|
||||
}
|
||||
|
||||
//
|
||||
// providers ...
|
||||
//
|
||||
|
||||
interface Provider {
|
||||
}
|
||||
|
||||
@Priority(20)
|
||||
static class Provider1 implements Provider {
|
||||
}
|
||||
|
||||
static class Provider2 implements Provider {
|
||||
}
|
||||
|
||||
@Priority(30)
|
||||
static class Provider3 implements Provider {
|
||||
}
|
||||
|
||||
@Priority(10)
|
||||
static class Provider4 implements Provider {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
*/
|
||||
public class FilterLoadingTest {
|
||||
|
||||
private static final String ORIGINAL_VALUE_SUBJECT_TO_AUTO_FILTERING = "originalValue";
|
||||
static final String ORIGINAL_VALUE_SUBJECT_TO_AUTO_FILTERING = "originalValue";
|
||||
private static final String ORIGINAL_VALUE_SUBJECT_TO_AUTO_FILTERING_VIA_PROVIDER = "originalValueForProviderTest";
|
||||
|
||||
private static final String UNAFFECTED_KEY = "key1";
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2020 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.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
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(Map.of("mp-1", "mp-value-1",
|
||||
"mp-2", "mp-value-2",
|
||||
"app.storageEnabled", "false",
|
||||
"mp-array", "a,b,c",
|
||||
"mp-list.0", "1",
|
||||
"mp-list.1", "2",
|
||||
"mp-list.2", "3")))
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.build();
|
||||
|
||||
assertThat(helidonConfig, instanceOf(Config.class));
|
||||
|
||||
config = (Config) helidonConfig;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConfigSources() {
|
||||
Iterable<ConfigSource> configSources = config.getConfigSources();
|
||||
List<ConfigSource> asList = new ArrayList<>();
|
||||
for (ConfigSource configSource : configSources) {
|
||||
asList.add(configSource);
|
||||
}
|
||||
|
||||
assertThat(asList, hasSize(2));
|
||||
|
||||
ConfigSourceRuntimeImpl helidonWrapper = (ConfigSourceRuntimeImpl) asList.get(0);
|
||||
assertThat(helidonWrapper.unwrap(), instanceOf(ClasspathConfigSource.class));
|
||||
helidonWrapper = (ConfigSourceRuntimeImpl) asList.get(1);
|
||||
assertThat(helidonWrapper.unwrap(), 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));
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,15 @@
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-key-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- an implementation of a filter is part of this library - it only makes sense
|
||||
when MP config implementation is on the classpath, not needed or used otherwise
|
||||
-->
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-yaml</artifactId>
|
||||
|
||||
@@ -37,7 +37,7 @@ import io.helidon.config.spi.ConfigFilter;
|
||||
* <p>
|
||||
* Password in properties must be stored as follows:
|
||||
* <ul>
|
||||
* <li>${AES=base64} - encrypted password using a master password (must be provided to Prime through configuration, system
|
||||
* <li>${AES=base64} - encrypted password using a master password (must be provided to prime through configuration, system
|
||||
* property or environment variable)</li>
|
||||
* <li>${RSA=base64} - encrypted password using a public key (private key must be available to Prime instance,
|
||||
* its location must be provided to prime through configuration, system property or environment variable)</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ import io.helidon.common.configurable.Resource;
|
||||
import io.helidon.common.pki.KeyConfig;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigValue;
|
||||
import io.helidon.config.mp.MpConfig;
|
||||
|
||||
/**
|
||||
* Encryption utilities for secrets protection.
|
||||
@@ -280,6 +281,29 @@ public final class EncryptionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<char[]> resolveMasterPassword(boolean requireEncryption, org.eclipse.microprofile.config.Config config) {
|
||||
Optional<char[]> result = getEnv(ConfigProperties.MASTER_PASSWORD_ENV_VARIABLE)
|
||||
.or(() -> {
|
||||
Optional<String> value = config.getOptionalValue(ConfigProperties.MASTER_PASSWORD_CONFIG_KEY, String.class);
|
||||
if (value.isPresent()) {
|
||||
if (requireEncryption) {
|
||||
LOGGER.warning(
|
||||
"Master password is configured as clear text in configuration when encryption is required. "
|
||||
+ "This value will be ignored. System property or environment variable expected!!!");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
})
|
||||
.map(String::toCharArray);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
LOGGER.fine("Securing properties using master password is not available, as master password is not configured");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Optional<char[]> resolveMasterPassword(boolean requireEncryption, Config config) {
|
||||
Optional<char[]> result = getEnv(ConfigProperties.MASTER_PASSWORD_ENV_VARIABLE)
|
||||
.or(() -> {
|
||||
@@ -303,6 +327,10 @@ public final class EncryptionUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
static Optional<PrivateKey> resolvePrivateKey(org.eclipse.microprofile.config.Config config){
|
||||
return resolvePrivateKey(MpConfig.toHelidonConfig(config).get("security.config.rsa"));
|
||||
}
|
||||
|
||||
static Optional<PrivateKey> resolvePrivateKey(Config config) {
|
||||
// load configuration values
|
||||
KeyConfig.PemBuilder pemBuilder = KeyConfig.pemBuilder().config(config);
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2020 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.encryption;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.util.HashSet;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.config.mp.spi.MpConfigFilter;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
|
||||
/**
|
||||
* Provides possibility to decrypt passwords from configuration sources.
|
||||
* Configuration can be used to enforce encryption (e.g. we will fail on clear-text value).
|
||||
* <p>
|
||||
* Password in properties must be stored as follows:
|
||||
* <ul>
|
||||
* <li>${AES=base64} - encrypted password using a master password (must be provided to prime through configuration, system
|
||||
* property or environment variable)</li>
|
||||
* <li>${RSA=base64} - encrypted password using a public key (private key must be available to Prime instance,
|
||||
* its location must be provided to prime through configuration, system property or environment variable)</li>
|
||||
* <li>${ALIAS=alias_name} - reference to another property, that is encrypted</li>
|
||||
* <li>${CLEAR=text} - clear-text password. Intentionally denoting this value as a protectable one, so we can enforce encryption
|
||||
* (e.g. in prod)</li>
|
||||
* </ul>
|
||||
* Example:
|
||||
* <pre>
|
||||
* google_client_secret=${AES=mYRkg+4Q4hua1kvpCCI2hg==}
|
||||
* service_password=${RSA=mYRkg+4Q4hua1kvpCCI2hg==}
|
||||
* another_password=${ALIAS=service_password}
|
||||
* cleartext_password=${CLEAR=known_password}
|
||||
* </pre>
|
||||
*
|
||||
* @see ConfigProperties#PRIVATE_KEYSTORE_PATH_ENV_VARIABLE
|
||||
* @see ConfigProperties#MASTER_PASSWORD_ENV_VARIABLE
|
||||
* @see ConfigProperties#MASTER_PASSWORD_CONFIG_KEY
|
||||
* @see ConfigProperties#REQUIRE_ENCRYPTION_ENV_VARIABLE
|
||||
*/
|
||||
public final class MpEncryptionFilter implements MpConfigFilter {
|
||||
private static final String PREFIX_LEGACY_AES = "${AES=";
|
||||
private static final String PREFIX_LEGACY_RSA = "${RSA=";
|
||||
static final String PREFIX_GCM = "${GCM=";
|
||||
static final String PREFIX_RSA = "${RSA-P=";
|
||||
private static final Logger LOGGER = Logger.getLogger(MpEncryptionFilter.class.getName());
|
||||
private static final String PREFIX_ALIAS = "${ALIAS=";
|
||||
private static final String PREFIX_CLEAR = "${CLEAR=";
|
||||
|
||||
static {
|
||||
HelidonFeatures.register("Config", "Encryption");
|
||||
}
|
||||
|
||||
private PrivateKey privateKey;
|
||||
private char[] masterPassword;
|
||||
|
||||
private boolean requireEncryption;
|
||||
|
||||
private MpConfigFilter clearFilter;
|
||||
private MpConfigFilter rsaFilter;
|
||||
private MpConfigFilter aesFilter;
|
||||
private MpConfigFilter aliasFilter;
|
||||
|
||||
/**
|
||||
* This constructor is only for use by {@link io.helidon.config.mp.spi.MpConfigFilter} service loader.
|
||||
*/
|
||||
@Deprecated
|
||||
public MpEncryptionFilter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(org.eclipse.microprofile.config.Config config) {
|
||||
this.requireEncryption = EncryptionUtil.getEnv(ConfigProperties.REQUIRE_ENCRYPTION_ENV_VARIABLE)
|
||||
.map(Boolean::parseBoolean)
|
||||
.or(() -> config.getOptionalValue(ConfigProperties.REQUIRE_ENCRYPTION_CONFIG_KEY, Boolean.class))
|
||||
.orElse(true);
|
||||
|
||||
this.masterPassword = EncryptionUtil.resolveMasterPassword(requireEncryption, config)
|
||||
.orElse(null);
|
||||
this.privateKey = EncryptionUtil.resolvePrivateKey(config)
|
||||
.orElse(null);
|
||||
|
||||
if (null != privateKey && !(privateKey instanceof RSAPrivateKey)) {
|
||||
throw new ConfigEncryptionException("Private key must be an RSA private key, but is: "
|
||||
+ privateKey.getClass().getName());
|
||||
}
|
||||
|
||||
MpConfigFilter noOp = (key, stringValue) -> stringValue;
|
||||
|
||||
aesFilter = (null == masterPassword ? noOp : (key, stringValue) -> decryptAes(masterPassword, stringValue));
|
||||
rsaFilter = (null == privateKey ? noOp : (key, stringValue) -> decryptRsa(privateKey, stringValue));
|
||||
clearFilter = this::clearText;
|
||||
aliasFilter = (key, stringValue) -> aliased(stringValue, config);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String propertyName, String value) {
|
||||
return maybeDecode(propertyName, value);
|
||||
}
|
||||
|
||||
private static String removePlaceholder(String prefix, String value) {
|
||||
return value.substring(prefix.length(), value.length() - 1);
|
||||
}
|
||||
|
||||
private String maybeDecode(String propertyName, String value) {
|
||||
Set<String> processedValues = new HashSet<>();
|
||||
|
||||
do {
|
||||
processedValues.add(value);
|
||||
if (!value.startsWith("${") && !value.endsWith("}")) {
|
||||
//this is not encoded, safely return
|
||||
return value;
|
||||
}
|
||||
value = aliasFilter.apply(propertyName, value);
|
||||
value = clearFilter.apply(propertyName, value);
|
||||
value = rsaFilter.apply(propertyName, value);
|
||||
value = aesFilter.apply(propertyName, value);
|
||||
} while (!processedValues.contains(value));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private String clearText(String propertyName, String value) {
|
||||
// cleartext_password=${CLEAR=known_password}
|
||||
if (value.startsWith(PREFIX_CLEAR)) {
|
||||
if (requireEncryption) {
|
||||
throw new ConfigEncryptionException("Key \"" + propertyName + "\" is a clear text password, yet encryption is "
|
||||
+ "required");
|
||||
}
|
||||
return removePlaceholder(PREFIX_CLEAR, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private String aliased(String value, Config config) {
|
||||
|
||||
if (value.startsWith(PREFIX_ALIAS)) {
|
||||
// another_password=${ALIAS=service_password}
|
||||
String alias = removePlaceholder(PREFIX_ALIAS, value);
|
||||
|
||||
return config.getOptionalValue(alias, String.class)
|
||||
.orElseThrow(() -> new NoSuchElementException("Aliased key not found. Value: " + value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private String decryptRsa(PrivateKey privateKey, String value) {
|
||||
// service_password=${RSA=mYRkg+4Q4hua1kvpCCI2hg==}
|
||||
if (value.startsWith(PREFIX_LEGACY_RSA)) {
|
||||
LOGGER.log(Level.WARNING, () -> "You are using legacy RSA encryption. Please re-encrypt the value with RSA-P.");
|
||||
String b64Value = removePlaceholder(PREFIX_LEGACY_RSA, value);
|
||||
try {
|
||||
return EncryptionUtil.decryptRsaLegacy(privateKey, b64Value);
|
||||
} catch (ConfigEncryptionException e) {
|
||||
LOGGER.log(Level.FINEST, e, () -> "Failed to decrypt " + value);
|
||||
return value;
|
||||
}
|
||||
} else if (value.startsWith(PREFIX_RSA)) {
|
||||
String b64Value = removePlaceholder(PREFIX_RSA, value);
|
||||
try {
|
||||
return EncryptionUtil.decryptRsa(privateKey, b64Value);
|
||||
} catch (ConfigEncryptionException e) {
|
||||
LOGGER.log(Level.FINEST, e, () -> "Failed to decrypt " + value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private String decryptAes(char[] masterPassword, String value) {
|
||||
// google_client_secret=${AES=mYRkg+4Q4hua1kvpCCI2hg==}
|
||||
|
||||
if (value.startsWith(PREFIX_LEGACY_AES)) {
|
||||
LOGGER.log(Level.WARNING, () -> "You are using legacy AES encryption. Please re-encrypt the value with GCM.");
|
||||
String b64Value = value.substring(PREFIX_LEGACY_AES.length(), value.length() - 1);
|
||||
try {
|
||||
return EncryptionUtil.decryptAesLegacy(masterPassword, b64Value);
|
||||
} catch (ConfigEncryptionException e) {
|
||||
LOGGER.log(Level.FINEST, e, () -> "Failed to decrypt " + value);
|
||||
return value;
|
||||
}
|
||||
} else if (value.startsWith(PREFIX_GCM)) {
|
||||
String b64Value = value.substring(PREFIX_GCM.length(), value.length() - 1);
|
||||
try {
|
||||
return EncryptionUtil.decryptAes(masterPassword, b64Value);
|
||||
} catch (ConfigEncryptionException e) {
|
||||
LOGGER.log(Level.FINEST, e, () -> "Failed to decrypt " + value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,10 @@ module io.helidon.config.encryption {
|
||||
requires transitive io.helidon.common.pki;
|
||||
requires transitive io.helidon.config;
|
||||
|
||||
requires static io.helidon.config.mp;
|
||||
|
||||
exports io.helidon.config.encryption;
|
||||
|
||||
provides io.helidon.config.spi.ConfigFilter with io.helidon.config.encryption.EncryptionFilterService;
|
||||
provides io.helidon.config.mp.spi.MpConfigFilter with io.helidon.config.encryption.MpEncryptionFilter;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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.encryption.MpEncryptionFilter
|
||||
@@ -46,6 +46,7 @@
|
||||
<module>testing</module>
|
||||
<module>test-infrastructure</module>
|
||||
<module>tests</module>
|
||||
<module>config-mp</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -58,6 +58,5 @@
|
||||
<module>test-mappers-2-complex</module>
|
||||
<module>test-meta-source</module>
|
||||
<module>test-parsers-1-complex</module>
|
||||
<module>test-mp-reference</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
||||
@@ -50,6 +50,12 @@
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-testing</artifactId>
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.yaml;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.helidon.config.ConfigException;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* MicroProfile {@link org.eclipse.microprofile.config.spi.ConfigSource} that can be used
|
||||
* to add YAML files from classpath or file system using the
|
||||
* {@link org.eclipse.microprofile.config.spi.ConfigProviderResolver#getBuilder()}.
|
||||
* <p>The YAML file is transformed to a flat map as follows:</p>
|
||||
* <strong>Object nodes</strong>
|
||||
* <p>
|
||||
* Each node in the tree is dot separated.
|
||||
* <pre>
|
||||
* server:
|
||||
* host: "localhost"
|
||||
* port: 8080
|
||||
* </pre>
|
||||
* Will be transformed to the following properties:
|
||||
* <pre>
|
||||
* server.host=localhost
|
||||
* server.port=8080
|
||||
* </pre>
|
||||
* <strong>List nodes (arrays)</strong>
|
||||
* <p>
|
||||
* Each node will be indexed (0 based)
|
||||
* <pre>
|
||||
* providers:
|
||||
* - abac:
|
||||
* enabled: true
|
||||
* names: ["first", "second", "third"]
|
||||
* </pre>
|
||||
* Will be transformed to the following properties:
|
||||
* <pre>
|
||||
* providers.0.abac.enabled=true
|
||||
* names.0=first
|
||||
* names.1=second
|
||||
* names.2=third
|
||||
* </pre>
|
||||
*/
|
||||
public class YamlMpConfigSource implements ConfigSource {
|
||||
private final Map<String, String> properties;
|
||||
private final String name;
|
||||
|
||||
private YamlMpConfigSource(String name, Map<String, String> properties) {
|
||||
this.properties = properties;
|
||||
this.name = "yaml: " + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a YAML config source from file system.
|
||||
*
|
||||
* @param path path to the YAML file
|
||||
* @return config source loaded from the file
|
||||
* @see #create(java.net.URL)
|
||||
*/
|
||||
public static ConfigSource create(Path path) {
|
||||
try {
|
||||
return create(path.toUri().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new ConfigException("Failed to load YAML config source from path: " + path.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a YAML config source from URL.
|
||||
* The URL may be any URL which is support by the used JVM.
|
||||
*
|
||||
* @param url url of the resource
|
||||
* @return config source loaded from the URL
|
||||
*/
|
||||
public static ConfigSource create(URL url) {
|
||||
try (InputStreamReader reader = new InputStreamReader(url.openConnection().getInputStream(), StandardCharsets.UTF_8)) {
|
||||
return create(url.toString(), reader);
|
||||
} catch (Exception e) {
|
||||
throw new ConfigException("Failed to configure YAML config source", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from YAML content as a reader.
|
||||
* This method will NOT close the reader.
|
||||
*
|
||||
* @param name name of the config source
|
||||
* @param content reader with the YAML content
|
||||
* @return config source loaded from the content
|
||||
*/
|
||||
public static ConfigSource create(String name, Reader content) {
|
||||
Map yamlMap;
|
||||
Yaml yaml = new Yaml();
|
||||
yamlMap = yaml.loadAs(content, Map.class);
|
||||
if (yamlMap == null) { // empty source
|
||||
return new YamlMpConfigSource(name, Map.of());
|
||||
}
|
||||
|
||||
return new YamlMpConfigSource(name, fromMap(yamlMap));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.unmodifiableMap(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
return properties.get(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private static Map<String, String> fromMap(Map yamlMap) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
process(result, "", yamlMap);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void process(Map<String, String> resultMap, String prefix, Map yamlMap) {
|
||||
yamlMap.forEach((key, value) -> {
|
||||
processNext(resultMap, prefix(prefix, key.toString()), value);
|
||||
});
|
||||
}
|
||||
|
||||
private static void process(Map<String, String> resultMap, String prefix, List yamlList) {
|
||||
int counter = 0;
|
||||
for (Object value : yamlList) {
|
||||
processNext(resultMap, prefix(prefix, String.valueOf(counter)), value);
|
||||
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void processNext(Map<String, String> resultMap,
|
||||
String prefix,
|
||||
Object value) {
|
||||
if (value instanceof List) {
|
||||
process(resultMap, prefix, (List) value);
|
||||
} else if (value instanceof Map) {
|
||||
process(resultMap, prefix, (Map) value);
|
||||
} else {
|
||||
String stringValue = (null == value) ? "" : value.toString();
|
||||
resultMap.put(prefix, stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static String prefix(String prefix, String stringKey) {
|
||||
if (prefix.isEmpty()) {
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
return prefix + "." + stringKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.helidon.config.ConfigException;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
|
||||
|
||||
/**
|
||||
* YAML config source provider for MicroProfile config that supports file {@code application.yaml}.
|
||||
* This class should not be used directly - it is loaded automatically by Java service loader.
|
||||
*/
|
||||
public class YamlMpConfigSourceProvider implements ConfigSourceProvider {
|
||||
@Override
|
||||
public Iterable<ConfigSource> getConfigSources(ClassLoader classLoader) {
|
||||
Enumeration<URL> resources;
|
||||
try {
|
||||
resources = classLoader.getResources("application.yaml");
|
||||
} catch (IOException e) {
|
||||
throw new ConfigException("Failed to read resources from classpath", e);
|
||||
}
|
||||
|
||||
List<ConfigSource> result = new LinkedList<>();
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
result.add(YamlMpConfigSource.create(url));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import io.helidon.config.yaml.YamlConfigParser;
|
||||
import io.helidon.config.yaml.YamlMpConfigSourceProvider;
|
||||
|
||||
/**
|
||||
* YAML Parser implementation.
|
||||
@@ -27,9 +28,11 @@ module io.helidon.config.yaml {
|
||||
|
||||
requires transitive io.helidon.config;
|
||||
requires io.helidon.common;
|
||||
requires static microprofile.config.api;
|
||||
|
||||
exports io.helidon.config.yaml;
|
||||
|
||||
provides io.helidon.config.spi.ConfigParser with YamlConfigParser;
|
||||
provides org.eclipse.microprofile.config.spi.ConfigSourceProvider with YamlMpConfigSourceProvider;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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.yaml.YamlMpConfigSourceProvider
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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.yaml;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class YamlMpConfigSourceTest {
|
||||
private static final String TEST_1 = "server:\n"
|
||||
+ " host: \"localhost\"\n"
|
||||
+ " port: 8080\n";
|
||||
|
||||
private static final String TEST_2 = "providers:\n"
|
||||
+ " - abac:\n"
|
||||
+ " enabled: true\n"
|
||||
+ "names: [\"first\", \"second\", \"third\"]";
|
||||
@Test
|
||||
void testObjectNode() {
|
||||
ConfigSource source = YamlMpConfigSource.create("testObjectNode", new StringReader(TEST_1));
|
||||
assertThat(source.getValue("server.host"), is("localhost"));
|
||||
assertThat(source.getValue("server.port"), is("8080"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testListNode() {
|
||||
ConfigSource source = YamlMpConfigSource.create("testObjectNode", new StringReader(TEST_2));
|
||||
assertThat(source.getValue("providers.0.abac.enabled"), is("true"));
|
||||
assertThat(source.getValue("names.0"), is("first"));
|
||||
assertThat(source.getValue("names.1"), is("second"));
|
||||
assertThat(source.getValue("names.2"), is("third"));
|
||||
}
|
||||
}
|
||||
35
dependencies/pom.xml
vendored
35
dependencies/pom.xml
vendored
@@ -79,7 +79,7 @@
|
||||
<version.lib.jsonp-impl>1.1.6</version.lib.jsonp-impl>
|
||||
<version.lib.junit>5.6.2</version.lib.junit>
|
||||
<version.lib.maven-wagon>2.10</version.lib.maven-wagon>
|
||||
<version.lib.microprofile-config>1.3</version.lib.microprofile-config>
|
||||
<version.lib.microprofile-config>1.4</version.lib.microprofile-config>
|
||||
<version.lib.microprofile-health>2.1</version.lib.microprofile-health>
|
||||
<version.lib.microprofile-jwt>1.1.1</version.lib.microprofile-jwt>
|
||||
<version.lib.microprofile-metrics-api>2.2</version.lib.microprofile-metrics-api>
|
||||
@@ -601,6 +601,39 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.annotation.versioning</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-rest-tck</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.arquillian.junit</groupId>
|
||||
<artifactId>arquillian-junit-container</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api-tck</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.arquillian.junit</groupId>
|
||||
<artifactId>arquillian-junit-container</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.reactive-streams-operators</groupId>
|
||||
<artifactId>microprofile-reactive-streams-operators-api</artifactId>
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.config</groupId>
|
||||
<artifactId>helidon-microprofile-config</artifactId>
|
||||
<groupId>io.helidon.microprofile.cdi</groupId>
|
||||
<artifactId>helidon-microprofile-cdi</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
<groupId>io.helidon.webserver</groupId>
|
||||
<artifactId>helidon-webserver</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>jakarta.json</artifactId>
|
||||
|
||||
@@ -39,7 +39,7 @@ import javax.json.JsonObject;
|
||||
import javax.json.JsonReaderFactory;
|
||||
import javax.json.stream.JsonParsingException;
|
||||
|
||||
import io.helidon.config.MpConfigProviderResolver;
|
||||
import io.helidon.config.mp.MpConfigProviderResolver;
|
||||
|
||||
import com.oracle.svm.core.annotate.AutomaticFeature;
|
||||
import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry;
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-context</artifactId>
|
||||
|
||||
@@ -30,6 +30,7 @@ import javax.inject.Inject;
|
||||
|
||||
import io.helidon.common.configurable.ScheduledThreadPoolSupplier;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.mp.MpConfig;
|
||||
|
||||
import org.eclipse.microprofile.reactive.messaging.Message;
|
||||
import org.eclipse.microprofile.reactive.messaging.spi.Connector;
|
||||
@@ -89,14 +90,18 @@ public class KafkaConnector implements IncomingConnectorFactory, OutgoingConnect
|
||||
|
||||
@Override
|
||||
public PublisherBuilder<? extends Message<?>> getPublisherBuilder(org.eclipse.microprofile.config.Config config) {
|
||||
KafkaPublisher<Object, Object> publisher = KafkaPublisher.builder().config((Config) config).scheduler(scheduler).build();
|
||||
KafkaPublisher<Object, Object> publisher = KafkaPublisher.builder()
|
||||
.config(MpConfig.toHelidonConfig(config))
|
||||
.scheduler(scheduler)
|
||||
.build();
|
||||
|
||||
resources.add(publisher);
|
||||
return ReactiveStreams.fromPublisher(publisher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriberBuilder<? extends Message<?>, Void> getSubscriberBuilder(org.eclipse.microprofile.config.Config config) {
|
||||
return ReactiveStreams.fromSubscriber(KafkaSubscriber.create((Config) config));
|
||||
return ReactiveStreams.fromSubscriber(KafkaSubscriber.create(MpConfig.toHelidonConfig(config)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,10 +22,12 @@ module io.helidon.messaging.connectors.kafka {
|
||||
requires static kafka.clients;
|
||||
requires org.reactivestreams;
|
||||
requires transitive io.helidon.config;
|
||||
requires io.helidon.config.mp;
|
||||
requires transitive microprofile.reactive.messaging.api;
|
||||
requires transitive microprofile.reactive.streams.operators.api;
|
||||
requires io.helidon.common.context;
|
||||
requires io.helidon.common.configurable;
|
||||
requires microprofile.config.api;
|
||||
|
||||
exports io.helidon.messaging.connectors.kafka;
|
||||
}
|
||||
@@ -46,16 +46,13 @@
|
||||
<groupId>io.helidon.common</groupId>
|
||||
<artifactId>helidon-common-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.annotation.versioning</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.webserver</groupId>
|
||||
|
||||
@@ -29,6 +29,8 @@ module io.helidon.metrics {
|
||||
requires io.helidon.webserver;
|
||||
requires io.helidon.media.jsonp.server;
|
||||
requires java.json;
|
||||
requires io.helidon.config.mp;
|
||||
requires microprofile.config.api;
|
||||
|
||||
provides io.helidon.common.metrics.InternalBridge
|
||||
with io.helidon.metrics.InternalBridgeImpl;
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config</artifactId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.common</groupId>
|
||||
|
||||
@@ -43,9 +43,10 @@ import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.common.HelidonFlavor;
|
||||
import io.helidon.common.context.Context;
|
||||
import io.helidon.common.context.Contexts;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.MpConfigProviderResolver;
|
||||
import io.helidon.config.mp.MpConfig;
|
||||
import io.helidon.config.mp.MpConfigProviderResolver;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.ConfigProvider;
|
||||
import org.jboss.weld.AbstractCDI;
|
||||
import org.jboss.weld.bean.builtin.BeanManagerProxy;
|
||||
@@ -151,7 +152,9 @@ final class HelidonContainerImpl extends Weld implements HelidonContainer {
|
||||
};
|
||||
setResourceLoader(resourceLoader);
|
||||
|
||||
Config config = (Config) ConfigProvider.getConfig();
|
||||
Config mpConfig = ConfigProvider.getConfig();
|
||||
io.helidon.config.Config config = MpConfig.toHelidonConfig(mpConfig);
|
||||
|
||||
Map<String, String> properties = config.get("cdi")
|
||||
.detach()
|
||||
.asMap()
|
||||
@@ -263,7 +266,7 @@ final class HelidonContainerImpl extends Weld implements HelidonContainer {
|
||||
bm = CDI.current().getBeanManager();
|
||||
}
|
||||
|
||||
Config config = (Config) ConfigProvider.getConfig();
|
||||
org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
|
||||
|
||||
MpConfigProviderResolver.runtimeStart(config);
|
||||
|
||||
@@ -316,7 +319,8 @@ final class HelidonContainerImpl extends Weld implements HelidonContainer {
|
||||
now = System.currentTimeMillis() - now;
|
||||
LOGGER.fine("Container started in " + now + " millis (this excludes the initialization time)");
|
||||
|
||||
HelidonFeatures.print(HelidonFlavor.MP, config.get("features.print-details").asBoolean().orElse(false));
|
||||
HelidonFeatures.print(HelidonFlavor.MP,
|
||||
config.getOptionalValue("features.print-details", Boolean.class).orElse(false));
|
||||
|
||||
// shutdown hook should be added after all initialization is done, otherwise a race condition may happen
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
@@ -29,6 +29,7 @@ module io.helidon.microprofile.cdi {
|
||||
|
||||
requires io.helidon.common;
|
||||
requires io.helidon.config;
|
||||
requires io.helidon.config.mp;
|
||||
|
||||
requires weld.core.impl;
|
||||
requires weld.spi;
|
||||
@@ -36,6 +37,7 @@ module io.helidon.microprofile.cdi {
|
||||
requires weld.se.core;
|
||||
requires io.helidon.common.context;
|
||||
requires jakarta.inject.api;
|
||||
requires microprofile.config.api;
|
||||
|
||||
exports io.helidon.microprofile.cdi;
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
import javax.enterprise.inject.spi.CDI;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.mp.MpConfigSources;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -59,7 +59,11 @@ class HelidonContainerInitializerTest {
|
||||
@Test
|
||||
void testRestart() {
|
||||
// this is a reproducer for bug 1554
|
||||
Config config = Config.create(ConfigSources.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")));
|
||||
Config config = ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")))
|
||||
.build();
|
||||
|
||||
configResolver.registerConfig((org.eclipse.microprofile.config.Config) config, cl);
|
||||
// now we can start using SeContainerInitializer
|
||||
SeContainer container = SeContainerInitializer.newInstance()
|
||||
@@ -79,7 +83,11 @@ class HelidonContainerInitializerTest {
|
||||
@Test
|
||||
void testRestart2() {
|
||||
// this is a reproducer for bug 1554
|
||||
Config config = Config.create(ConfigSources.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")));
|
||||
Config config = ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")))
|
||||
.build();
|
||||
|
||||
configResolver.registerConfig((org.eclipse.microprofile.config.Config) config, cl);
|
||||
// now we can start using SeContainerInitializer
|
||||
SeContainerInitializer seContainerInitializer = SeContainerInitializer.newInstance();
|
||||
@@ -103,8 +111,13 @@ class HelidonContainerInitializerTest {
|
||||
|
||||
assertThrows(IllegalStateException.class, SeContainerInitializer::newInstance);
|
||||
|
||||
Config config = Config.create(ConfigSources.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")));
|
||||
configResolver.registerConfig((org.eclipse.microprofile.config.Config) config, cl);
|
||||
Config config = ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources
|
||||
.create(Map.of(HelidonContainerInitializer.CONFIG_ALLOW_INITIALIZER, "true")))
|
||||
.build();
|
||||
|
||||
configResolver.registerConfig(config, cl);
|
||||
|
||||
SeContainerInitializer seContainerInitializer = SeContainerInitializer.newInstance();
|
||||
assertThat(seContainerInitializer, instanceOf(HelidonContainerInitializer.class));
|
||||
|
||||
@@ -23,10 +23,10 @@ import javax.enterprise.inject.Instance;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.enterprise.inject.spi.CDI;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.MpConfigProviderResolver;
|
||||
import io.helidon.config.mp.MpConfigProviderResolver;
|
||||
import io.helidon.config.mp.MpConfigSources;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -68,13 +68,18 @@ class MainTest {
|
||||
assertThat(extension.events(), contains(TestExtension.BUILD_TIME_START,
|
||||
TestExtension.BUILD_TIME_END));
|
||||
|
||||
Config config = Config.create(ConfigSources.create(Map.of("key", "value")));
|
||||
|
||||
Config config = ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources.create(Map.of("key", "value")))
|
||||
.build();
|
||||
|
||||
ConfigProviderResolver.instance()
|
||||
.registerConfig((org.eclipse.microprofile.config.Config) config, Thread.currentThread().getContextClassLoader());
|
||||
.registerConfig(config, Thread.currentThread().getContextClassLoader());
|
||||
|
||||
instance.start();
|
||||
|
||||
Config runtimeConfig = extension.runtimeConfig();
|
||||
Object runtimeConfig = extension.runtimeConfig();
|
||||
assertThat(runtimeConfig, instanceOf(MpConfigProviderResolver.ConfigDelegate.class));
|
||||
assertThat(((MpConfigProviderResolver.ConfigDelegate) runtimeConfig).delegate(), sameInstance(config));
|
||||
|
||||
|
||||
@@ -37,22 +37,23 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config</artifactId>
|
||||
<artifactId>helidon-config-mp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-yaml</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-encryption</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.config</groupId>
|
||||
<artifactId>helidon-config-object-mapping</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.cdi</groupId>
|
||||
<artifactId>helidon-microprofile-cdi</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.enterprise</groupId>
|
||||
<artifactId>jakarta.enterprise.cdi-api</artifactId>
|
||||
@@ -64,6 +65,11 @@
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.cdi</groupId>
|
||||
<artifactId>helidon-microprofile-cdi</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.bundles</groupId>
|
||||
<artifactId>internal-test-libs</artifactId>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,6 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
@@ -38,6 +37,8 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
@@ -62,19 +63,23 @@ import javax.inject.Provider;
|
||||
import io.helidon.common.HelidonFeatures;
|
||||
import io.helidon.common.HelidonFlavor;
|
||||
import io.helidon.common.NativeImageHelper;
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.MissingValueException;
|
||||
import io.helidon.config.mp.MpConfig;
|
||||
import io.helidon.config.mp.MpConfigImpl;
|
||||
import io.helidon.config.mp.MpConfigProviderResolver;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.ConfigProvider;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
|
||||
/**
|
||||
* Extension to enable config injection in CDI container (all of {@link Config}, {@link org.eclipse.microprofile.config.Config}
|
||||
* and {@link ConfigProperty}).
|
||||
* Extension to enable config injection in CDI container (all of {@link io.helidon.config.Config},
|
||||
* {@link org.eclipse.microprofile.config.Config} and {@link ConfigProperty}).
|
||||
*/
|
||||
public class ConfigCdiExtension implements Extension {
|
||||
private static final Logger LOGGER = Logger.getLogger(ConfigCdiExtension.class.getName());
|
||||
private static final Pattern SPLIT_PATTERN = Pattern.compile("(?<!\\\\),");
|
||||
private static final Pattern ESCAPED_COMMA_PATTERN = Pattern.compile("\\,", Pattern.LITERAL);
|
||||
private static final Annotation CONFIG_PROPERTY_LITERAL = new ConfigProperty() {
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -111,7 +116,6 @@ public class ConfigCdiExtension implements Extension {
|
||||
}
|
||||
|
||||
private final List<InjectionPoint> ips = new LinkedList<>();
|
||||
private Config runtimeHelidonConfig;
|
||||
|
||||
/**
|
||||
* Constructor invoked by CDI container.
|
||||
@@ -174,10 +178,17 @@ public class ConfigCdiExtension implements Extension {
|
||||
.createWith(creationalContext -> new SerializableConfig());
|
||||
|
||||
abd.addBean()
|
||||
.addTransitiveTypeClosure(Config.class)
|
||||
.beanClass(Config.class)
|
||||
.addTransitiveTypeClosure(io.helidon.config.Config.class)
|
||||
.beanClass(io.helidon.config.Config.class)
|
||||
.scope(ApplicationScoped.class)
|
||||
.createWith(creationalContext -> (Config) ConfigProvider.getConfig());
|
||||
.createWith(creationalContext -> {
|
||||
Config config = ConfigProvider.getConfig();
|
||||
if (config instanceof io.helidon.config.Config) {
|
||||
return config;
|
||||
} else {
|
||||
return MpConfig.toHelidonConfig(config);
|
||||
}
|
||||
});
|
||||
|
||||
Set<Type> types = ips.stream()
|
||||
.map(InjectionPoint::getType)
|
||||
@@ -233,7 +244,7 @@ public class ConfigCdiExtension implements Extension {
|
||||
// this is build-time of native-image - e.g. run from command line or maven
|
||||
// logging may not be working/configured to deliver this message as it should
|
||||
System.err.println("You are accessing configuration key '" + configKey + "' during"
|
||||
+ " container initialization. This will not work nice with Graal native-image");
|
||||
+ " container initialization. This will not work nicely with Graal native-image");
|
||||
}
|
||||
|
||||
Type type = ip.getType();
|
||||
@@ -255,7 +266,11 @@ public class ConfigCdiExtension implements Extension {
|
||||
Map<String, String> - a detached key/value mapping of whole subtree
|
||||
*/
|
||||
FieldTypes fieldTypes = FieldTypes.create(type);
|
||||
Config config = (Config) ConfigProvider.getConfig();
|
||||
org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
|
||||
if (config instanceof MpConfigProviderResolver.ConfigDelegate) {
|
||||
// get the actual instance to have access to Helidon specific methods
|
||||
config = ((MpConfigProviderResolver.ConfigDelegate) config).delegate();
|
||||
}
|
||||
String defaultValue = defaultValue(annotation);
|
||||
Object value = configValue(config, fieldTypes, configKey, defaultValue);
|
||||
|
||||
@@ -284,9 +299,31 @@ public class ConfigCdiExtension implements Extension {
|
||||
}
|
||||
|
||||
private static <T> T withDefault(Config config, String key, Class<T> type, String defaultValue) {
|
||||
return config.get(key)
|
||||
.as(type)
|
||||
.orElseGet(() -> (null == defaultValue) ? null : config.convert(type, defaultValue));
|
||||
return config.getOptionalValue(key, type)
|
||||
.orElseGet(() -> convert(key, config, defaultValue, type));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T convert(String key, Config config, String value, Class<T> type) {
|
||||
if (null == value) {
|
||||
return null;
|
||||
}
|
||||
if (String.class.equals(type)) {
|
||||
return (T) value;
|
||||
}
|
||||
if (config instanceof MpConfigImpl) {
|
||||
return ((MpConfigImpl) config).getConverter(type)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Did not find converter for type "
|
||||
+ type.getName()
|
||||
+ ", for key "
|
||||
+ key))
|
||||
.convert(value);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Helidon CDI MP Config implementation requires Helidon config instance. "
|
||||
+ "Current config is " + config.getClass().getName()
|
||||
+ ", which is not supported, as we cannot convert arbitrary String values");
|
||||
|
||||
}
|
||||
|
||||
private static Object parameterizedConfigValue(Config config,
|
||||
@@ -321,7 +358,13 @@ public class ConfigCdiExtension implements Extension {
|
||||
typeArg2);
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(rawType)) {
|
||||
return config.get(configKey).detach().asMap().get();
|
||||
Map<String, String> result = new HashMap<>();
|
||||
config.getPropertyNames()
|
||||
.forEach(name -> {
|
||||
// workaround for race condition (if key disappears from source after we call getPropertyNames
|
||||
config.getOptionalValue(name, String.class).ifPresent(value -> result.put(name, value));
|
||||
});
|
||||
return result;
|
||||
} else if (Set.class.isAssignableFrom(rawType)) {
|
||||
return new LinkedHashSet<>(asList(config, configKey, typeArg, defaultValue));
|
||||
} else {
|
||||
@@ -330,32 +373,71 @@ public class ConfigCdiExtension implements Extension {
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> List<T> asList(Config config, String configKey, Class<T> typeArg, String defaultValue) {
|
||||
try {
|
||||
return config.get(configKey).asList(typeArg).get();
|
||||
} catch (MissingValueException e) {
|
||||
// if default
|
||||
if (null == defaultValue) {
|
||||
//noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException
|
||||
throw new NoSuchElementException("Missing list value for key "
|
||||
+ configKey
|
||||
+ ", original message: "
|
||||
+ e.getMessage());
|
||||
} else {
|
||||
if (defaultValue.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
static String[] toArray(String stringValue) {
|
||||
String[] values = SPLIT_PATTERN.split(stringValue, -1);
|
||||
|
||||
List<T> result = new LinkedList<>();
|
||||
|
||||
String[] values = defaultValue.split(",");
|
||||
for (String value : values) {
|
||||
result.add(config.convert(typeArg, value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
String value = values[i];
|
||||
values[i] = ESCAPED_COMMA_PATTERN.matcher(value).replaceAll(Matcher.quoteReplacement(","));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static <T> List<T> asList(Config config, String configKey, Class<T> typeArg, String defaultValue) {
|
||||
// first try to see if we have a direct value
|
||||
Optional<String> optionalValue = config.getOptionalValue(configKey, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
return toList(configKey, config, optionalValue.get(), typeArg);
|
||||
}
|
||||
|
||||
/*
|
||||
we also support indexed value
|
||||
e.g. for key "my.list" you can have both:
|
||||
my.list=12,13,14
|
||||
or (not and):
|
||||
my.list.0=12
|
||||
my.list.1=13
|
||||
*/
|
||||
|
||||
String indexedConfigKey = configKey + ".0";
|
||||
optionalValue = config.getOptionalValue(indexedConfigKey, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
List<T> result = new LinkedList<>();
|
||||
|
||||
// first element is already in
|
||||
result.add(convert(indexedConfigKey, config, optionalValue.get(), typeArg));
|
||||
|
||||
// hardcoded limit to lists of 1000 elements
|
||||
for (int i = 1; i < 1000; i++) {
|
||||
indexedConfigKey = configKey + "." + i;
|
||||
optionalValue = config.getOptionalValue(indexedConfigKey, String.class);
|
||||
if (optionalValue.isPresent()) {
|
||||
result.add(convert(indexedConfigKey, config, optionalValue.get(), typeArg));
|
||||
} else {
|
||||
// finish the iteration on first missing index
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (null == defaultValue) {
|
||||
throw new NoSuchElementException("Missing list value for key " + configKey);
|
||||
}
|
||||
|
||||
return toList(configKey, config, defaultValue, typeArg);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> List<T> toList(String configKey, Config config, String stringValue, Class<T> typeArg) {
|
||||
if (stringValue.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
// we have a comma separated list
|
||||
List<T> result = new LinkedList<>();
|
||||
for (String value : toArray(stringValue)) {
|
||||
result.add(convert(configKey, config, value, typeArg));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String defaultValue(ConfigProperty annotation) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2020 Oracle and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helidon implementation of microprofile config.
|
||||
* Helidon implementation of microprofile config for CDI.
|
||||
*/
|
||||
package io.helidon.microprofile.config;
|
||||
|
||||
@@ -23,7 +23,10 @@ module io.helidon.microprofile.config {
|
||||
requires jakarta.inject.api;
|
||||
requires io.helidon.common;
|
||||
requires io.helidon.config;
|
||||
requires microprofile.config.api;
|
||||
requires transitive microprofile.config.api;
|
||||
requires io.helidon.config.mp;
|
||||
requires java.annotation;
|
||||
requires io.helidon.common.serviceloader;
|
||||
|
||||
exports io.helidon.microprofile.config;
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@ public class MutableMpTest {
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value.set(value);
|
||||
listener.accept("value", value);
|
||||
if (null != listener) {
|
||||
listener.accept("value", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# needed to run unit tests independently (without beans.xml)
|
||||
# needed to run unit tests independently based on explicit beans using SeContainerInitializer
|
||||
mp.initializer.allow=true
|
||||
mp.initializer.warn=false
|
||||
|
||||
|
||||
@@ -65,14 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.metrics</groupId>
|
||||
<artifactId>microprofile-metrics-api</artifactId>
|
||||
<version>${version.lib.microprofile-metrics-api}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.annotation.versioning</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
|
||||
@@ -881,6 +881,18 @@ public class JwtAuthProvider extends SynchronousProvider implements Authenticati
|
||||
mpConfig.getOptionalValue(CONFIG_PUBLIC_KEY_PATH, String.class).ifPresent(this::publicKeyPath);
|
||||
mpConfig.getOptionalValue(CONFIG_EXPECTED_ISSUER, String.class).ifPresent(this::expectedIssuer);
|
||||
|
||||
if (null == publicKey && null == publicKeyPath) {
|
||||
// this is a fix for incomplete TCK tests
|
||||
// we will configure this location in our tck configuration
|
||||
String key = "helidon.mp.jwt.verify.publickey.location";
|
||||
mpConfig.getOptionalValue(key, String.class).ifPresent(it -> {
|
||||
publicKeyPath(it);
|
||||
LOGGER.warning("You have configured public key for JWT-Auth provider using a property"
|
||||
+ " reserved for TCK tests (" + key + "). Please use "
|
||||
+ CONFIG_PUBLIC_KEY_PATH + " instead.");
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.config.mp.MpConfigSources;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
|
||||
/**
|
||||
* Detached configuration of a single connector.
|
||||
@@ -49,13 +51,9 @@ class AdHocConfigBuilder {
|
||||
}
|
||||
|
||||
org.eclipse.microprofile.config.Config build() {
|
||||
Config newConfig = Config.builder(ConfigSources.create(configuration))
|
||||
.disableEnvironmentVariablesSource()
|
||||
.disableSystemPropertiesSource()
|
||||
.disableFilterServices()
|
||||
.disableSourceServices()
|
||||
.disableParserServices()
|
||||
return ConfigProviderResolver.instance()
|
||||
.getBuilder()
|
||||
.withSources(MpConfigSources.create(configuration))
|
||||
.build();
|
||||
return (org.eclipse.microprofile.config.Config) newConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ module io.helidon.microprofile.messaging {
|
||||
requires static jakarta.activation;
|
||||
requires jakarta.interceptor.api;
|
||||
requires io.helidon.config;
|
||||
requires io.helidon.config.mp;
|
||||
requires io.helidon.microprofile.config;
|
||||
requires io.helidon.microprofile.server;
|
||||
requires io.helidon.microprofile.reactive;
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package io.helidon.microprofile.messaging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -28,38 +26,24 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.LogManager;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
import javax.enterprise.inject.spi.CDI;
|
||||
|
||||
import io.helidon.config.Config;
|
||||
import io.helidon.config.ConfigSources;
|
||||
import io.helidon.microprofile.server.Server;
|
||||
import io.helidon.config.mp.MpConfigSources;
|
||||
import io.helidon.microprofile.server.ServerCdiExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.eclipse.microprofile.config.Config;
|
||||
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public abstract class AbstractCDITest {
|
||||
|
||||
static Server singleServerReference;
|
||||
|
||||
static {
|
||||
try (InputStream is = AbstractCDITest.class.getResourceAsStream("/logging.properties")) {
|
||||
LogManager.getLogManager().readConfiguration(is);
|
||||
} catch (IOException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected SeContainer cdiContainer;
|
||||
|
||||
protected Map<String, String> cdiConfig() {
|
||||
@@ -81,7 +65,6 @@ public abstract class AbstractCDITest {
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
try {
|
||||
singleServerReference.stop();
|
||||
cdiContainer.close();
|
||||
} catch (Throwable t) {
|
||||
//emergency cleanup see #1446
|
||||
@@ -111,18 +94,13 @@ public abstract class AbstractCDITest {
|
||||
private static SeContainer startCdiContainer(Map<String, String> p, Set<Class<?>> beanClasses) {
|
||||
p = new HashMap<>(p);
|
||||
p.put("mp.initializer.allow", "true");
|
||||
Config config = Config.builder()
|
||||
.sources(ConfigSources.create(p))
|
||||
Config config = ConfigProviderResolver.instance().getBuilder()
|
||||
.withSources(MpConfigSources.create(p))
|
||||
.build();
|
||||
|
||||
final Server.Builder builder = Server.builder();
|
||||
assertNotNull(builder);
|
||||
builder.config(config);
|
||||
singleServerReference = builder.build();
|
||||
ConfigProviderResolver.instance()
|
||||
.registerConfig((org.eclipse.microprofile.config.Config) config, Thread.currentThread().getContextClassLoader());
|
||||
.registerConfig(config, Thread.currentThread().getContextClassLoader());
|
||||
final SeContainerInitializer initializer = SeContainerInitializer.newInstance();
|
||||
assertNotNull(initializer);
|
||||
initializer.addBeanClasses(beanClasses.toArray(new Class<?>[0]));
|
||||
return initializer.initialize();
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
<description>Server of the microprofile implementation</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.microprofile.config</groupId>
|
||||
<artifactId>microprofile-config-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.cdi</groupId>
|
||||
<artifactId>helidon-microprofile-cdi</artifactId>
|
||||
@@ -74,7 +70,6 @@
|
||||
<dependency>
|
||||
<groupId>io.helidon.microprofile.config</groupId>
|
||||
<artifactId>helidon-microprofile-config</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- this is to include JSON-P native image configuration -->
|
||||
|
||||
@@ -104,9 +104,7 @@ public class JaxRsCdiExtension implements Extension {
|
||||
if (applications.isEmpty() && applicationMetas.isEmpty()) {
|
||||
// create a synthetic application from all resource classes
|
||||
// the classes set must be created before the lambda, as resources are cleared later on
|
||||
if (resources.isEmpty()) {
|
||||
LOGGER.warning("There are no JAX-RS applications or resources. Maybe you forgot META-INF/beans.xml file?");
|
||||
} else {
|
||||
if (!resources.isEmpty()) {
|
||||
Set<Class<?>> classes = new HashSet<>(resources);
|
||||
applicationMetas.add(JaxRsApplication.builder()
|
||||
.synthetic(true)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user