From 6378f80f1c4ff0ad9a50e1cc9944163e2a8eeeeb Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 7 Sep 2021 13:38:54 +0100 Subject: [PATCH] Do not validate configs in unused beans --- .../builditem/ConfigClassBuildItem.java | 34 ++- .../builditem/ConfigMappingBuildItem.java | 46 +++ .../builditem/ConfigPropertiesBuildItem.java | 41 +++ .../configuration/ConfigMappingUtils.java | 46 +-- .../steps/ConfigGenerationBuildStep.java | 20 +- .../arc/deployment/ConfigBuildStep.java | 275 +++++++++++++++--- ...ConfigPropertyInjectionValidationTest.java | 5 +- .../arc/test/config/NullConverterTest.java | 5 +- .../test/config/RemovedConfigMappingTest.java | 40 +++ .../config/RemovedConfigPropertiesTest.java | 47 +++ .../config/RemovedConfigPropertyTest.java | 31 ++ .../config/UnremovedConfigMappingTest.java | 39 +++ .../config/ConfigMappingValidatorTest.java | 2 + .../io/quarkus/it/smallrye/config/Cloud.java | 2 + 14 files changed, 527 insertions(+), 106 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigMappingBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigPropertiesBuildItem.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigMappingTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertiesTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertyTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/UnremovedConfigMappingTest.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java index 0a51d1f063af6..949d642025dcc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java @@ -1,9 +1,11 @@ package io.quarkus.deployment.builditem; +import java.util.Objects; import java.util.Set; +import org.jboss.jandex.DotName; + import io.quarkus.builder.item.MultiBuildItem; -import io.quarkus.runtime.annotations.StaticInitSafe; public final class ConfigClassBuildItem extends MultiBuildItem { private final Class configClass; @@ -11,6 +13,8 @@ public final class ConfigClassBuildItem extends MultiBuildItem { private final String prefix; private final Type type; + private final DotName name; + public ConfigClassBuildItem( final Class configClass, final Set generatedClasses, @@ -21,6 +25,7 @@ public ConfigClassBuildItem( this.generatedClasses = generatedClasses; this.prefix = prefix; this.type = type; + this.name = DotName.createSimple(configClass.getName()); } public Class getConfigClass() { @@ -39,6 +44,10 @@ public Type getType() { return type; } + public DotName getName() { + return name; + } + public boolean isMapping() { return Type.MAPPING.equals(type); } @@ -47,12 +56,27 @@ public boolean isProperties() { return Type.PROPERTIES.equals(type); } - public boolean isStaticInitSafe() { - return configClass.isAnnotationPresent(StaticInitSafe.class); - } - public enum Type { MAPPING, PROPERTIES } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ConfigClassBuildItem that = (ConfigClassBuildItem) o; + return configClass.equals(that.configClass) && + prefix.equals(that.prefix) && + type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(configClass, prefix, type); + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigMappingBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigMappingBuildItem.java new file mode 100644 index 0000000000000..78639aaccc718 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigMappingBuildItem.java @@ -0,0 +1,46 @@ +package io.quarkus.deployment.builditem; + +import java.util.Objects; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.annotations.StaticInitSafe; + +public final class ConfigMappingBuildItem extends MultiBuildItem { + private final Class configClass; + private final String prefix; + + public ConfigMappingBuildItem(final Class configClass, final String prefix) { + this.configClass = configClass; + this.prefix = prefix; + } + + public Class getConfigClass() { + return configClass; + } + + public String getPrefix() { + return prefix; + } + + public boolean isStaticInitSafe() { + return configClass.isAnnotationPresent(StaticInitSafe.class); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ConfigMappingBuildItem that = (ConfigMappingBuildItem) o; + return configClass.equals(that.configClass) && + prefix.equals(that.prefix); + } + + @Override + public int hashCode() { + return Objects.hash(configClass, prefix); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigPropertiesBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigPropertiesBuildItem.java new file mode 100644 index 0000000000000..e995505590dce --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigPropertiesBuildItem.java @@ -0,0 +1,41 @@ +package io.quarkus.deployment.builditem; + +import java.util.Objects; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class ConfigPropertiesBuildItem extends MultiBuildItem { + private final Class configClass; + private final String prefix; + + public ConfigPropertiesBuildItem(final Class configClass, final String prefix) { + this.configClass = configClass; + this.prefix = prefix; + } + + public Class getConfigClass() { + return configClass; + } + + public String getPrefix() { + return prefix; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ConfigPropertiesBuildItem that = (ConfigPropertiesBuildItem) o; + return configClass.equals(that.configClass) && + prefix.equals(that.prefix); + } + + @Override + public int hashCode() { + return Objects.hash(configClass, prefix); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java index 78b3631edc25f..5ef24b6cd8aaf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigMappingUtils.java @@ -1,12 +1,6 @@ package io.quarkus.deployment.configuration; -import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.MAPPING; -import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Type.PROPERTIES; -import static java.util.Collections.emptySet; -import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; -import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; -import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER; import java.util.ArrayList; import java.util.HashSet; @@ -18,7 +12,6 @@ import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; @@ -48,25 +41,6 @@ public static void generateConfigClasses( AnnotationTarget target = instance.target(); AnnotationValue annotationPrefix = instance.value("prefix"); - if (target.kind().equals(FIELD)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - configClasses.produce( - toConfigClassBuildItem(instance, toClass(target.asField().type().name()), - annotationPrefix.asString())); - continue; - } - } - - if (target.kind().equals(METHOD_PARAMETER)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { - ClassType classType = target.asMethodParameter().method().parameters() - .get(target.asMethodParameter().position()).asClassType(); - configClasses - .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); - continue; - } - } - if (!target.kind().equals(CLASS)) { continue; } @@ -105,7 +79,8 @@ public static void generateConfigClasses( } } - configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); + configClasses.produce( + new ConfigClassBuildItem(configClass, generatedClassesNames, prefix, getConfigClassType(instance))); } } @@ -118,22 +93,11 @@ private static Class toClass(DotName dotName) { } } - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - String prefix) { - return toConfigClassBuildItem(instance, configClass, emptySet(), prefix); - } - - private static ConfigClassBuildItem toConfigClassBuildItem( - AnnotationInstance instance, - Class configClass, - Set generatedClasses, - String prefix) { + private static ConfigClassBuildItem.Type getConfigClassType(AnnotationInstance instance) { if (instance.name().equals(CONFIG_MAPPING_NAME)) { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); + return ConfigClassBuildItem.Type.MAPPING; } else { - return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); + return ConfigClassBuildItem.Type.PROPERTIES; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index e21d9716ad89a..fadfcc3a81c76 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -33,7 +33,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.AdditionalBootstrapConfigSourceProviderBuildItem; import io.quarkus.deployment.builditem.AdditionalStaticInitConfigSourceProviderBuildItem; -import io.quarkus.deployment.builditem.ConfigClassBuildItem; +import io.quarkus.deployment.builditem.ConfigMappingBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -114,7 +114,7 @@ void generateConfigClass( List additionalBootstrapConfigSourceProviders, List staticInitConfigSourceProviders, List staticInitConfigSourceFactories, - List configClasses) + List configMappings) throws IOException { if (liveReloadBuildItem.isLiveReload()) { @@ -150,8 +150,8 @@ void generateConfigClass( .setRuntimeConfigSources(discoveredConfigSources) .setRuntimeConfigSourceProviders(discoveredConfigSourceProviders) .setRuntimeConfigSourceFactories(discoveredConfigSourceFactories) - .setStaticConfigMappings(staticSafeConfigMappings(configClasses)) - .setRuntimeConfigMappings(runtimeConfigMappings(configClasses)) + .setStaticConfigMappings(staticSafeConfigMappings(configMappings)) + .setRuntimeConfigMappings(runtimeConfigMappings(configMappings)) .build() .run(); } @@ -270,17 +270,15 @@ private static Set staticSafeServices(Set services) { return staticSafe; } - private static Set staticSafeConfigMappings(List configClasses) { - return configClasses.stream() - .filter(ConfigClassBuildItem::isMapping) - .filter(ConfigClassBuildItem::isStaticInitSafe) + private static Set staticSafeConfigMappings(List configMappings) { + return configMappings.stream() + .filter(ConfigMappingBuildItem::isStaticInitSafe) .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) .collect(toSet()); } - private static Set runtimeConfigMappings(List configClasses) { - return configClasses.stream() - .filter(ConfigClassBuildItem::isMapping) + private static Set runtimeConfigMappings(List configMappings) { + return configMappings.stream() .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) .collect(toSet()); } 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 ee60809ab577f..83349a013350f 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,16 +1,21 @@ package io.quarkus.arc.deployment; +import static io.quarkus.arc.processor.Annotations.getParameterAnnotations; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME; import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; +import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX; import static org.jboss.jandex.AnnotationInstance.create; import static org.jboss.jandex.AnnotationTarget.Kind.CLASS; +import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; +import static org.jboss.jandex.AnnotationTarget.Kind.METHOD; import static org.jboss.jandex.AnnotationValue.createStringValue; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -25,6 +30,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperties; 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; @@ -34,8 +40,11 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; +import io.quarkus.arc.Unremovable; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem; +import io.quarkus.arc.processor.Annotations; import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.BeanConfigurator; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.arc.runtime.ConfigBeanCreator; @@ -47,6 +56,8 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ConfigClassBuildItem; +import io.quarkus.deployment.builditem.ConfigMappingBuildItem; +import io.quarkus.deployment.builditem.ConfigPropertiesBuildItem; import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -71,7 +82,6 @@ public class ConfigBuildStep { private static final DotName LIST_NAME = DotName.createSimple(List.class.getName()); private static final DotName SUPPLIER_NAME = DotName.createSimple(Supplier.class.getName()); private static final DotName CONFIG_VALUE_NAME = DotName.createSimple(io.smallrye.config.ConfigValue.class.getName()); - public static final AnnotationInstance[] EMPTY_ANNOTATION_INSTANCES = {}; @BuildStep void additionalBeans(BuildProducer additionalBeans) { @@ -80,8 +90,7 @@ void additionalBeans(BuildProducer additionalBeans) { } @BuildStep - void analyzeConfigPropertyInjectionPoints(BeanDiscoveryFinishedBuildItem beanDiscovery, - BuildProducer configProperties, + void registerCustomConfigBeanTypes(BeanDiscoveryFinishedBuildItem beanDiscovery, BuildProducer reflectiveClass, BuildProducer syntheticBeans) { @@ -93,6 +102,42 @@ void analyzeConfigPropertyInjectionPoints(BeanDiscoveryFinishedBuildItem beanDis continue; } + AnnotationInstance configProperty = injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTY_NAME); + if (configProperty != null) { + // Register a custom bean for injection points that are not handled by ConfigProducer + Type injectedType = injectionPoint.getType(); + if (!isHandledByProducers(injectedType)) { + customBeanTypes.add(injectedType); + } + } + } + + for (Type type : customBeanTypes) { + if (type.kind() != Kind.ARRAY) { + // Implicit converters are most likely used + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, type.name().toString())); + } + DotName implClazz = type.kind() == Kind.ARRAY ? DotName.createSimple(ConfigBeanCreator.class.getName()) + : type.name(); + syntheticBeans.produce(SyntheticBeanBuildItem.configure(implClazz) + .creator(ConfigBeanCreator.class) + .providerType(type) + .types(type) + .addQualifier(MP_CONFIG_PROPERTY_NAME) + .param("requiredType", type.name().toString()).done()); + } + } + + @BuildStep + void validateConfigInjectionPoints(ValidationPhaseBuildItem validationPhase, + BuildProducer configProperties) { + + for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) { + if (injectionPoint.hasDefaultedQualifier()) { + // Defaulted qualifier means no @ConfigProperty + continue; + } + AnnotationInstance configProperty = injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTY_NAME); if (configProperty != null) { AnnotationValue nameValue = configProperty.value("name"); @@ -114,12 +159,7 @@ void analyzeConfigPropertyInjectionPoints(BeanDiscoveryFinishedBuildItem beanDis } } - // Register a custom bean for injection points that are not handled by ConfigProducer Type injectedType = injectionPoint.getType(); - if (!isHandledByProducers(injectedType)) { - customBeanTypes.add(injectedType); - } - if (DotNames.OPTIONAL.equals(injectedType.name()) || DotNames.OPTIONAL_INT.equals(injectedType.name()) || DotNames.OPTIONAL_LONG.equals(injectedType.name()) @@ -141,26 +181,11 @@ void analyzeConfigPropertyInjectionPoints(BeanDiscoveryFinishedBuildItem beanDis configProperties.produce(new ConfigPropertyBuildItem(propertyName, injectedType, propertyDefaultValue)); } } - - for (Type type : customBeanTypes) { - if (type.kind() != Kind.ARRAY) { - // Implicit converters are most likely used - reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, type.name().toString())); - } - DotName implClazz = type.kind() == Kind.ARRAY ? DotName.createSimple(ConfigBeanCreator.class.getName()) - : type.name(); - syntheticBeans.produce(SyntheticBeanBuildItem.configure(implClazz) - .creator(ConfigBeanCreator.class) - .providerType(type) - .types(type) - .addQualifier(MP_CONFIG_PROPERTY_NAME) - .param("requiredType", type.name().toString()).done()); - } } @BuildStep @Record(RUNTIME_INIT) - void validateConfigProperties(ConfigRecorder recorder, List configProperties, + void validateConfigValues(ConfigRecorder recorder, List configProperties, BeanContainerBuildItem beanContainer, BuildProducer reflectiveClass) { // IMPL NOTE: we do depend on BeanContainerBuildItem to make sure that the BeanDeploymentValidator finished its processing @@ -250,40 +275,98 @@ void generateConfigClasses( } @BuildStep - void beanConfigClasses( + void registerConfigMappingsBean( + BeanRegistrationPhaseBuildItem beanRegistration, List configClasses, - BeanRegistrationPhaseBuildItem beanRegistrationPhase, CombinedIndexBuildItem combinedIndex, - BuildProducer beanConfigurationRegistry) { + BuildProducer beanConfigurator) { + if (configClasses.isEmpty()) { + return; + } + + Map configMappingNames = new HashMap<>(); for (ConfigClassBuildItem configClass : configClasses) { - if (configClass.getGeneratedClasses().isEmpty()) { - continue; + if (configClass.isMapping()) { + configMappingNames.put(configClass.getName(), configClass); + } + } + + Set configMappings = new HashSet<>(); + for (InjectionPointInfo injectionPoint : beanRegistration.getInjectionPoints()) { + Type type = injectionPoint.getType(); + ConfigClassBuildItem configClass = configMappingNames.get(type.name()); + if (configClass != null) { + configMappings.add(configClass); + } + } + + for (ConfigClassBuildItem configClass : configMappings) { + BeanConfigurator bean = beanRegistration.getContext() + .configure(configClass.getConfigClass()) + .types(collectTypes(combinedIndex, configClass.getConfigClass())) + .creator(ConfigMappingCreator.class) + .param("type", configClass.getConfigClass()) + .param("prefix", configClass.getPrefix()); + + if (configClass.getConfigClass().isAnnotationPresent(Unremovable.class)) { + bean.unremovable(); } - List qualifiers = new ArrayList<>(); + beanConfigurator.produce(new BeanConfiguratorBuildItem(bean)); + } + } + + @BuildStep + void registerConfigPropertiesBean( + BeanRegistrationPhaseBuildItem beanRegistration, + List configClasses, + CombinedIndexBuildItem combinedIndex, + BuildProducer beanConfigurator) { + + if (configClasses.isEmpty()) { + return; + } + + Map configPropertiesNames = new HashMap<>(); + for (ConfigClassBuildItem configClass : configClasses) { if (configClass.isProperties()) { - qualifiers.add( - create(MP_CONFIG_PROPERTIES_NAME, null, - new AnnotationValue[] { createStringValue("prefix", configClass.getPrefix()) })); + configPropertiesNames.put(configClass.getName(), configClass); } + } - collectTypes(combinedIndex, configClass); + Set configProperties = new HashSet<>(); + for (InjectionPointInfo injectionPoint : beanRegistration.getInjectionPoints()) { + AnnotationInstance instance = injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTIES_NAME); + if (instance == null) { + continue; + } - beanConfigurationRegistry.produce(new BeanConfiguratorBuildItem( - beanRegistrationPhase.getContext() + Type type = injectionPoint.getType(); + ConfigClassBuildItem configClass = configPropertiesNames.get(type.name()); + if (configClass != null) { + configProperties.add(configClass); + } + } + + for (ConfigClassBuildItem configClass : configProperties) { + beanConfigurator.produce(new BeanConfiguratorBuildItem( + beanRegistration.getContext() .configure(configClass.getConfigClass()) - .types(collectTypes(combinedIndex, configClass)) - .qualifiers(qualifiers.toArray(EMPTY_ANNOTATION_INSTANCES)) + .types(collectTypes(combinedIndex, configClass.getConfigClass())) + .addQualifier(create(MP_CONFIG_PROPERTIES_NAME, null, + new AnnotationValue[] { + createStringValue("prefix", configClass.getPrefix()) + })) .creator(ConfigMappingCreator.class) .param("type", configClass.getConfigClass()) .param("prefix", configClass.getPrefix()))); } } - private Type[] collectTypes(CombinedIndexBuildItem combinedIndex, ConfigClassBuildItem configClass) { + private Type[] collectTypes(CombinedIndexBuildItem combinedIndex, Class configClass) { IndexView index = combinedIndex.getIndex(); - DotName configIfaceName = DotName.createSimple(configClass.getConfigClass().getName()); + DotName configIfaceName = DotName.createSimple(configClass.getName()); ClassInfo configIfaceInfo = index.getClassByName(configIfaceName); if ((configIfaceInfo == null) || configIfaceInfo.interfaceNames().isEmpty()) { return new Type[] { Type.create(configIfaceName, Kind.CLASS) }; @@ -315,12 +398,116 @@ private static void collectInterfacesRec(ClassInfo current, IndexView index, Set } } + @BuildStep + void validateConfigMappingsInjectionPoints( + ArcConfig arcConfig, + ValidationPhaseBuildItem validationPhase, + List configClasses, + BuildProducer configMappings) { + + if (configClasses.isEmpty()) { + return; + } + + Map configMappingNames = new HashMap<>(); + for (ConfigClassBuildItem configClass : configClasses) { + if (configClass.isMapping()) { + configMappingNames.put(configClass.getName(), configClass); + } + } + + Set toRegister = new HashSet<>(); + for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) { + Type type = injectionPoint.getType(); + ConfigClassBuildItem configClass = configMappingNames.get(type.name()); + if (configClass != null) { + AnnotationTarget target = injectionPoint.getTarget(); + AnnotationInstance mapping = null; + if (target.kind().equals(FIELD)) { + mapping = target.asField().annotation(CONFIG_MAPPING_NAME); + } else if (target.kind().equals(METHOD)) { + List parameters = target.asMethod().parameters(); + for (int i = 0; i < parameters.size(); i++) { + Type parameter = parameters.get(i); + if (parameter.name().equals(type.name())) { + Set parameterAnnotations = getParameterAnnotations( + validationPhase.getBeanProcessor().getBeanDeployment(), target.asMethod(), i); + mapping = Annotations.find(parameterAnnotations, CONFIG_MAPPING_NAME); + } + } + } + + AnnotationValue annotationPrefix = null; + if (mapping != null) { + annotationPrefix = mapping.value("prefix"); + } + + String prefix = annotationPrefix != null ? annotationPrefix.asString() : configClass.getPrefix(); + toRegister.add(new ConfigMappingBuildItem(configClass.getConfigClass(), prefix)); + } + } + + for (ConfigClassBuildItem configClass : configMappingNames.values()) { + // We don't look in the beans here, because SR Config has an API that can retrieve the mapping without CDI + if (!arcConfig.shouldEnableBeanRemoval() || configClass.getConfigClass().isAnnotationPresent(Unremovable.class)) { + toRegister.add(new ConfigMappingBuildItem(configClass.getConfigClass(), configClass.getPrefix())); + } + } + + toRegister.forEach(configMappings::produce); + } + + @BuildStep + void validateConfigPropertiesInjectionPoints( + ArcConfig arcConfig, + ValidationPhaseBuildItem validationPhase, + List configClasses, + BuildProducer configProperties) { + + if (configClasses.isEmpty()) { + return; + } + + Map configPropertiesNames = new HashMap<>(); + for (ConfigClassBuildItem configClass : configClasses) { + if (configClass.isProperties()) { + configPropertiesNames.put(configClass.getName(), configClass); + } + } + + Set toRegister = new HashSet<>(); + for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) { + AnnotationInstance properties = injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTIES_NAME); + if (properties != null) { + Type type = injectionPoint.getType(); + ConfigClassBuildItem configClass = configPropertiesNames.get(type.name()); + if (configClass != null) { + AnnotationValue annotationPrefix = properties.value("prefix"); + String prefix = annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX) + ? annotationPrefix.asString() + : configClass.getPrefix(); + toRegister.add(new ConfigPropertiesBuildItem(configClass.getConfigClass(), prefix)); + } + } + } + + for (ConfigClassBuildItem configClass : configPropertiesNames.values()) { + if (!arcConfig.shouldEnableBeanRemoval() + || !validationPhase.getContext().beans().withBeanType(configClass.getConfigClass()).isEmpty()) { + toRegister.add(new ConfigPropertiesBuildItem(configClass.getConfigClass(), configClass.getPrefix())); + } + } + + toRegister.forEach(configProperties::produce); + } + @BuildStep @Record(RUNTIME_INIT) void registerConfigClasses( RecorderContext context, ConfigRecorder recorder, - List configClasses) throws Exception { + List configMappings, + List configProperties) throws Exception { context.registerNonDefaultConstructor( ConfigClassWithPrefix.class.getDeclaredConstructor(Class.class, String.class), @@ -328,10 +515,8 @@ void registerConfigClasses( .collect(toList())); recorder.registerConfigProperties( - configClasses.stream() - .filter(ConfigClassBuildItem::isProperties) - .map(configProperties -> configClassWithPrefix(configProperties.getConfigClass(), - configProperties.getPrefix())) + configProperties.stream() + .map(p -> configClassWithPrefix(p.getConfigClass(), p.getPrefix())) .collect(toSet())); } diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionValidationTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionValidationTest.java index 86582d4112cf1..fad6e0815387f 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionValidationTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionValidationTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.Unremovable; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; @@ -27,13 +28,11 @@ public void testValidationFailed() { Assertions.fail(); } + @Unremovable @ApplicationScoped static class Configured { - @Inject @ConfigProperty(name = "unconfigured") String foo; - } - } diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/NullConverterTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/NullConverterTest.java index afd8ecf2f1128..bc04f81556051 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/NullConverterTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/NullConverterTest.java @@ -9,9 +9,11 @@ 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.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.Unremovable; import io.quarkus.test.QuarkusUnitTest; public class NullConverterTest { @@ -25,9 +27,10 @@ public class NullConverterTest { @Test void nullProperty() { - + Assertions.fail(); } + @Unremovable @ApplicationScoped static class NullConverterBean { @Inject diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigMappingTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigMappingTest.java new file mode 100644 index 0000000000000..88cb11b00b0f2 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigMappingTest.java @@ -0,0 +1,40 @@ +package io.quarkus.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; + +public class RemovedConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(RemovedConfigBean.class) + .addClass(RemovedConfigMapping.class)); + + @Test + void skipConfigValidation() { + assertEquals(0, Arc.container().beanManager().getBeans(RemovedConfigBean.class).size()); + assertEquals(0, Arc.container().beanManager().getBeans(RemovedConfigMapping.class).size()); + } + + @ApplicationScoped + public static class RemovedConfigBean { + @Inject + RemovedConfigMapping mapping; + } + + @ConfigMapping + public interface RemovedConfigMapping { + String prop(); + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertiesTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertiesTest.java new file mode 100644 index 0000000000000..60125a41edb54 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertiesTest.java @@ -0,0 +1,47 @@ +package io.quarkus.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperties; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; + +public class RemovedConfigPropertiesTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(RemovedConfigBean.class) + .addClass(RemovedConfigProperties.class)); + + @Test + void skipConfigValidation() { + assertEquals(0, Arc.container().beanManager().getBeans(RemovedConfigBean.class).size()); + assertEquals(0, Arc.container().beanManager().getBeans(RemovedConfigProperties.class).size()); + } + + @ApplicationScoped + public static class RemovedConfigBean { + @Inject + @ConfigProperties + RemovedConfigProperties properties; + } + + @ConfigProperties + public static class RemovedConfigProperties { + @ConfigProperty(name = "my.prop") + String prop; + + public String getProp() { + return prop; + } + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertyTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertyTest.java new file mode 100644 index 0000000000000..83b4843a3deba --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/RemovedConfigPropertyTest.java @@ -0,0 +1,31 @@ +package io.quarkus.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; + +public class RemovedConfigPropertyTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(RemovedConfigBean.class)); + + @Test + void skipConfigValidation() { + assertEquals(0, Arc.container().beanManager().getBeans(RemovedConfigBean.class).size()); + } + + @ApplicationScoped + public static class RemovedConfigBean { + @ConfigProperty(name = "my.prop") + String prop; + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/UnremovedConfigMappingTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/UnremovedConfigMappingTest.java new file mode 100644 index 0000000000000..b59915b5f8bd6 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/UnremovedConfigMappingTest.java @@ -0,0 +1,39 @@ +package io.quarkus.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Unremovable; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.WithDefault; + +public class UnremovedConfigMappingTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(UnremovedConfigMapping.class)); + + @Inject + SmallRyeConfig config; + + @Test + void unremoved() { + UnremovedConfigMapping mapping = config.getConfigMapping(UnremovedConfigMapping.class); + assertEquals("1234", mapping.prop()); + } + + @Unremovable + @ConfigMapping(prefix = "mapping") + public interface UnremovedConfigMapping { + @WithDefault("1234") + String prop(); + } +} diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingValidatorTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingValidatorTest.java index 39a7c5918b8c9..3bafe81f4258c 100644 --- a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingValidatorTest.java +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingValidatorTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.Unremovable; import io.quarkus.test.QuarkusUnitTest; import io.smallrye.config.ConfigMapping; import io.smallrye.config.ConfigValidationException; @@ -33,6 +34,7 @@ void validator() { "validator.server.host must be less than or equal to 3"); } + @Unremovable @ConfigMapping(prefix = "validator.server") public interface Server { @Max(3) diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Cloud.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Cloud.java index bacd07dad7502..cffd9b6d8cd46 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Cloud.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Cloud.java @@ -1,8 +1,10 @@ package io.quarkus.it.smallrye.config; +import io.quarkus.arc.Unremovable; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithParentName; +@Unremovable @ConfigMapping(prefix = "cloud") public interface Cloud { @WithParentName