diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5cacf24150fd86..c884d18dfe2a96 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -42,7 +42,7 @@ 2.0 1.2 1.6.0 - 2.2.0 + 2.3.0 3.0.2 3.0.1 2.1.4 @@ -3179,6 +3179,11 @@ smallrye-config-common ${smallrye-config.version} + + io.smallrye.config + smallrye-config-validator + ${smallrye-config.version} + io.smallrye.config smallrye-config-source-yaml diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java index 0699b8d2d6d956..1019d415ea7564 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigBuildSteps.java @@ -30,6 +30,7 @@ import io.smallrye.config.ConfigSourceFactory; import io.smallrye.config.ConfigSourceInterceptor; import io.smallrye.config.ConfigSourceInterceptorFactory; +import io.smallrye.config.ConfigValidator; import io.smallrye.config.SmallRyeConfigProviderResolver; class ConfigBuildSteps { @@ -88,7 +89,8 @@ void nativeServiceProviders( Converter.class, ConfigSourceInterceptor.class, ConfigSourceInterceptorFactory.class, - ConfigSourceFactory.class)) { + ConfigSourceFactory.class, + ConfigValidator.class)) { final String serviceName = serviceClass.getName(); final Set names = ServiceUtil.classNamesNamedIn(classLoader, SERVICES_PREFIX + serviceName); final List list = names.stream() diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ApplicationPropertiesConfigSourceLoader.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ApplicationPropertiesConfigSourceLoader.java index f5ac2de74bf0a0..2aba6e8589a61c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ApplicationPropertiesConfigSourceLoader.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ApplicationPropertiesConfigSourceLoader.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.net.URI; import java.net.URL; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -24,35 +25,27 @@ protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws } public static class InClassPath extends ApplicationPropertiesConfigSourceLoader implements ConfigSourceProvider { - @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { - return super.loadConfigSource(url, 250); - } - @Override public List getConfigSources(final ClassLoader classLoader) { - return loadConfigSources("application.properties", classLoader); + return loadConfigSources("application.properties", 250, classLoader); } @Override - protected List tryFileSystem(final URI uri) { + protected List tryFileSystem(final URI uri, final int ordinal) { return new ArrayList<>(); } } public static class InFileSystem extends ApplicationPropertiesConfigSourceLoader implements ConfigSourceProvider { - @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { - return super.loadConfigSource(url, 260); - } - @Override public List getConfigSources(final ClassLoader classLoader) { - return loadConfigSources("config/application.properties", classLoader); + return loadConfigSources( + Paths.get(System.getProperty("user.dir"), "config", "application.properties").toUri().toString(), 260, + classLoader); } @Override - protected List tryClassPath(final URI uri, final ClassLoader classLoader) { + protected List tryClassPath(final URI uri, final int ordinal, final ClassLoader classLoader) { return new ArrayList<>(); } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index 4c070f03f55b3a..1ea1cb9fc8b766 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -1,10 +1,10 @@ package io.quarkus.runtime.configuration; -import static io.smallrye.config.AbstractLocationConfigSourceFactory.SMALLRYE_LOCATIONS; import static io.smallrye.config.DotEnvConfigSourceProvider.dotEnvSources; -import static io.smallrye.config.ProfileConfigSourceInterceptor.SMALLRYE_PROFILE; -import static io.smallrye.config.ProfileConfigSourceInterceptor.SMALLRYE_PROFILE_PARENT; import static io.smallrye.config.PropertiesConfigSourceProvider.classPathSources; +import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_LOCATIONS; +import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; +import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE_PARENT; import static io.smallrye.config.SmallRyeConfigBuilder.META_INF_MICROPROFILE_CONFIG_PROPERTIES; import java.io.IOException; @@ -116,11 +116,11 @@ public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final b public static SmallRyeConfigBuilder emptyConfigBuilder() { final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); - builder.withDefaultValue(SMALLRYE_PROFILE, ProfileManager.getActiveProfile()); + builder.withDefaultValue(SMALLRYE_CONFIG_PROFILE, ProfileManager.getActiveProfile()); final Map relocations = new HashMap<>(); - relocations.put(SMALLRYE_LOCATIONS, "quarkus.config.locations"); - relocations.put(SMALLRYE_PROFILE_PARENT, "quarkus.config.profile.parent"); + relocations.put(SMALLRYE_CONFIG_LOCATIONS, "quarkus.config.locations"); + relocations.put(SMALLRYE_CONFIG_PROFILE_PARENT, "quarkus.config.profile.parent"); // Override the priority, because of the ProfileConfigSourceInterceptor and profile.parent. builder.withInterceptorFactories(new ConfigSourceInterceptorFactory() { @Override @@ -137,6 +137,7 @@ public OptionalInt getPriority() { builder.addDefaultInterceptors(); builder.addDiscoveredInterceptors(); builder.addDiscoveredConverters(); + builder.addDiscoveredValidator(); return builder; } 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 2dd8af0b069640..e9430dcd366522 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,9 +1,13 @@ package io.quarkus.arc.deployment; +import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.MAPPING; +import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.PROPERTIES; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; -import static io.smallrye.config.ConfigMappings.ConfigMappingWithPrefix.configMappingWithPrefix; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; +import static java.util.Collections.emptySet; 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; @@ -57,14 +61,13 @@ import io.smallrye.config.ConfigMapping; import io.smallrye.config.ConfigMappingLoader; import io.smallrye.config.ConfigMappingMetadata; -import io.smallrye.config.ConfigMappings.ConfigMappingWithPrefix; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.inject.ConfigProducer; /** * MicroProfile Config related build steps. */ public class ConfigBuildStep { - private static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName()); private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName()); @@ -226,13 +229,11 @@ public void transform(TransformationContext context) { } @BuildStep - void generateConfigMappings( + void generateConfigClasses( CombinedIndexBuildItem combinedIndex, - BeanRegistrationPhaseBuildItem beanRegistrationPhase, BuildProducer generatedClasses, BuildProducer reflectiveClasses, - BuildProducer configMappings, - BuildProducer beanConfigurationRegistry) { + BuildProducer configClasses) { List mappingAnnotations = new ArrayList<>(); mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(CONFIG_MAPPING_NAME)); @@ -243,18 +244,20 @@ void generateConfigMappings( AnnotationValue annotationPrefix = instance.value("prefix"); if (target.kind().equals(FIELD)) { - if (annotationPrefix != null && !annotationPrefix.asString().equals(ConfigProperties.UNCONFIGURED_PREFIX)) { - configMappings.produce( - new ConfigMappingBuildItem(toClass(target.asField().type().name()), annotationPrefix.asString())); + 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(ConfigProperties.UNCONFIGURED_PREFIX)) { + if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) { ClassType classType = target.asMethodParameter().method().parameters() .get(target.asMethodParameter().position()).asClassType(); - configMappings.produce(new ConfigMappingBuildItem(toClass(classType.name()), annotationPrefix.asString())); + configClasses + .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString())); continue; } } @@ -263,11 +266,12 @@ void generateConfigMappings( continue; } - Class type = toClass(target.asClass().name()); + Class configClass = toClass(target.asClass().name()); String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse(""); - List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(type); - List mappingsInfo = new ArrayList<>(); + List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass); + Set generatedClassesNames = new HashSet<>(); + Set mappingsInfo = new HashSet<>(); configMappingsMetadata.forEach(mappingMetadata -> { generatedClasses.produce( new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes())); @@ -276,6 +280,8 @@ void generateConfigMappings( reflectiveClasses .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build()); + generatedClassesNames.add(mappingMetadata.getClassName()); + ClassInfo mappingInfo = combinedIndex.getIndex() .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName())); if (mappingInfo != null) { @@ -296,40 +302,63 @@ void generateConfigMappings( } } - configMappings.produce(new ConfigMappingBuildItem(type, prefix)); + configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix)); + } + } + + @BuildStep + void beanConfigClasses( + List configClasses, + BeanRegistrationPhaseBuildItem beanRegistrationPhase, + BuildProducer beanConfigurationRegistry) { + + for (ConfigClassBuildItem configClass : configClasses) { + if (configClass.getGeneratedClasses().isEmpty()) { + continue; + } List qualifiers = new ArrayList<>(); - if (instance.name().equals(MP_CONFIG_PROPERTIES_NAME)) { + if (configClass.isProperties()) { qualifiers.add( - create(MP_CONFIG_PROPERTIES_NAME, null, new AnnotationValue[] { createStringValue("prefix", prefix) })); + create(MP_CONFIG_PROPERTIES_NAME, null, + new AnnotationValue[] { createStringValue("prefix", configClass.getPrefix()) })); } beanConfigurationRegistry.produce(new BeanConfiguratorBuildItem( beanRegistrationPhase.getContext() - .configure(type) - .types(type) + .configure(configClass.getConfigClass()) + .types(configClass.getConfigClass()) .qualifiers(qualifiers.toArray(new AnnotationInstance[] {})) .creator(ConfigMappingCreator.class) - .param("type", type) - .param("prefix", prefix))); + .param("type", configClass.getConfigClass()) + .param("prefix", configClass.getPrefix()))); } } @BuildStep @Record(RUNTIME_INIT) - void registerConfigMappings( + void registerConfigClasses( RecorderContext context, ConfigRecorder recorder, - List configMappings) throws Exception { + List configClasses) throws Exception { context.registerNonDefaultConstructor( - ConfigMappingWithPrefix.class.getDeclaredConstructor(Class.class, String.class), - configMappingWithPrefix -> Stream.of(configMappingWithPrefix.getKlass(), configMappingWithPrefix.getPrefix()) + ConfigClassWithPrefix.class.getDeclaredConstructor(Class.class, String.class), + configClassWithPrefix -> Stream.of(configClassWithPrefix.getKlass(), configClassWithPrefix.getPrefix()) .collect(toList())); - recorder.registerConfigMappings(configMappings.stream() - .map(configMapping -> configMappingWithPrefix(configMapping.getInterfaceType(), configMapping.getPrefix())) - .collect(toSet())); + recorder.registerConfigMappings( + configClasses.stream() + .filter(ConfigClassBuildItem::isMapping) + .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix())) + .collect(toSet())); + + recorder.registerConfigProperties( + configClasses.stream() + .filter(ConfigClassBuildItem::isProperties) + .map(configProperties -> configClassWithPrefix(configProperties.getConfigClass(), + configProperties.getPrefix())) + .collect(toSet())); } private static Class toClass(DotName dotName) { @@ -341,6 +370,27 @@ 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) { + if (instance.name().equals(CONFIG_MAPPING_NAME)) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING); + } else if (instance.name().equals(MP_CONFIG_PROPERTIES_NAME)) { + return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES); + } else { + throw new IllegalArgumentException(); + } + } + 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/ConfigClassBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java new file mode 100644 index 00000000000000..1d011ddbe5824d --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.deployment; + +import java.util.Set; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class ConfigClassBuildItem extends MultiBuildItem { + private final Class configClass; + private final Set generatedClasses; + private final String prefix; + private final Type type; + + public ConfigClassBuildItem( + final Class configClass, + final Set generatedClasses, + final String prefix, + final Type type) { + + this.configClass = configClass; + this.generatedClasses = generatedClasses; + this.prefix = prefix; + this.type = type; + } + + public Class getConfigClass() { + return configClass; + } + + public Set getGeneratedClasses() { + return generatedClasses; + } + + public String getPrefix() { + return prefix; + } + + public Type getType() { + return type; + } + + public boolean isMapping() { + return Type.MAPPING.equals(type); + } + + public boolean isProperties() { + return Type.PROPERTIES.equals(type); + } + + public enum Type { + MAPPING, + PROPERTIES; + } +} 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 deleted file mode 100644 index b97fa357e77e9c..00000000000000 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigMappingBuildItem.java +++ /dev/null @@ -1,21 +0,0 @@ -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/runtime/src/main/java/io/quarkus/arc/config/ConfigIgnore.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigIgnore.java index b73a65fdf54240..ff11ee45db0491 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigIgnore.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigIgnore.java @@ -9,7 +9,11 @@ /** * When applied to a field of class annotated with {@link ConfigProperties}, that field will be ignored * for the purposes of configuration + * + * @deprecated Please, use {@link io.smallrye.config.ConfigMapping} instead. This will be removed in a future Quarkus + * version. */ +@Deprecated @Target(FIELD) @Retention(RUNTIME) public @interface ConfigIgnore { diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigPrefix.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigPrefix.java index 6a0ccf5d7510a4..0a861dcfedb122 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigPrefix.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigPrefix.java @@ -11,6 +11,9 @@ /** * Uses in order to set the prefix for a configuration object at the injection point + * + * @deprecated Please, use {@link io.smallrye.config.ConfigMapping} instead. This will be removed in a future Quarkus + * version. */ @Qualifier @Target({ FIELD, PARAMETER }) diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigProperties.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigProperties.java index 6df0fcc1e725af..f7fb0886c80134 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigProperties.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/config/ConfigProperties.java @@ -10,7 +10,11 @@ /** * Allow configuration properties with a common prefix to be grouped into a single class + * + * @deprecated Please, use {@link io.smallrye.config.ConfigMapping} instead. This will be removed in a future Quarkus + * version. */ +@Deprecated @Target({ TYPE }) @Retention(RUNTIME) public @interface ConfigProperties { 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 68350c3b60d3f4..c232571d6c5130 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,7 @@ import io.quarkus.runtime.annotations.Recorder; import io.smallrye.config.ConfigMappings; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.ConfigValidationException; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.inject.ConfigProducerUtil; @@ -43,10 +44,19 @@ public void validateConfigProperties(Set properties) { } } - public void registerConfigMappings(final Set configMappingsWithPrefix) { + public void registerConfigMappings(final Set configClasses) { try { SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); - ConfigMappings.registerConfigMappings(config, configMappingsWithPrefix); + ConfigMappings.registerConfigMappings(config, configClasses); + } catch (ConfigValidationException e) { + throw new DeploymentException(e.getMessage(), e); + } + } + + public void registerConfigProperties(final Set configClasses) { + try { + SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + ConfigMappings.registerConfigProperties(config, configClasses); } catch (ConfigValidationException e) { throw new DeploymentException(e.getMessage(), e); } diff --git a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlConfigSourceLoader.java b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlConfigSourceLoader.java index cfb664721c4b60..2f063642c30b91 100644 --- a/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlConfigSourceLoader.java +++ b/extensions/config-yaml/runtime/src/main/java/io/quarkus/config/yaml/runtime/ApplicationYamlConfigSourceLoader.java @@ -19,7 +19,6 @@ protected String[] getFileExtensions() { "yaml", "yml" }; - } @Override @@ -28,41 +27,31 @@ protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws } public static class InClassPath extends ApplicationYamlConfigSourceLoader implements ConfigSourceProvider { - @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { - return super.loadConfigSource(url, 255); - } - @Override public List getConfigSources(final ClassLoader classLoader) { List configSources = new ArrayList<>(); - configSources.addAll(loadConfigSources("application.yaml", classLoader)); - configSources.addAll(loadConfigSources("application.yml", classLoader)); + configSources.addAll(loadConfigSources("application.yaml", 255, classLoader)); + configSources.addAll(loadConfigSources("application.yml", 255, classLoader)); return configSources; } @Override - protected List tryFileSystem(final URI uri) { + protected List tryFileSystem(final URI uri, final int ordinal) { return new ArrayList<>(); } } public static class InFileSystem extends ApplicationYamlConfigSourceLoader implements ConfigSourceProvider { - @Override - protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { - return super.loadConfigSource(url, 265); - } - @Override public List getConfigSources(final ClassLoader classLoader) { List configSources = new ArrayList<>(); - configSources.addAll(loadConfigSources("config/application.yaml", classLoader)); - configSources.addAll(loadConfigSources("config/application.yml", classLoader)); + configSources.addAll(loadConfigSources("config/application.yaml", 265, classLoader)); + configSources.addAll(loadConfigSources("config/application.yml", 265, classLoader)); return configSources; } @Override - protected List tryClassPath(final URI uri, final ClassLoader classLoader) { + protected List tryClassPath(final URI uri, final int ordinal, final ClassLoader classLoader) { return new ArrayList<>(); } } diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index 2eb14a165ce059..8aa4bdcfefe1cf 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -48,6 +48,7 @@ import io.quarkus.arc.deployment.AutoAddScopeBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +import io.quarkus.arc.deployment.ConfigClassBuildItem; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.BeanInfo; @@ -182,6 +183,7 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC BuildProducer beanContainerListener, BuildProducer beanValidationAnnotations, ShutdownContextBuildItem shutdownContext, + List configClasses, List additionalJaxRsResourceMethodAnnotations, Capabilities capabilities, LocalesBuildTimeConfig localesBuildTimeConfig, @@ -274,6 +276,12 @@ public void build(HibernateValidatorRecorder recorder, RecorderContext recorderC } } + for (ConfigClassBuildItem configClass : configClasses) { + for (String generatedClass : configClass.getGeneratedClasses()) { + classNamesToBeValidated.add(DotName.createSimple(generatedClass)); + } + } + // JAX-RS methods are handled differently by the transformer so those need to be gathered here. // Note: The focus only on methods is basically an incomplete solution, since there could also be // class-level JAX-RS annotations but currently the transformer only looks at methods. 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 new file mode 100644 index 00000000000000..ec8b70552d8431 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/config/ConfigMappingValidatorTest.java @@ -0,0 +1,41 @@ +package io.quarkus.hibernate.validator.test.config; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import javax.inject.Inject; +import javax.validation.constraints.Max; + +import org.eclipse.microprofile.config.Config; +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.ConfigValidationException; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigMappingValidatorTest { + @RegisterExtension + static final QuarkusUnitTest UNIT_TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("server.host=localhost\n"), "application.properties")); + + @Inject + Config config; + + @Test + void validator() { + assertThrows(ConfigValidationException.class, + () -> config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class), + "server.host must be less than or equal to 3"); + } + + @ConfigMapping(prefix = "server") + public interface Server { + @Max(3) + String host(); + } +} diff --git a/extensions/hibernate-validator/runtime/pom.xml b/extensions/hibernate-validator/runtime/pom.xml index faa299a71afca9..a07425441a1e49 100644 --- a/extensions/hibernate-validator/runtime/pom.xml +++ b/extensions/hibernate-validator/runtime/pom.xml @@ -30,6 +30,10 @@ org.glassfish jakarta.el + + io.smallrye.config + smallrye-config-validator + org.jboss.resteasy resteasy-core diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java new file mode 100644 index 00000000000000..13aafb261b8c6c --- /dev/null +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateBeanValidationConfigValidator.java @@ -0,0 +1,12 @@ +package io.quarkus.hibernate.validator.runtime; + +import javax.validation.Validator; + +import io.smallrye.config.validator.BeanValidationConfigValidator; + +public class HibernateBeanValidationConfigValidator implements BeanValidationConfigValidator { + @Override + public Validator getValidator() { + return ValidatorHolder.getValidator(); + } +} diff --git a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator new file mode 100644 index 00000000000000..cf5de21457647e --- /dev/null +++ b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigValidator @@ -0,0 +1,2 @@ +#io.smallrye.config.validator.BeanValidationConfigValidatorImpl +io.quarkus.hibernate.validator.runtime.HibernateBeanValidationConfigValidator diff --git a/integration-tests/smallrye-config/pom.xml b/integration-tests/smallrye-config/pom.xml index 67d90fc4324aff..cf5a8c414e6895 100644 --- a/integration-tests/smallrye-config/pom.xml +++ b/integration-tests/smallrye-config/pom.xml @@ -15,6 +15,10 @@ Quarkus - Integration Tests - SmallRye Config + + io.quarkus + quarkus-hibernate-validator + io.quarkus quarkus-config-yaml @@ -47,6 +51,19 @@ + + io.quarkus + quarkus-hibernate-validator-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-config-yaml-deployment 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 new file mode 100644 index 00000000000000..bacd07dad75027 --- /dev/null +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Cloud.java @@ -0,0 +1,10 @@ +package io.quarkus.it.smallrye.config; + +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithParentName; + +@ConfigMapping(prefix = "cloud") +public interface Cloud { + @WithParentName + Server server(); +} diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Server.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Server.java index b2a8e55f2f4322..a266a558e11e41 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Server.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/Server.java @@ -5,6 +5,11 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Size; import com.fasterxml.jackson.annotation.JsonProperty; @@ -22,6 +27,7 @@ public interface Server { String host(); @JsonProperty + @Min(8000) int port(); @JsonProperty @@ -48,6 +54,9 @@ public interface Server { @JsonProperty Log log(); + @JsonProperty + Info info(); + interface Form { @JsonProperty String loginPage(); @@ -77,6 +86,10 @@ interface Ssl { interface Proxy { @JsonProperty boolean enable(); + + @JsonProperty + @Max(10) + int timeout(); } interface Log { @@ -99,6 +112,10 @@ interface Log { @JsonProperty Period period(); + @JsonProperty + @Max(15) + int days(); + @RegisterForReflection enum Pattern { COMMON, @@ -113,14 +130,37 @@ interface Cors { List origins(); @JsonProperty - List methods(); + List<@Size(min = 2) String> methods(); interface Origin { @JsonProperty + @Size(min = 5) String host(); @JsonProperty + @Min(8000) int port(); } } + + interface Info { + @JsonProperty + Optional<@Size(max = 3) String> name(); + + @JsonProperty + @Max(3) + OptionalInt code(); + + @JsonProperty + Optional> alias(); + + @JsonProperty + Map admins(); + + interface Admin { + @JsonProperty + @Size(max = 3) + String username(); + } + } } diff --git a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java index 3bd44522eae7d7..5be4bf80738e02 100644 --- a/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java +++ b/integration-tests/smallrye-config/src/main/java/io/quarkus/it/smallrye/config/ServerResource.java @@ -1,14 +1,23 @@ package io.quarkus.it.smallrye.config; import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Response; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.inject.ConfigProperties; +import io.smallrye.config.ConfigValidationException; +import io.smallrye.config.SmallRyeConfig; + @Path("/server") public class ServerResource { + @Inject + Config config; @Inject Server server; @Inject @@ -25,4 +34,24 @@ public Response getServer() { public Response getServerProperties() { return Response.ok(serverProperties).build(); } + + @GET + @Path("/validator/cloud") + public Response validator() { + try { + config.unwrap(SmallRyeConfig.class).getConfigMapping(Cloud.class, "cloud"); + } catch (ConfigValidationException e) { + JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder(); + for (int i = 0; i < e.getProblemCount(); i++) { + jsonArrayBuilder.add(e.getProblem(i).getMessage()); + } + JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder(); + jsonObjectBuilder.add("errors", jsonArrayBuilder); + return Response.ok().entity(jsonObjectBuilder.build().toString()).build(); + } catch (Exception e) { + e.printStackTrace(); + } + + return Response.serverError().build(); + } } diff --git a/integration-tests/smallrye-config/src/main/resources/application.yaml b/integration-tests/smallrye-config/src/main/resources/application.yaml index ee5a005d2cfeec..65a19799838e2b 100644 --- a/integration-tests/smallrye-config/src/main/resources/application.yaml +++ b/integration-tests/smallrye-config/src/main/resources/application.yaml @@ -25,6 +25,49 @@ server: log: period: P1D + days: 10 + +cloud: + host: localhost + port: 8080 + timeout: 60s + io-threads: 200 + + form: + login-page: login.html + error-page: error.html + landing-page: index.html + + ssl: + port: 8443 + certificate: certificate + + cors: + origins: + - host: some-server + port: 9000 + - host: localhost + port: 1 + methods: + - GET + - POST + + proxy: + enable: true + timeout: 20 + + log: + period: P1D + days: 20 + + info: + name: Bond + code: 007 + alias: + - James + admins: + root: + username: root profile: main: diff --git a/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/ServerResourceTest.java b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/ServerResourceTest.java index b2ea4c38ac4a5b..f8c230723c40f1 100644 --- a/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/ServerResourceTest.java +++ b/integration-tests/smallrye-config/src/test/java/io/quarkus/it/smallrye/config/ServerResourceTest.java @@ -1,15 +1,33 @@ package io.quarkus.it.smallrye.config; import static io.restassured.RestAssured.given; +import static javax.ws.rs.core.HttpHeaders.ACCEPT; +import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.Response.Status.OK; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.http.Header; @QuarkusTest class ServerResourceTest { + @BeforeAll + static void beforeAll() { + RestAssured.filters( + (requestSpec, responseSpec, ctx) -> { + requestSpec.header(new Header(CONTENT_TYPE, APPLICATION_JSON)); + requestSpec.header(new Header(ACCEPT, APPLICATION_JSON)); + return ctx.next(requestSpec, responseSpec); + }); + } + @Test void mapping() { given() @@ -46,4 +64,19 @@ void properties() { .body("host", equalTo("localhost")) .body("port", equalTo(8080)); } + + @Test + void invalid() { + given().get("/server/validator/{prefix}", "cloud") + .then() + .statusCode(OK.getStatusCode()) + .body("errors", hasSize(7)) + .body("errors", hasItem("cloud.log.days must be less than or equal to 15")) + .body("errors", hasItem("cloud.cors.origins[1].port must be greater than or equal to 8000")) + .body("errors", hasItem("cloud.info.name size must be between 0 and 3")) + .body("errors", hasItem("cloud.info.code must be less than or equal to 3")) + .body("errors", hasItem("cloud.info.alias[0] size must be between 0 and 3")) + .body("errors", hasItem("cloud.info.admins.root.username size must be between 0 and 3")) + .body("errors", hasItem("cloud.proxy.timeout must be less than or equal to 10")); + } } diff --git a/tcks/microprofile-config/pom.xml b/tcks/microprofile-config/pom.xml index 9d61669968eb8d..691d6e160d803d 100644 --- a/tcks/microprofile-config/pom.xml +++ b/tcks/microprofile-config/pom.xml @@ -32,7 +32,6 @@ Bob - false dummy Tennis 120