From 47d4aa59e3770a94f56ed8f8d2189b5869b104e3 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 17 Sep 2020 17:39:48 +0100 Subject: [PATCH] Update SmallRye Config to 1.9.0. --- bom/application/pom.xml | 2 +- .../runtime/configuration/Substitutions.java | 26 ++ .../graal/ClassDefinerSubstitutions.java | 18 ++ .../arc/deployment/ConfigBuildStep.java | 79 +++++ .../deployment/ConfigMappingBuildItem.java | 21 ++ .../arc/test/config/ConfigMappingTest.java | 293 ++++++++++++++++++ .../arc/runtime/ConfigMappingCreator.java | 19 ++ .../quarkus/arc/runtime/ConfigRecorder.java | 8 + 8 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/ClassDefinerSubstitutions.java create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigMappingBuildItem.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index f0ab65da932ed..a6d70ee780e48 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -34,7 +34,7 @@ 1.0.1 1.4.1 1.4.0 - 1.8.6 + 1.9.0 2.2.3 2.4.3 2.0.9 diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java index 8754453787476..8a7f5bc3a2e8b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java @@ -12,6 +12,7 @@ import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; import com.oracle.svm.core.threadlocal.FastThreadLocalInt; +import io.smallrye.common.constraint.Assert; import io.smallrye.config.Expressions; /** @@ -52,4 +53,29 @@ public static T withoutExpansion(Supplier supplier) { } } } + + @TargetClass(className = "io.smallrye.config.ConfigMappingObjectLoader") + static final class Target_ConfigMappingObjectLoader { + @Substitute + static Class createMappingObjectClass(final String className, final byte[] bytes) { + return null; + } + } + + @TargetClass(className = "io.smallrye.config.ConfigMappingInterface") + static final class Target_ConfigMappingInterface { + @Alias + static ClassValue cv = null; + + // ClassValue is substituted by a regular ConcurrentHashMap - java.lang.ClassValue.get(JavaLangSubstitutions.java:514) + @Substitute + public static Target_ConfigMappingInterface getConfigurationInterface(Class interfaceType) { + Assert.checkNotNullParam("interfaceType", interfaceType); + try { + return cv.get(interfaceType); + } catch (NullPointerException e) { + return null; + } + } + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/ClassDefinerSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/ClassDefinerSubstitutions.java new file mode 100644 index 0000000000000..3760352943b86 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/ClassDefinerSubstitutions.java @@ -0,0 +1,18 @@ +package io.quarkus.runtime.graal; + +import java.lang.invoke.MethodHandles; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.TargetClass; + +import io.smallrye.common.classloader.ClassDefiner; + +final class ClassDefinerSubstitutions { + @TargetClass(ClassDefiner.class) + static final class Target_io_smallrye_common_classloader_ClassDefiner { + @Delete + public static Class defineClass(MethodHandles.Lookup lookup, Class parent, String className, byte[] classBytes) { + return null; + } + } +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java index 44bc1fe124d6d..72bf0df64291e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -1,21 +1,26 @@ package io.quarkus.arc.deployment; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static io.smallrye.config.ConfigMappings.ConfigMappingWithPrefix.configMappingWithPrefix; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import javax.enterprise.context.Dependent; import javax.enterprise.inject.CreationException; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -30,15 +35,23 @@ import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.arc.runtime.ConfigBeanCreator; +import io.quarkus.arc.runtime.ConfigMappingCreator; import io.quarkus.arc.runtime.ConfigRecorder; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.annotations.ConfigPhase; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappingMetadata; +import io.smallrye.config.ConfigMappingObjectLoader; +import io.smallrye.config.ConfigMappings.ConfigMappingWithPrefix; import io.smallrye.config.inject.ConfigProducer; /** @@ -47,6 +60,7 @@ public class ConfigBuildStep { private static final DotName CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); + private static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName()); private static final DotName SET_NAME = DotName.createSimple(Set.class.getName()); private static final DotName LIST_NAME = DotName.createSimple(List.class.getName()); @@ -174,6 +188,71 @@ public void register(RegistrationContext context) { }); } + @BuildStep + void generateConfigMappings( + CombinedIndexBuildItem combinedIndex, + BeanRegistrationPhaseBuildItem beanRegistrationPhase, + BuildProducer generatedClasses, + BuildProducer reflectiveClasses, + BuildProducer configMappings, + BuildProducer beanConfigurators) { + + for (AnnotationInstance instance : combinedIndex.getIndex().getAnnotations(CONFIG_MAPPING_NAME)) { + AnnotationTarget target = instance.target(); + if (!target.kind().equals(AnnotationTarget.Kind.CLASS)) { + continue; + } + + ClassInfo classInfo = target.asClass(); + String prefix = Optional.ofNullable(instance.value("prefix")).map(AnnotationValue::asString).orElse(""); + + ConfigMappingMetadata configMappingMetadata = ConfigMappingObjectLoader + .getConfigMappingMetadata(toClass(classInfo)); + + generatedClasses.produce(new GeneratedClassBuildItem(true, configMappingMetadata.getClassName(), + configMappingMetadata.getClassBytes())); + reflectiveClasses + .produce(new ReflectiveClassBuildItem(true, false, configMappingMetadata.getInterfaceType())); + reflectiveClasses + .produce(new ReflectiveClassBuildItem(true, false, false, configMappingMetadata.getClassName())); + configMappings.produce(new ConfigMappingBuildItem(toClass(classInfo), prefix)); + + beanConfigurators.produce(new BeanConfiguratorBuildItem( + beanRegistrationPhase.getContext() + .configure(configMappingMetadata.getInterfaceType()) + .types(configMappingMetadata.getInterfaceType()) + .creator(ConfigMappingCreator.class) + .param("interfaceType", configMappingMetadata.getInterfaceType()))); + } + } + + @BuildStep + @Record(RUNTIME_INIT) + void registerConfigMappings( + RecorderContext context, + ConfigRecorder recorder, + List configMappings) throws Exception { + + context.registerNonDefaultConstructor( + ConfigMappingWithPrefix.class.getDeclaredConstructor(Class.class, String.class), + configMappingWithPrefix -> Stream.of(configMappingWithPrefix.getKlass(), configMappingWithPrefix.getPrefix()) + .collect(toList())); + + recorder.registerConfigMappings(configMappings.stream() + .map(configMapping -> configMappingWithPrefix(configMapping.getInterfaceType(), configMapping.getPrefix())) + .collect(toSet())); + } + + private static Class toClass(ClassInfo classInfo) { + String className = classInfo.name().toString(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("The class (" + className + ") cannot be created during deployment.", e); + } + } + private String getPropertyName(String name, ClassInfo declaringClass) { StringBuilder builder = new StringBuilder(); if (declaringClass.enclosingClass() == null) { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigMappingBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigMappingBuildItem.java new file mode 100644 index 0000000000000..b97fa357e77e9 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigMappingBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class ConfigMappingBuildItem extends MultiBuildItem { + private final Class interfaceType; + private final String prefix; + + public ConfigMappingBuildItem(final Class interfaceType, final String prefix) { + this.interfaceType = interfaceType; + this.prefix = prefix; + } + + public Class getInterfaceType() { + return interfaceType; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java new file mode 100644 index 0000000000000..8620e9ebbc4e5 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java @@ -0,0 +1,293 @@ +package io.quarkus.arc.test.config; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.Converter; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.WithConverter; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; +import io.smallrye.config.WithParentName; + +public class ConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("config.my.prop=1234\n" + + "group.host=localhost\n" + + "group.port=8080\n" + + "types.int=9\n" + + "types.long=9999999999\n" + + "types.float=99.9\n" + + "types.double=99.99\n" + + "types.char=c\n" + + "types.boolean=true\n" + + "types.value=1234\n" + + "optionals.server.host=localhost\n" + + "optionals.server.port=8080\n" + + "optionals.optional=optional\n" + + "optionals.optional.int=9\n" + + "collections.strings=foo,bar\n" + + "collections.ints=1,2,3\n" + + "maps.server.host=localhost\n" + + "maps.server.port=8080\n" + + "maps.group.server.host=localhost\n" + + "maps.group.server.port=8080\n" + + "converters.foo=notbar\n" + + "override.server.host=localhost\n" + + "override.server.port=8080\n" + + "cloud.server.host=cloud\n" + + "cloud.server.port=9000\n"), "application.properties")); + @Inject + Config config; + + @ConfigMapping(prefix = "config") + public interface MyConfigMapping { + @WithName("my.prop") + String myProp(); + } + + @Inject + MyConfigMapping myConfigMapping; + + @Test + void configMapping() { + SmallRyeConfig smallRyeConfig = ((SmallRyeConfig) config); + MyConfigMapping configMapping = smallRyeConfig.getConfigMapping(MyConfigMapping.class); + assertNotNull(configMapping); + assertEquals("1234", configMapping.myProp()); + + assertNotNull(myConfigMapping); + assertEquals("1234", myConfigMapping.myProp()); + } + + @ConfigMapping(prefix = "group") + public interface GroupMapping { + @WithParentName + ServerHost host(); + + @WithParentName + ServerPort port(); + + interface ServerHost { + String host(); + } + + interface ServerPort { + int port(); + } + } + + @Inject + GroupMapping groupMapping; + + @Test + void groups() { + assertNotNull(groupMapping); + assertEquals("localhost", groupMapping.host().host()); + assertEquals(8080, groupMapping.port().port()); + } + + @ConfigMapping(prefix = "types") + public interface SomeTypes { + @WithName("int") + int intPrimitive(); + + @WithName("int") + Integer intWrapper(); + + @WithName("long") + long longPrimitive(); + + @WithName("long") + Long longWrapper(); + + @WithName("float") + float floatPrimitive(); + + @WithName("float") + Float floatWrapper(); + + @WithName("double") + double doublePrimitive(); + + @WithName("double") + Double doubleWrapper(); + + @WithName("char") + char charPrimitive(); + + @WithName("char") + Character charWrapper(); + + @WithName("boolean") + boolean booleanPrimitive(); + + @WithName("boolean") + Boolean booleanWrapper(); + + @WithName("value") + ConfigValue configValue(); + } + + @Inject + SomeTypes types; + + @Test + void types() { + assertNotNull(types); + assertEquals(9, types.intPrimitive()); + assertEquals(9, types.intWrapper()); + assertEquals(9999999999L, types.longPrimitive()); + assertEquals(9999999999L, types.longWrapper()); + assertEquals(99.9f, types.floatPrimitive()); + assertEquals(99.9f, types.floatWrapper()); + assertEquals(99.99, types.doublePrimitive()); + assertEquals(99.99, types.doubleWrapper()); + assertEquals('c', types.charPrimitive()); + assertEquals('c', types.charWrapper()); + assertTrue(types.booleanPrimitive()); + assertTrue(types.booleanWrapper()); + assertEquals("1234", types.configValue().getValue()); + } + + @ConfigMapping(prefix = "optionals") + public interface Optionals { + Optional server(); + + Optional optional(); + + @WithName("optional.int") + OptionalInt optionalInt(); + + interface Server { + String host(); + + int port(); + } + } + + @Inject + Optionals optionals; + + @Test + void optionals() { + assertTrue(optionals.server().isPresent()); + assertEquals("localhost", optionals.server().get().host()); + assertEquals(8080, optionals.server().get().port()); + + assertTrue(optionals.optional().isPresent()); + assertEquals("optional", optionals.optional().get()); + assertTrue(optionals.optionalInt().isPresent()); + assertEquals(9, optionals.optionalInt().getAsInt()); + } + + @ConfigMapping(prefix = "collections") + public interface Collections { + @WithName("strings") + List listStrings(); + + @WithName("ints") + List listInts(); + } + + @Inject + Collections collections; + + @Test + void collections() { + assertEquals(Stream.of("foo", "bar").collect(toList()), collections.listStrings()); + assertEquals(Stream.of(1, 2, 3).collect(toList()), collections.listInts()); + } + + @ConfigMapping(prefix = "maps") + public interface Maps { + Map server(); + + Map group(); + + interface Server { + String host(); + + int port(); + } + } + + @Inject + Maps maps; + + @Test + void maps() { + assertEquals("localhost", maps.server().get("host")); + assertEquals(8080, Integer.valueOf(maps.server().get("port"))); + + assertEquals("localhost", maps.group().get("server").host()); + assertEquals(8080, maps.group().get("server").port()); + } + + @ConfigMapping(prefix = "defaults") + public interface Defaults { + @WithDefault("foo") + String foo(); + + @WithDefault("bar") + String bar(); + } + + @Inject + Defaults defaults; + + @Test + void defaults() { + assertEquals("foo", defaults.foo()); + assertEquals("bar", defaults.bar()); + assertEquals("foo", config.getValue("defaults.foo", String.class)); + + final List propertyNames = stream(config.getPropertyNames().spliterator(), false).collect(toList()); + assertFalse(propertyNames.contains("defaults.foo")); + } + + @ConfigMapping(prefix = "converters") + public interface Converters { + @WithConverter(FooBarConverter.class) + String foo(); + + class FooBarConverter implements Converter { + @Override + public String convert(final String value) { + return "bar"; + } + } + } + + @Inject + Converters converters; + + @Test + void converters() { + assertEquals("bar", converters.foo()); + } +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java new file mode 100644 index 0000000000000..8c05ae1384feb --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigMappingCreator.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.runtime; + +import java.util.Map; + +import javax.enterprise.context.spi.CreationalContext; + +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.arc.BeanCreator; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigMappingCreator implements BeanCreator { + @Override + public Object create(CreationalContext creationalContext, Map params) { + Class interfaceType = (Class) params.get("interfaceType"); + SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + return config.getConfigMapping(interfaceType); + } +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigRecorder.java index 00f4a4398559e..ce742a5328a04 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigRecorder.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigRecorder.java @@ -10,6 +10,8 @@ import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.runtime.annotations.Recorder; +import io.smallrye.config.ConfigMappings; +import io.smallrye.config.SmallRyeConfig; /** * @author Martin Kouba @@ -44,6 +46,12 @@ public void validateConfigProperties(Map> properties) { } } + public void registerConfigMappings(final Set configMappingsWithPrefix) + throws Exception { + SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + config.getConfigMappings().registerConfigMappings(config, configMappingsWithPrefix); + } + private Class load(String className, ClassLoader cl) { switch (className) { case "boolean":