From 6625d56d1a7d1b7644e8dcfd9226a6da113a01b3 Mon Sep 17 00:00:00 2001
From: Roberto Cortez <radcortez@yahoo.com>
Date: Wed, 6 Mar 2024 16:53:16 +0000
Subject: [PATCH] Update MicroProfile Config to 3.1

---
 bom/application/pom.xml                       |   2 +-
 .../arc/deployment/ConfigBuildStep.java       | 108 ++++++++++++------
 .../deployment/ConfigPropertyBuildItem.java   |  36 +++++-
 .../ClassConfigurationPropertiesUtil.java     |   5 +-
 .../InterfaceConfigurationPropertiesUtil.java |   4 +-
 tcks/microprofile-config/pom.xml              |   6 +-
 6 files changed, 114 insertions(+), 47 deletions(-)

diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index f9e8c714810e3..d19b9565d1224 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -39,7 +39,7 @@
         <hdrhistogram.version>2.1.12</hdrhistogram.version><!-- keep in sync with micrometer -->
         <google-auth.version>0.22.0</google-auth.version>
         <graphql-java.version>21.3</graphql-java.version> <!-- keep in sync with smallrye-graphql -->
-        <microprofile-config-api.version>3.0.3</microprofile-config-api.version>
+        <microprofile-config-api.version>3.1</microprofile-config-api.version>
         <microprofile-health-api.version>4.0.1</microprofile-health-api.version>
         <microprofile-metrics-api.version>4.0.1</microprofile-metrics-api.version>
         <microprofile-context-propagation.version>1.3</microprofile-context-propagation.version>
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 bffd54d9b832c..8e9dce3d6591d 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
@@ -2,6 +2,7 @@
 
 import static io.quarkus.arc.processor.Annotations.getParameterAnnotations;
 import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
+import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
 import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Kind.MAPPING;
 import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Kind.PROPERTIES;
 import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME;
@@ -49,8 +50,10 @@
 import io.quarkus.arc.processor.Annotations;
 import io.quarkus.arc.processor.AnnotationsTransformer;
 import io.quarkus.arc.processor.BeanConfigurator;
+import io.quarkus.arc.processor.BuiltinScope;
 import io.quarkus.arc.processor.DotNames;
 import io.quarkus.arc.processor.InjectionPointInfo;
+import io.quarkus.arc.processor.ObserverInfo;
 import io.quarkus.arc.runtime.ConfigBeanCreator;
 import io.quarkus.arc.runtime.ConfigMappingCreator;
 import io.quarkus.arc.runtime.ConfigRecorder;
@@ -135,8 +138,23 @@ void registerCustomConfigBeanTypes(BeanDiscoveryFinishedBuildItem beanDiscovery,
     }
 
     @BuildStep
-    void validateConfigInjectionPoints(ValidationPhaseBuildItem validationPhase,
-            BuildProducer<ConfigPropertyBuildItem> configProperties) {
+    void configPropertyInjectionPoints(
+            ValidationPhaseBuildItem validationPhase,
+            BuildProducer<ConfigPropertyBuildItem> configProperties,
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
+
+        // @Observes @Initialized(ApplicationScoped.class) requires validation at static init
+        Set<MethodInfo> observerMethods = new HashSet<>();
+        for (ObserverInfo observer : validationPhase.getBeanProcessor().getBeanDeployment().getObservers()) {
+            if (observer.isSynthetic()) {
+                continue;
+            }
+            AnnotationInstance instance = Annotations.getParameterAnnotation(observer.getObserverMethod(),
+                    DotNames.INITIALIZED);
+            if (instance != null && instance.value().asClass().name().equals(BuiltinScope.APPLICATION.getName())) {
+                observerMethods.add(observer.getObserverMethod());
+            }
+        }
 
         for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) {
             if (injectionPoint.hasDefaultedQualifier()) {
@@ -185,47 +203,43 @@ void validateConfigInjectionPoints(ValidationPhaseBuildItem validationPhase,
                     propertyDefaultValue = defaultValue.asString();
                 }
 
-                configProperties.produce(new ConfigPropertyBuildItem(propertyName, injectedType, propertyDefaultValue));
+                if (injectionPoint.getTarget().kind().equals(METHOD)
+                        && observerMethods.contains(injectionPoint.getTarget().asMethod())) {
+                    configProperties
+                            .produce(ConfigPropertyBuildItem.staticInit(propertyName, injectedType, propertyDefaultValue));
+                }
+
+                configProperties.produce(ConfigPropertyBuildItem.runtimeInit(propertyName, injectedType, propertyDefaultValue));
             }
         }
     }
 
     @BuildStep
-    @Record(RUNTIME_INIT)
-    void validateConfigValues(ConfigRecorder recorder, List<ConfigPropertyBuildItem> configProperties,
-            BeanContainerBuildItem beanContainer, BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
-        // IMPL NOTE: we do depend on BeanContainerBuildItem to make sure that the BeanDeploymentValidator finished its processing
-
-        // the non-primitive types need to be registered for reflection since Class.forName is used at runtime to load the class
-        for (ConfigPropertyBuildItem item : configProperties) {
-            Type requiredType = item.getPropertyType();
-            String propertyType = requiredType.name().toString();
-            if (requiredType.kind() != Kind.PRIMITIVE) {
-                reflectiveClass.produce(ReflectiveClassBuildItem.builder(propertyType).build());
-            }
-        }
+    @Record(STATIC_INIT)
+    void validateStaticInitConfigProperty(
+            ConfigRecorder recorder,
+            List<ConfigPropertyBuildItem> configProperties,
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
 
-        Set<ConfigValidationMetadata> propertiesToValidate = new HashSet<>();
-        for (ConfigPropertyBuildItem configProperty : configProperties) {
-            String rawTypeName = configProperty.getPropertyType().name().toString();
-            List<String> actualTypeArgumentNames = Collections.emptyList();
-            if (configProperty.getPropertyType().kind() == Kind.PARAMETERIZED_TYPE) {
-                List<Type> argumentTypes = configProperty.getPropertyType().asParameterizedType().arguments();
-                actualTypeArgumentNames = new ArrayList<>(argumentTypes.size());
-                for (Type argumentType : argumentTypes) {
-                    actualTypeArgumentNames.add(argumentType.name().toString());
-                    if (argumentType.kind() != Kind.PRIMITIVE) {
-                        reflectiveClass.produce(ReflectiveClassBuildItem.builder(argumentType.name().toString())
-                                .build());
-                    }
-                }
+        recorder.validateConfigProperties(
+                configProperties.stream()
+                        .filter(ConfigPropertyBuildItem::isStaticInit)
+                        .map(p -> configPropertyToConfigValidation(p, reflectiveClass))
+                        .collect(toSet()));
+    }
 
-            }
-            propertiesToValidate.add(new ConfigValidationMetadata(configProperty.getPropertyName(),
-                    rawTypeName, actualTypeArgumentNames, configProperty.getDefaultValue()));
-        }
+    @BuildStep
+    @Record(RUNTIME_INIT)
+    void validateRuntimeConfigProperty(
+            ConfigRecorder recorder,
+            List<ConfigPropertyBuildItem> configProperties,
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
 
-        recorder.validateConfigProperties(propertiesToValidate);
+        recorder.validateConfigProperties(
+                configProperties.stream()
+                        .filter(ConfigPropertyBuildItem::isRuntimeInit)
+                        .map(p -> configPropertyToConfigValidation(p, reflectiveClass))
+                        .collect(toSet()));
     }
 
     @BuildStep
@@ -524,6 +538,30 @@ public static boolean isHandledByProducers(Type type) {
                 MP_CONFIG_VALUE_NAME.equals(type.name());
     }
 
+    private static ConfigValidationMetadata configPropertyToConfigValidation(ConfigPropertyBuildItem configProperty,
+            BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
+        String typeName = configProperty.getPropertyType().name().toString();
+        List<String> typeArgumentNames = Collections.emptyList();
+
+        if (configProperty.getPropertyType().kind() != Kind.PRIMITIVE) {
+            reflectiveClass.produce(ReflectiveClassBuildItem.builder(typeName).build());
+        }
+
+        if (configProperty.getPropertyType().kind() == Kind.PARAMETERIZED_TYPE) {
+            List<Type> argumentTypes = configProperty.getPropertyType().asParameterizedType().arguments();
+            typeArgumentNames = new ArrayList<>(argumentTypes.size());
+            for (Type argumentType : argumentTypes) {
+                typeArgumentNames.add(argumentType.name().toString());
+                if (argumentType.kind() != Kind.PRIMITIVE) {
+                    reflectiveClass.produce(ReflectiveClassBuildItem.builder(argumentType.name().toString()).build());
+                }
+            }
+        }
+
+        return new ConfigValidationMetadata(configProperty.getPropertyName(), typeName, typeArgumentNames,
+                configProperty.getDefaultValue());
+    }
+
     private static Map<Type, ConfigClassBuildItem> configClassesToTypesMap(List<ConfigClassBuildItem> configClasses,
             ConfigClassBuildItem.Kind kind) {
         Map<Type, ConfigClassBuildItem> configClassesTypes = new HashMap<>();
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java
index ad5fef88ab6cf..5e78261619882 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java
@@ -3,6 +3,7 @@
 import org.jboss.jandex.Type;
 
 import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.runtime.ExecutionMode;
 
 /**
  * Represents a mandatory config property that needs to be validated at runtime.
@@ -11,11 +12,18 @@ public final class ConfigPropertyBuildItem extends MultiBuildItem {
     private final String propertyName;
     private final Type propertyType;
     private final String defaultValue;
+    private final ExecutionMode executionMode;
+
+    private ConfigPropertyBuildItem(
+            final String propertyName,
+            final Type propertyType,
+            final String defaultValue,
+            final ExecutionMode executionMode) {
 
-    public ConfigPropertyBuildItem(final String propertyName, final Type propertyType, final String defaultValue) {
         this.propertyName = propertyName;
         this.propertyType = propertyType;
         this.defaultValue = defaultValue;
+        this.executionMode = executionMode;
     }
 
     public String getPropertyName() {
@@ -29,4 +37,30 @@ public Type getPropertyType() {
     public String getDefaultValue() {
         return defaultValue;
     }
+
+    public ExecutionMode getExecutionMode() {
+        return executionMode;
+    }
+
+    public boolean isStaticInit() {
+        return executionMode.equals(ExecutionMode.STATIC_INIT);
+    }
+
+    public boolean isRuntimeInit() {
+        return executionMode.equals(ExecutionMode.RUNTIME_INIT);
+    }
+
+    public static ConfigPropertyBuildItem staticInit(
+            final String propertyName,
+            final Type propertyType,
+            final String defaultValue) {
+        return new ConfigPropertyBuildItem(propertyName, propertyType, defaultValue, ExecutionMode.STATIC_INIT);
+    }
+
+    public static ConfigPropertyBuildItem runtimeInit(
+            final String propertyName,
+            final Type propertyType,
+            final String defaultValue) {
+        return new ConfigPropertyBuildItem(propertyName, propertyType, defaultValue, ExecutionMode.RUNTIME_INIT);
+    }
 }
diff --git a/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ClassConfigurationPropertiesUtil.java b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ClassConfigurationPropertiesUtil.java
index a9082db669ce8..a8b14681e05b9 100644
--- a/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ClassConfigurationPropertiesUtil.java
+++ b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/ClassConfigurationPropertiesUtil.java
@@ -399,9 +399,8 @@ private ResultHandle populateConfigObject(ClassLoader classLoader, ClassInfo con
         }
 
         for (ConfigPropertyBuildItemCandidate candidate : configPropertyBuildItemCandidates) {
-            configProperties
-                    .produce(new ConfigPropertyBuildItem(candidate.getConfigPropertyName(), candidate.getConfigPropertyType(),
-                            null));
+            configProperties.produce(ConfigPropertyBuildItem.runtimeInit(candidate.getConfigPropertyName(),
+                    candidate.getConfigPropertyType(), null));
         }
 
         return configObject;
diff --git a/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/InterfaceConfigurationPropertiesUtil.java b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/InterfaceConfigurationPropertiesUtil.java
index 7b642060b1cd9..a1dec9502c709 100644
--- a/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/InterfaceConfigurationPropertiesUtil.java
+++ b/extensions/spring-boot-properties/deployment/src/main/java/io/quarkus/spring/boot/properties/deployment/InterfaceConfigurationPropertiesUtil.java
@@ -259,8 +259,8 @@ private String generateImplementationForInterfaceConfigPropertiesRec(ClassInfo o
                                     method.declaringClass().name(), methodCreator, config);
                             methodCreator.returnValue(value);
                             if (defaultValueStr == null || ConfigProperty.UNCONFIGURED_VALUE.equals(defaultValueStr)) {
-                                configProperties
-                                        .produce(new ConfigPropertyBuildItem(fullConfigName, returnType, defaultValueStr));
+                                configProperties.produce(
+                                        ConfigPropertyBuildItem.runtimeInit(fullConfigName, returnType, defaultValueStr));
                             }
                         }
                     }
diff --git a/tcks/microprofile-config/pom.xml b/tcks/microprofile-config/pom.xml
index f36b1ca04bfd5..2263e81879cb7 100644
--- a/tcks/microprofile-config/pom.xml
+++ b/tcks/microprofile-config/pom.xml
@@ -13,7 +13,7 @@
     <name>Quarkus - TCK - MicroProfile Config</name>
 
     <properties>
-        <microprofile-config-tck.version>3.0.3</microprofile-config-tck.version>
+        <microprofile-config-tck.version>3.1</microprofile-config-tck.version>
     </properties>
 
     <build>
@@ -43,10 +43,6 @@
                     <systemPropertyVariables>
                         <!-- Disable quarkus optimization -->
                         <quarkus.arc.remove-unused-beans>false</quarkus.arc.remove-unused-beans>
-                        <!-- 1. There is a bug in TCK: -->
-                        <!-- https://github.com/eclipse/microprofile-config/issues/543 -->
-                        <!-- 2. After transformation we get a ClassCastException which is very likely caused by a class loading problem in our Arquillian adapter -->
-                        <quarkus.arc.transform-unproxyable-classes>false</quarkus.arc.transform-unproxyable-classes>
                     </systemPropertyVariables>
                     <!-- This workaround allows us to run a single test using 
                         the "test" system property -->