diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a217b347e..82167e472 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,8 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/project.yml b/.github/project.yml index c823288e1..fb7d5a385 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,4 +1,4 @@ name: SmallRye Config release: - current-version: 2.11.1 - next-version: 2.11.2-SNAPSHOT + current-version: 3.7.1 + next-version: 3.7.2-SNAPSHOT diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5f5a0767..69b9d79ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,8 +4,6 @@ on: push: branches: - main - - 1.x - - jakarta paths-ignore: - '.gitignore' - 'CODEOWNERS' @@ -25,42 +23,48 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 17] + java: [11, 17, 21] name: build with jdk ${{matrix.java}} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: checkout - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 name: set up jdk ${{matrix.java}} with: + distribution: 'temurin' java-version: ${{matrix.java}} + cache: 'maven' + cache-dependency-path: '**/pom.xml' - name: build with maven run: mvn -B formatter:validate verify --file pom.xml - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: tck-report with: - name: tck-report + name: tck-report-java-${{matrix.java}} path: testsuite/tck/target/surefire-reports build-windows: runs-on: windows-latest strategy: matrix: - java: [8, 11, 17] + java: [11, 17, 21] name: build with jdk ${{matrix.java}} windows steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: checkout - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 name: set up jdk ${{matrix.java}} with: + distribution: 'temurin' java-version: ${{matrix.java}} + cache: 'maven' + cache-dependency-path: '**/pom.xml' - name: build with maven run: mvn -B formatter:validate verify --file pom.xml @@ -72,14 +76,17 @@ jobs: name: docs steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: checkout - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: 11 + cache: 'maven' + cache-dependency-path: '**/pom.xml' - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: '3.9' @@ -103,15 +110,24 @@ jobs: name: quality steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-java@v1 + + - uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: 11 + cache: 'maven' + cache-dependency-path: '**/pom.xml' + + - name: build with docs and coverage + run: mvn verify -Pcoverage javadoc:javadoc + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 - name: sonar - env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - SONAR_TOKEN: ${{secrets.SONAR_TOKEN}} - run: mvn -B verify --file pom.xml -Pcoverage javadoc:javadoc sonar:sonar -Dsonar.projectKey=smallrye_smallrye-config -Dsonar.login=$SONAR_TOKEN + run: mvn sonar:sonar -Dsonar.projectKey=smallrye_smallrye-config -Dsonar.token=${{secrets.SONAR_TOKEN}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e196dd41..6bc50c5b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,15 +22,16 @@ jobs: github-token: ${{secrets.GITHUB_TOKEN}} metadata-file-path: '.github/project.yml' - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: token: ${{secrets.RELEASE_TOKEN}} - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: 11 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: '3.9' @@ -68,11 +69,12 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} milestone-title: ${{steps.metadata.outputs.current-version}} + milestone-next: ${{steps.metadata.outputs.next-version}} - name: generate tck report run: | cd target/checkout - mvn surefire-report:report-only + mvn surefire-report:report mv testsuite/tck/target/tck-results.html $GITHUB_WORKSPACE - uses: meeDamian/github-release@2.0 diff --git a/.github/workflows/update-milestone.yml b/.github/workflows/update-milestone.yml new file mode 100644 index 000000000..5a1f33549 --- /dev/null +++ b/.github/workflows/update-milestone.yml @@ -0,0 +1,17 @@ +name: Update Milestone + +on: + pull_request_target: + types: [closed] + +jobs: + update: + runs-on: ubuntu-latest + name: update-milestone + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/milestone-set-action@main + name: milestone set + with: + github-token: ${{secrets.GITHUB_TOKEN}} diff --git a/README.adoc b/README.adoc index 58e160ddd..6251c4f30 100644 --- a/README.adoc +++ b/README.adoc @@ -9,7 +9,14 @@ image:https://img.shields.io/maven-central/v/io.smallrye.config/smallrye-config? = SmallRye Config -SmallRye Config is an implementation of {microprofile-config}[Eclipse MicroProfile Config]. +*SmallRye Config* is a library that provides a way to configure applications, frameworks and containers. It is used +in applications servers like https://wildfly.org/[WildFly] and https://openliberty.io[Open Liberty], or frameworks +like https://quarkus.io[Quarkus]. It can also be used completely standalone in any Java application, which makes it a +very flexible library. + +It follows the https://github.com/eclipse/microprofile-config/[MicroProfile Config] specification to provide +the initial config foundations and expands with it own concepts to cover a wide range of use cases observed in the +configuration space. == Instructions @@ -20,7 +27,7 @@ Compile and test the project: mvn verify ---- -Generate the documentation (from the documentation folder): +Generate the documentation (from the `documentation` folder): [source,bash] ---- @@ -35,10 +42,10 @@ mkdocs serve * link:converters[] - Additional Converters * link:documentation[] - Project documentation * link:examples[] - Examples projects to demonstrate SmallRye Config features -* link:implementation[] - Implementation of the MicroProfile Config API +* link:implementation[] - Implementation of SmallRye Config * link:sources[] - Implementation of custom Config Sources * link:testsuite[] - Test suite to run the implementation against the MicroProfile Config TCK -* link:utils[] - A set of additional extensions to enhance MicroProfile Config +* link:utils[] - A set of additional extensions to enhance SmallRye Config * link:validator[] - Bean Validation integration === Contributing diff --git a/cdi/pom.xml b/cdi/pom.xml index 14f4d28c3..25d68da8d 100644 --- a/cdi/pom.xml +++ b/cdi/pom.xml @@ -21,12 +21,53 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config - SmallRye: MicroProfile Config CDI Implementation + SmallRye Config: CDI + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.2 + + + package + + shade + + + false + true + asm-shaded + + + org.objectweb.asm + io.smallrye.config.asm9 + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/versions/** + **/module-info.class + + + + + + + + + diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigException.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigException.java index 2dfaadc89..b6344027f 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigException.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigException.java @@ -19,7 +19,7 @@ /** * A relatively generic Exception that encapsulates the configuration * property's name. - * + * * @author Steve Moyer - The Pennsylvania State University */ public class ConfigException extends Exception { diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java index 8d53e7a78..4cf745669 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java @@ -18,8 +18,6 @@ import static io.smallrye.config.ConfigMappings.registerConfigMappings; import static io.smallrye.config.ConfigMappings.registerConfigProperties; import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; -import static io.smallrye.config.inject.ConfigMappingInjectionBean.getPrefixFromInjectionPoint; -import static io.smallrye.config.inject.ConfigMappingInjectionBean.getPrefixFromType; import static io.smallrye.config.inject.ConfigProducer.isClassHandledByConfigProducer; import static io.smallrye.config.inject.InjectionMessages.formatInjectionPoint; import static io.smallrye.config.inject.SecuritySupport.getContextClassLoader; @@ -38,21 +36,22 @@ import java.util.function.Supplier; import java.util.stream.StreamSupport; -import javax.enterprise.event.Observes; -import javax.enterprise.inject.Instance; -import javax.enterprise.inject.spi.AfterBeanDiscovery; -import javax.enterprise.inject.spi.AfterDeploymentValidation; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.BeforeBeanDiscovery; -import javax.enterprise.inject.spi.Extension; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.enterprise.inject.spi.ProcessAnnotatedType; -import javax.enterprise.inject.spi.ProcessInjectionPoint; -import javax.enterprise.inject.spi.WithAnnotations; -import javax.inject.Provider; - -import org.eclipse.microprofile.config.Config; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.AfterDeploymentValidation; +import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.ProcessAnnotatedType; +import jakarta.enterprise.inject.spi.ProcessInjectionPoint; +import jakarta.enterprise.inject.spi.WithAnnotations; +import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Provider; + import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -69,11 +68,14 @@ */ public class ConfigExtension implements Extension { private final Set configPropertyInjectionPoints = new HashSet<>(); - - private final Set> configProperties = new HashSet<>(); - private final Set configPropertiesInjectionPoints = new HashSet<>(); - private final Set> configMappings = new HashSet<>(); - private final Set configMappingInjectionPoints = new HashSet<>(); + /** ConfigProperties for SmallRye Config */ + private final Set configProperties = new HashSet<>(); + /** ConfigProperties for CDI */ + private final Set configPropertiesBeans = new HashSet<>(); + /** ConfigMappings for SmallRye Config */ + private final Set configMappings = new HashSet<>(); + /** ConfigMappings for CDI */ + private final Set configMappingBeans = new HashSet<>(); public ConfigExtension() { } @@ -81,15 +83,33 @@ public ConfigExtension() { protected void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) { AnnotatedType configBean = bm.createAnnotatedType(ConfigProducer.class); bbd.addAnnotatedType(configBean, ConfigProducer.class.getName()); + + // Remove NonBinding annotation. OWB is not able to look up CDI beans programmatically with NonBinding in the + // case the look-up changed the non-binding parameters (in this case the prefix) + AnnotatedTypeConfigurator configPropertiesConfigurator = bbd + .configureQualifier(ConfigProperties.class); + configPropertiesConfigurator.methods().forEach(methodConfigurator -> methodConfigurator + .remove(annotation -> annotation.annotationType().equals(Nonbinding.class))); } protected void processConfigProperties( @Observes @WithAnnotations(ConfigProperties.class) ProcessAnnotatedType> processAnnotatedType) { - // Even if we filter in the CDI event, beans containing injection points of ConfigMapping are also fired. + // Even if we filter in the CDI event, beans containing injection points of ConfigProperties are also fired. if (processAnnotatedType.getAnnotatedType().isAnnotationPresent(ConfigProperties.class)) { - // We are going to veto, because it may be a managed bean and we will use a configurator bean + // We are going to veto, because it may be a managed bean, and we will use a configurator bean processAnnotatedType.veto(); - configProperties.add(processAnnotatedType.getAnnotatedType()); + + // Each config class is both in SmallRyeConfig and managed by a configurator bean. + // CDI requires more beans for injection points due to binding prefix. + ConfigClassWithPrefix properties = configClassWithPrefix(processAnnotatedType.getAnnotatedType().getJavaClass(), + processAnnotatedType.getAnnotatedType().getAnnotation(ConfigProperties.class).prefix()); + // Unconfigured is represented as an empty String in SmallRye Config + if (!properties.getPrefix().equals(ConfigProperties.UNCONFIGURED_PREFIX)) { + configProperties.add(properties); + } else { + configProperties.add(ConfigClassWithPrefix.configClassWithPrefix(properties.getKlass(), "")); + } + configPropertiesBeans.add(properties); } } @@ -97,9 +117,15 @@ protected void processConfigMappings( @Observes @WithAnnotations(ConfigMapping.class) ProcessAnnotatedType> processAnnotatedType) { // Even if we filter in the CDI event, beans containing injection points of ConfigMapping are also fired. if (processAnnotatedType.getAnnotatedType().isAnnotationPresent(ConfigMapping.class)) { - // We are going to veto, because it may be a managed bean and we will use a configurator bean + // We are going to veto, because it may be a managed bean, and we will use a configurator bean processAnnotatedType.veto(); - configMappings.add(processAnnotatedType.getAnnotatedType()); + + // Each config class is both in SmallRyeConfig and managed by a configurator bean. + // CDI requires a single configurator bean per class due to non-binding prefix. + ConfigClassWithPrefix mapping = configClassWithPrefix(processAnnotatedType.getAnnotatedType().getJavaClass(), + processAnnotatedType.getAnnotatedType().getAnnotation(ConfigMapping.class).prefix()); + configMappings.add(mapping); + configMappingBeans.add(mapping); } } @@ -109,11 +135,25 @@ protected void processConfigInjectionPoints(@Observes ProcessInjectionPoint, ? } if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigProperties.class)) { - configPropertiesInjectionPoints.add(pip.getInjectionPoint()); + ConfigClassWithPrefix properties = configClassWithPrefix((Class>) pip.getInjectionPoint().getType(), + pip.getInjectionPoint().getAnnotated().getAnnotation(ConfigProperties.class).prefix()); + + // If the prefix is empty at the injection point, fallbacks to the class prefix (already registered) + if (!properties.getPrefix().equals(ConfigProperties.UNCONFIGURED_PREFIX)) { + configProperties.add(properties); + } + // Cover all combinations of the configurator bean for ConfigProperties because prefix is binding + configPropertiesBeans.add(properties); } + // Add to SmallRyeConfig config classes with a different prefix by injection point if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigMapping.class)) { - configMappingInjectionPoints.add(pip.getInjectionPoint()); + ConfigClassWithPrefix mapping = configClassWithPrefix((Class>) pip.getInjectionPoint().getType(), + pip.getInjectionPoint().getAnnotated().getAnnotation(ConfigMapping.class).prefix()); + // If the prefix is empty at the injection point, fallbacks to the class prefix (already registered) + if (!mapping.getPrefix().isEmpty()) { + configMappings.add(mapping); + } } } @@ -125,7 +165,7 @@ protected void registerCustomBeans(@Observes AfterBeanDiscovery abd, BeanManager ParameterizedType type = (ParameterizedType) requiredType; // TODO We should probably handle all parameterized types correctly if (type.getRawType().equals(Provider.class) || type.getRawType().equals(Instance.class)) { - // These injection points are satisfied by the built-in Instance bean + // These injection points are satisfied by the built-in Instance bean Type typeArgument = type.getActualTypeArguments()[0]; if (typeArgument instanceof Class && !isClassHandledByConfigProducer(typeArgument)) { customTypes.add((Class>) typeArgument); @@ -137,18 +177,28 @@ protected void registerCustomBeans(@Observes AfterBeanDiscovery abd, BeanManager } } - customTypes.stream().map(customType -> new ConfigInjectionBean<>(bm, customType)).forEach(abd::addBean); - configProperties.stream().map(annotatedType -> new ConfigMappingInjectionBean<>(bm, annotatedType)) - .forEach(abd::addBean); - configMappings.stream().map(annotatedType -> new ConfigMappingInjectionBean<>(bm, annotatedType)).forEach(abd::addBean); + customTypes.forEach(customType -> abd.addBean(new ConfigInjectionBean<>(bm, customType))); + configPropertiesBeans.forEach(properties -> abd.addBean(new ConfigPropertiesInjectionBean<>(properties))); + configMappingBeans.forEach(mapping -> abd.addBean(new ConfigMappingInjectionBean<>(mapping, bm))); } protected void validate(@Observes AfterDeploymentValidation adv) { - Config config = ConfigProvider.getConfig(getContextClassLoader()); + SmallRyeConfig config = ConfigProvider.getConfig(getContextClassLoader()).unwrap(SmallRyeConfig.class); Set configNames = StreamSupport.stream(config.getPropertyNames().spliterator(), false).collect(toSet()); for (InjectionPoint injectionPoint : getConfigPropertyInjectionPoints()) { Type type = injectionPoint.getType(); + // validate injection config name + ConfigProperty configProperty = injectionPoint.getAnnotated().getAnnotation(ConfigProperty.class); + String name; + try { + name = ConfigProducerUtil.getConfigKey(injectionPoint, configProperty); + } catch (IllegalStateException e) { + adv.addDeploymentProblem(InjectionMessages.msg.retrieveConfigFailure(null, formatInjectionPoint(injectionPoint), + e.getLocalizedMessage(), e)); + continue; + } + // We don't validate the Optional / Provider / Supplier / ConfigValue for defaultValue. if (type instanceof Class && org.eclipse.microprofile.config.ConfigValue.class.isAssignableFrom((Class>) type) || type instanceof Class && OptionalInt.class.isAssignableFrom((Class>) type) @@ -161,23 +211,15 @@ protected void validate(@Observes AfterDeploymentValidation adv) { continue; } - ConfigProperty configProperty = injectionPoint.getAnnotated().getAnnotation(ConfigProperty.class); - String name; - try { - name = ConfigProducerUtil.getConfigKey(injectionPoint, configProperty); - } catch (IllegalStateException e) { - adv.addDeploymentProblem( - InjectionMessages.msg.retrieveConfigFailure(null, formatInjectionPoint(injectionPoint), - e.getLocalizedMessage(), e)); - continue; - } - // Check if the name is part of the properties first. // Since properties can be a subset, then search for the actual property for a value. - // Check if it is a map - // Finally also check if the property is indexed (might be a Collection with indexed properties). - if ((!configNames.contains(name) && ConfigProducerUtil.getRawValue(name, config) == null) - && !isMap(type) && !isIndexed(type, name, config)) { + // Check if the property is indexed (might be a Collection with indexed properties). + // Check if the properti is part of a Map + if (!configNames.contains(name) + && ConfigProducerUtil.getConfigValue(name, config).getValue() == null + && !isIndexed(type, name, config) + && !isMap(type, name, config)) { + if (configProperty.defaultValue().equals(ConfigProperty.UNCONFIGURED_VALUE)) { adv.addDeploymentProblem( InjectionMessages.msg.noConfigValue(name, formatInjectionPoint(injectionPoint))); @@ -194,14 +236,9 @@ protected void validate(@Observes AfterDeploymentValidation adv) { } } - Set configMappingsWithPrefix = mapToConfigObjectWithPrefix(configMappings, - configMappingInjectionPoints); - Set configPropertiesWithPrefix = mapToConfigObjectWithPrefix(configProperties, - configPropertiesInjectionPoints); - try { - registerConfigMappings(config.unwrap(SmallRyeConfig.class), configMappingsWithPrefix); - registerConfigProperties(config.unwrap(SmallRyeConfig.class), configPropertiesWithPrefix); + registerConfigMappings(config, configMappings); + registerConfigProperties(config, configProperties); } catch (ConfigValidationException e) { adv.addDeploymentProblem(e); } @@ -211,38 +248,16 @@ protected Set getConfigPropertyInjectionPoints() { return configPropertyInjectionPoints; } - private static Set mapToConfigObjectWithPrefix( - Set> annotatedTypes, - Set injectionPoints) { - - Set configMappingsWithPrefix = new HashSet<>(); - for (AnnotatedType> annotatedType : annotatedTypes) { - configMappingsWithPrefix - .add(configClassWithPrefix(annotatedType.getJavaClass(), getPrefixFromType(annotatedType))); - } - for (InjectionPoint injectionPoint : injectionPoints) { - getPrefixFromInjectionPoint(injectionPoint).ifPresent(prefix -> configMappingsWithPrefix - .add(configClassWithPrefix((Class>) injectionPoint.getType(), prefix))); - } - return configMappingsWithPrefix; - } - - private static boolean isIndexed(Type type, String name, Config config) { + private static boolean isIndexed(Type type, String name, SmallRyeConfig config) { return type instanceof ParameterizedType && (List.class.isAssignableFrom((Class>) ((ParameterizedType) type).getRawType()) || Set.class.isAssignableFrom((Class>) ((ParameterizedType) type).getRawType())) - && - !((SmallRyeConfig) config).getIndexedPropertiesIndexes(name).isEmpty(); + && !config.getIndexedPropertiesIndexes(name).isEmpty(); } - /** - * Indicates whether the given type is a type of Map. - * - * @param type the type to check - * @return {@code true} if the given type is a type of Map, {@code false} otherwise. - */ - private static boolean isMap(final Type type) { - return type instanceof ParameterizedType && - Map.class.isAssignableFrom((Class>) ((ParameterizedType) type).getRawType()); + private static boolean isMap(final Type type, String name, SmallRyeConfig config) { + return type instanceof ParameterizedType + && Map.class.isAssignableFrom((Class>) ((ParameterizedType) type).getRawType()) + && !config.getMapKeys(name).isEmpty(); } } diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigInjectionBean.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigInjectionBean.java index 22bf16238..d9d3c81fc 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigInjectionBean.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigInjectionBean.java @@ -23,16 +23,16 @@ import java.util.Optional; import java.util.Set; -import javax.enterprise.context.Dependent; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.Instance; -import javax.enterprise.inject.spi.Annotated; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.enterprise.inject.spi.PassivationCapable; -import javax.enterprise.util.AnnotationLiteral; -import javax.inject.Provider; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.PassivationCapable; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Provider; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -73,11 +73,6 @@ public Class> getBeanClass() { return ConfigInjectionBean.class; } - @Override - public boolean isNullable() { - return false; - } - @Override @SuppressWarnings("unchecked") public T create(CreationalContext context) { @@ -101,12 +96,12 @@ public T create(CreationalContext context) { } } else { Class> annotatedTypeClass = (Class>) annotated.getBaseType(); - if (defaultValue.length() == 0) { + if (defaultValue.isEmpty()) { return (T) getConfig().getValue(key, annotatedTypeClass); } else { Optional optionalValue = (Optional) getConfig().getOptionalValue(key, annotatedTypeClass); return optionalValue.orElseGet( - () -> (T) ((SmallRyeConfig) getConfig()).convert(defaultValue, annotatedTypeClass)); + () -> (T) getConfig().unwrap(SmallRyeConfig.class).convert(defaultValue, annotatedTypeClass)); } } diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigMappingInjectionBean.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigMappingInjectionBean.java index c657968cb..ed88adb33 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigMappingInjectionBean.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigMappingInjectionBean.java @@ -1,48 +1,37 @@ package io.smallrye.config.inject; import static io.smallrye.config.inject.SecuritySupport.getContextClassLoader; -import static java.util.Optional.ofNullable; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Collections; -import java.util.HashSet; -import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; -import javax.enterprise.context.Dependent; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.Default; -import javax.enterprise.inject.spi.Annotated; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.InjectionPoint; import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.config.inject.ConfigProperties; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; import io.smallrye.config.SmallRyeConfig; public class ConfigMappingInjectionBean implements Bean { private final BeanManager bm; - private final Class klass; - private final String prefix; - private final Set qualifiers = new HashSet<>(); + private final ConfigClassWithPrefix configClassWithPrefix; - public ConfigMappingInjectionBean(final BeanManager bm, final AnnotatedType type) { + public ConfigMappingInjectionBean(final ConfigClassWithPrefix configClassWithPrefix, final BeanManager bm) { this.bm = bm; - this.klass = type.getJavaClass(); - this.prefix = getPrefixFromType(type); - this.qualifiers.add(type.isAnnotationPresent(ConfigProperties.class) ? ConfigProperties.Literal.of(prefix) - : Default.Literal.INSTANCE); + this.configClassWithPrefix = configClassWithPrefix; } @Override - public Class> getBeanClass() { - return klass; + public Class getBeanClass() { + return (Class) configClassWithPrefix.getKlass(); } @Override @@ -50,18 +39,21 @@ public Set getInjectionPoints() { return Collections.emptySet(); } - @Override - public boolean isNullable() { - return false; - } - @Override public T create(final CreationalContext creationalContext) { InjectionPoint injectionPoint = (InjectionPoint) bm.getInjectableReference(new MetadataInjectionPoint(), creationalContext); - SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(getContextClassLoader()); - return config.getConfigMapping(klass, getPrefixFromInjectionPoint(injectionPoint).orElse(prefix)); + String prefix = configClassWithPrefix.getPrefix(); + if (injectionPoint != null && injectionPoint.getAnnotated() != null) { + ConfigMapping configMapping = injectionPoint.getAnnotated().getAnnotation(ConfigMapping.class); + if (configMapping != null) { + prefix = configMapping.prefix(); + } + } + + SmallRyeConfig config = ConfigProvider.getConfig(getContextClassLoader()).unwrap(SmallRyeConfig.class); + return config.getConfigMapping(getBeanClass(), prefix); } @Override @@ -71,12 +63,12 @@ public void destroy(final T instance, final CreationalContext creationalConte @Override public Set getTypes() { - return Collections.singleton(klass); + return Collections.singleton(configClassWithPrefix.getKlass()); } @Override public Set getQualifiers() { - return qualifiers; + return Collections.singleton(Default.Literal.INSTANCE); } @Override @@ -86,7 +78,7 @@ public Class extends Annotation> getScope() { @Override public String getName() { - return this.getClass() + "_" + klass.getName(); + return this.getClass().getSimpleName() + "_" + configClassWithPrefix.getKlass().getName(); } @Override @@ -98,44 +90,4 @@ public Set> getStereotypes() { public boolean isAlternative() { return false; } - - public static String getPrefixFromType(final Annotated annotated) { - final Optional prefixFromConfigMappingClass = ofNullable(annotated.getBaseType()).map(type -> (Class>) type) - .map(c -> c.getAnnotation(ConfigMapping.class)) - .map(ConfigMapping::prefix); - - final Optional prefixFromConfigPropertiesClass = ofNullable(annotated.getBaseType()) - .map(type -> (Class>) type) - .map(c -> c.getAnnotation(ConfigProperties.class)) - .map(ConfigProperties::prefix) - .filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX)); - - return Stream.of(prefixFromConfigMappingClass, prefixFromConfigPropertiesClass) - .flatMap(s -> s.map(Stream::of).orElseGet(Stream::empty)) - .findFirst() - .orElse(""); - } - - public static Optional getPrefixFromInjectionPoint(final InjectionPoint injectionPoint) { - final Optional prefixFromConfigMapping = Optional.ofNullable(injectionPoint.getAnnotated()) - .map(a -> a.getAnnotation(ConfigMapping.class)) - .map(ConfigMapping::prefix) - .filter(prefix -> !prefix.isEmpty()); - - final Optional prefixFromConfigProperties = Optional.ofNullable(injectionPoint.getAnnotated()) - .map(a -> a.getAnnotation(ConfigProperties.class)) - .map(ConfigProperties::prefix) - .filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX)); - - final Optional prefixFromQualifier = injectionPoint.getQualifiers().stream() - .filter(ConfigProperties.class::isInstance) - .map(ConfigProperties.class::cast) - .map(ConfigProperties::prefix) - .filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX)) - .findFirst(); - - return Stream.of(prefixFromConfigMapping, prefixFromConfigProperties, prefixFromQualifier) - .flatMap(s -> s.map(Stream::of).orElseGet(Stream::empty)) - .findFirst(); - } } diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigProducer.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigProducer.java index 69cfbf2a3..104c3770c 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigProducer.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigProducer.java @@ -21,10 +21,10 @@ import java.util.*; import java.util.function.Supplier; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.InjectionPoint; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -111,14 +111,14 @@ protected Character produceCharacterConfigProperty(InjectionPoint ip) { @Dependent @Produces @ConfigProperty - protected Optional produceOptionalConfigValue(InjectionPoint ip) { + protected Optional produceOptionalConfigProperty(InjectionPoint ip) { return ConfigProducerUtil.getValue(ip, getConfig()); } @Dependent @Produces @ConfigProperty - protected Supplier produceSupplierConfigValue(InjectionPoint ip) { + protected Supplier produceSupplierConfigProperty(InjectionPoint ip) { return () -> ConfigProducerUtil.getValue(ip, getConfig()); } @@ -168,7 +168,7 @@ protected OptionalDouble produceOptionalDoubleConfigProperty(InjectionPoint ip) @Produces @ConfigProperty protected ConfigValue produceConfigValue(InjectionPoint ip) { - return (ConfigValue) ConfigProducerUtil.getConfigValue(ip, getConfig()); + return ConfigProducerUtil.getConfigValue(ip, getConfig()); } public static boolean isClassHandledByConfigProducer(Type requiredType) { diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigProducerUtil.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigProducerUtil.java index 53da59352..9db8f3606 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/ConfigProducerUtil.java +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigProducerUtil.java @@ -11,6 +11,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -20,16 +21,17 @@ import java.util.function.IntFunction; import java.util.function.Supplier; -import javax.enterprise.inject.spi.AnnotatedMember; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.AnnotatedMember; +import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Provider; import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigValue; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.Converters; +import io.smallrye.config.ConfigValue; import io.smallrye.config.SecretKeys; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.common.AbstractConverter; @@ -55,7 +57,19 @@ private ConfigProducerUtil() { * @return the converted configuration value. */ public static T getValue(InjectionPoint injectionPoint, Config config) { - return getValue(getName(injectionPoint), injectionPoint.getType(), getDefaultValue(injectionPoint), config); + return getValue(getName(injectionPoint), getType(injectionPoint), getDefaultValue(injectionPoint), config); + } + + private static Type getType(InjectionPoint injectionPoint) { + Type type = injectionPoint.getType(); + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (parameterizedType.getRawType().equals(Provider.class) + || parameterizedType.getRawType().equals(Instance.class)) { + return parameterizedType.getActualTypeArguments()[0]; + } + } + return type; } /** @@ -72,38 +86,25 @@ public static T getValue(String name, Type type, String defaultValue, Config if (name == null) { return null; } + + SmallRyeConfig smallRyeConfig = config.unwrap(SmallRyeConfig.class); + ConfigValue configValue = getConfigValue(name, smallRyeConfig); + ConfigValue configValueWithDefault = configValue.withValue(resolveDefault(configValue.getValue(), defaultValue)); + if (hasCollection(type)) { - return convertValues(name, type, getRawValue(name, config), defaultValue, config); + return convertValues(configValueWithDefault, type, smallRyeConfig); } else if (hasMap(type)) { - return convertValues(name, type, defaultValue, config); + return convertMapValues(configValueWithDefault, type, smallRyeConfig); } - return ((SmallRyeConfig) config).convertValue(name, resolveDefault(getRawValue(name, config), defaultValue), - resolveConverter(type, config)); + return smallRyeConfig.convertValue(configValueWithDefault, resolveConverter(type, smallRyeConfig)); } - /** - * Converts the direct sub properties of the given parent property as a Map. - * - * @param name the name of the parent property for which we want the direct sub properties as a Map. - * @param type the {@link Type} of the configuration value to convert. - * @param defaultValue the default value to convert in case no sub properties could be found. - * @param config the configuration from which the values are retrieved. - * @param the expected type of the configuration value to convert. - * - * @return the converted configuration value. - */ - private static T convertValues(String name, Type type, String defaultValue, Config config) { - return ((SmallRyeConfig) config).convertValue(name, null, - resolveConverter(type, config, (kC, vC) -> new StaticMapConverter<>(name, defaultValue, config, kC, vC))); - } - - private static T convertValues(String name, Type type, String rawValue, String defaultValue, Config config) { - List indexedProperties = ((SmallRyeConfig) config).getIndexedProperties(name); + private static T convertValues(ConfigValue configValue, Type type, SmallRyeConfig config) { + List indexedProperties = config.getIndexedProperties(configValue.getName()); // If converting a config property which exists (i.e. myProp[1] = aValue) or no indexed properties exist for the config property - if (rawValue != null || indexedProperties.isEmpty()) { - return ((SmallRyeConfig) config).convertValue(name, resolveDefault(rawValue, defaultValue), - resolveConverter(type, config)); + if (configValue.getRawValue() != null || indexedProperties.isEmpty()) { + return config.convertValue(configValue, resolveConverter(type, config)); } BiFunction, IntFunction>, Collection> indexedConverter = (itemConverter, @@ -111,9 +112,7 @@ private static T convertValues(String name, Type type, String rawValue, Stri Collection collection = collectionFactory.apply(indexedProperties.size()); for (String indexedProperty : indexedProperties) { // Never null by the rules of converValue - collection.add( - ((SmallRyeConfig) config).convertValue(indexedProperty, getRawValue(indexedProperty, config), - itemConverter)); + collection.add(config.convertValue(getConfigValue(indexedProperty, config), itemConverter)); } return collection; }; @@ -121,37 +120,48 @@ private static T convertValues(String name, Type type, String rawValue, Stri return resolveConverterForIndexed(type, config, indexedConverter).convert(" "); } - static ConfigValue getConfigValue(InjectionPoint injectionPoint, Config config) { + private static T convertMapValues(ConfigValue configValue, Type type, SmallRyeConfig config) { + Map mapKeys = config.getMapKeys(configValue.getName()); + if (configValue.getRawValue() != null || mapKeys.isEmpty()) { + return config.convertValue(configValue, resolveConverter(type, config)); + } + + BiFunction, Converter>, Map, ?>> mapConverter = (keyConverter, valueConverter) -> { + Map map = new HashMap<>(mapKeys.size()); + for (Map.Entry entry : mapKeys.entrySet()) { + map.put(keyConverter.convert(entry.getKey()), + config.convertValue(getConfigValue(entry.getValue(), config), valueConverter)); + } + return map; + }; + + return ConfigProducerUtil. resolveConverterForMap(type, config, mapConverter).convert(" "); + } + + static ConfigValue getConfigValue(InjectionPoint injectionPoint, SmallRyeConfig config) { String name = getName(injectionPoint); if (name == null) { return null; } - ConfigValue configValue = config.getConfigValue(name); + io.smallrye.config.ConfigValue configValue = config.getConfigValue(name); if (configValue.getRawValue() == null) { - if (configValue instanceof io.smallrye.config.ConfigValue) { - configValue = ((io.smallrye.config.ConfigValue) configValue).withValue(getDefaultValue(injectionPoint)); - } + configValue = configValue.withValue(getDefaultValue(injectionPoint)); } return configValue; } - static String getRawValue(String name, Config config) { - return SecretKeys.doUnlocked(() -> config.getConfigValue(name).getValue()); + static ConfigValue getConfigValue(String name, SmallRyeConfig config) { + return SecretKeys.doUnlocked(() -> config.getConfigValue(name)); } private static String resolveDefault(String rawValue, String defaultValue) { return rawValue != null ? rawValue : defaultValue; } - private static Converter resolveConverter(final Type type, final Config config) { - return resolveConverter(type, config, Converters::newMapConverter); - } - @SuppressWarnings("unchecked") - private static Converter resolveConverter(final Type type, final Config config, - final BiFunction, Converter, Converter>> mapConverterFactory) { + private static Converter resolveConverter(final Type type, final SmallRyeConfig config) { Class rawType = rawTypeOf(type); if (type instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) type; @@ -161,17 +171,13 @@ private static Converter resolveConverter(final Type type, final Config c } else if (rawType == Set.class) { return (Converter) newCollectionConverter(resolveConverter(typeArgs[0], config), HashSet::new); } else if (rawType == Map.class) { - return (Converter) mapConverterFactory.apply(resolveConverter(typeArgs[0], config), - resolveConverter(typeArgs[1], config)); + return (Converter) newMapConverter(resolveConverter(typeArgs[0], config), + resolveConverter(typeArgs[1], config), HashMap::new); } else if (rawType == Optional.class) { - return (Converter) newOptionalConverter(resolveConverter(typeArgs[0], config, mapConverterFactory)); + return (Converter) newOptionalConverter(resolveConverter(typeArgs[0], config)); } else if (rawType == Supplier.class) { - return resolveConverter(typeArgs[0], config, mapConverterFactory); + return resolveConverter(typeArgs[0], config); } - } else if (rawType == Map.class) { - // No parameterized types have been provided so it assumes that a Map of String is expected - return (Converter) mapConverterFactory.apply(resolveConverter(String.class, config), - resolveConverter(String.class, config)); } // just try the raw type return config.getConverter(rawType).orElseThrow(() -> InjectionMessages.msg.noRegisteredConverter(rawType)); @@ -179,7 +185,7 @@ private static Converter resolveConverter(final Type type, final Config c /** * We need to handle indexed properties in a special way, since a Collection may be wrapped in other converters. - * The issue is that in the original code the value was retrieve by calling the first converter that will delegate + * The issue is that in the original code the value was retrieved by calling the first converter that will delegate * to all the wrapped types until it finally gets the result. For indexed properties, because it requires * additional key lookups, a special converter is added to perform the work. This is mostly a workaround, since * converters do not have the proper API, and probably should not have to handle this type of logic. @@ -189,7 +195,7 @@ private static Converter resolveConverter(final Type type, final Config c @SuppressWarnings("unchecked") private static Converter resolveConverterForIndexed( final Type type, - final Config config, + final SmallRyeConfig config, final BiFunction, IntFunction>, Collection> indexedConverter) { Class rawType = rawTypeOf(type); @@ -209,7 +215,30 @@ private static Converter resolveConverterForIndexed( } } - throw new IllegalStateException(); + throw new IllegalArgumentException(); + } + + @SuppressWarnings("unchecked") + private static Converter resolveConverterForMap( + final Type type, + final SmallRyeConfig config, + final BiFunction, Converter>, Map, ?>> mapConverter) { + + Class rawType = rawTypeOf(type); + if (type instanceof ParameterizedType) { + ParameterizedType paramType = (ParameterizedType) type; + Type[] typeArgs = paramType.getActualTypeArguments(); + if (rawType == Map.class) { + return (Converter) new MapKeyConverter<>(resolveConverter(typeArgs[0], config), + resolveConverter(typeArgs[1], config), mapConverter); + } else if (rawType == Optional.class) { + return (Converter) newOptionalConverter(resolveConverterForMap(typeArgs[0], config, mapConverter)); + } else if (rawType == Supplier.class) { + return resolveConverterForMap(typeArgs[0], config, mapConverter); + } + } + + throw new IllegalArgumentException(); } @SuppressWarnings("unchecked") @@ -225,13 +254,6 @@ private static Class rawTypeOf(final Type type) { } } - /** - * Indicates whether the given type is a type of Map or is a Supplier or Optional of Map. - * - * @param type the type to check - * @return {@code true} if the given type is a type of Map or is a Supplier or Optional of Map, - * {@code false} otherwise. - */ private static boolean hasMap(final Type type) { Class> rawType = rawTypeOf(type); if (rawType == Map.class) { @@ -335,76 +357,25 @@ public C convert(final String value) throws IllegalArgumentException, NullPointe } } - /** - * A {@code Converter} of a Map that gives the same Map content whatever the value to convert. It actually relies on - * its parameters to convert the sub properties of a fixed parent property as a Map. - * - * @param The type of the keys. - * @param The type of the values. - */ - static final class StaticMapConverter extends AbstractConverter> { - private static final long serialVersionUID = 402894491607011464L; - - /** - * The name of the parent property for which we want the direct sub properties as a Map. - */ - private final String name; - /** - * The default value to convert in case no sub properties could be found. - */ - private final String defaultValue; - /** - * The configuration from which the values are retrieved. - */ - private final Config config; - /** - * The converter to use for the keys. - */ - private final Converter keyConverter; - /** - * The converter to use the for values. - */ - private final Converter valueConverter; - - /** - * Construct a {@code StaticMapConverter} with the given parameters. - * - * @param name the name of the parent property for which we want the direct sub properties as a Map - * @param defaultValue the default value to convert in case no sub properties could be found - * @param config the configuration from which the values are retrieved - * @param keyConverter the converter to use for the keys - * @param valueConverter the converter to use the for values - */ - StaticMapConverter(String name, String defaultValue, Config config, Converter keyConverter, - Converter valueConverter) { - this.name = name; - this.defaultValue = defaultValue; - this.config = config; + static final class MapKeyConverter extends AbstractConverter> { + private static final long serialVersionUID = -2920578756435265533L; + + private final Converter extends K> keyConverter; + private final Converter extends V> valueConverter; + private final BiFunction, Converter extends V>, Map extends K, ? extends V>> mapConverter; + + public MapKeyConverter( + final Converter extends K> keyConverter, + final Converter extends V> valueConverter, + final BiFunction, Converter extends V>, Map extends K, ? extends V>> mapConverter) { this.keyConverter = keyConverter; this.valueConverter = valueConverter; + this.mapConverter = mapConverter; } - /** - * {@inheritDoc} - * - * Gives the sub properties as a Map if they exist, otherwise gives the default value converted with a - * {@code MapConverter}. - */ @Override - public Map convert(String value) throws IllegalArgumentException, NullPointerException { - Map result = getValues(name, config, keyConverter, valueConverter); - if (result == null && defaultValue != null) { - result = newMapConverter(keyConverter, valueConverter).convert(defaultValue); - } - return result; - } - - /** - * @return the content of the direct sub properties as the requested type of Map. - */ - private static Map getValues(String name, Config config, Converter keyConverter, - Converter valueConverter) { - return SecretKeys.doUnlocked(() -> ((SmallRyeConfig) config).getValuesAsMap(name, keyConverter, valueConverter)); + public Map extends K, ? extends V> convert(final String value) throws IllegalArgumentException, NullPointerException { + return mapConverter.apply(keyConverter, valueConverter); } } } diff --git a/cdi/src/main/java/io/smallrye/config/inject/ConfigPropertiesInjectionBean.java b/cdi/src/main/java/io/smallrye/config/inject/ConfigPropertiesInjectionBean.java new file mode 100644 index 000000000..d0653ee06 --- /dev/null +++ b/cdi/src/main/java/io/smallrye/config/inject/ConfigPropertiesInjectionBean.java @@ -0,0 +1,89 @@ +package io.smallrye.config.inject; + +import static io.smallrye.config.inject.SecuritySupport.getContextClassLoader; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Set; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.InjectionPoint; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperties; + +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigPropertiesInjectionBean implements Bean { + private final ConfigClassWithPrefix configClassWithPrefix; + private final Set qualifiers; + + ConfigPropertiesInjectionBean(final ConfigClassWithPrefix configClassWithPrefix) { + this.configClassWithPrefix = configClassWithPrefix; + this.qualifiers = Collections.singleton(ConfigProperties.Literal.of(configClassWithPrefix.getPrefix())); + } + + @Override + public Class getBeanClass() { + return (Class) configClassWithPrefix.getKlass(); + } + + @Override + public Set getInjectionPoints() { + return Collections.emptySet(); + } + + @Override + public T create(final CreationalContext creationalContext) { + String prefix = configClassWithPrefix.getPrefix(); + if (prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX)) { + prefix = configClassWithPrefix.getKlass().getAnnotation(ConfigProperties.class).prefix(); + if (prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX)) { + prefix = ""; + } + } + + SmallRyeConfig config = ConfigProvider.getConfig(getContextClassLoader()).unwrap(SmallRyeConfig.class); + return config.getConfigMapping(getBeanClass(), prefix); + } + + @Override + public void destroy(final T instance, final CreationalContext creationalContext) { + + } + + @Override + public Set getTypes() { + return Collections.singleton(configClassWithPrefix.getKlass()); + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public Class extends Annotation> getScope() { + return Dependent.class; + } + + @Override + public String getName() { + return this.getClass().getSimpleName() + "_" + configClassWithPrefix.getKlass().getName() + "_" + + configClassWithPrefix.getPrefix(); + } + + @Override + public Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + public boolean isAlternative() { + return false; + } +} diff --git a/cdi/src/main/java/io/smallrye/config/inject/InjectionMessages.java b/cdi/src/main/java/io/smallrye/config/inject/InjectionMessages.java index d12fe48db..085018bcc 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/InjectionMessages.java +++ b/cdi/src/main/java/io/smallrye/config/inject/InjectionMessages.java @@ -10,7 +10,7 @@ import java.util.NoSuchElementException; import java.util.stream.Collectors; -import javax.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.InjectionPoint; import org.jboss.logging.Messages; import org.jboss.logging.annotations.Cause; @@ -49,32 +49,32 @@ ConfigException retrieveConfigFailure(@Param @Pos(1) String configPropertyName, IllegalArgumentException noRegisteredConverter(Class> type); /** - * + * * Formats InjectPoint information for Exception messages. * - * + * * 3 possible InjectionPoint types are considered: * - * + * * Fields * Given: java.lang.String * io.smallrye.config.inject.ValidateInjectionTest$SkipPropertiesTest$SkipPropertiesBean.missingProp * Returns: io.smallrye.config.inject.ValidateInjectionTest$SkipPropertiesTest$SkipPropertiesBean.missingProp * - * + * * Method parameters * Given: private void * io.smallrye.config.inject.ValidateInjectionTest$MethodUnnamedPropertyTest$MethodUnnamedPropertyBean.methodUnnamedProperty(java.lang.String) * Returns: * io.smallrye.config.inject.ValidateInjectionTest$MethodUnnamedPropertyTest$MethodUnnamedPropertyBean.methodUnnamedProperty(String) * - * + * * Constructor parameters * Given: public * io.smallrye.config.inject.ValidateInjectionTest$ConstructorUnnamedPropertyTest$ConstructorUnnamedPropertyBean(java.lang.String) * Returns: * io.smallrye.config.inject.ValidateInjectionTest$ConstructorUnnamedPropertyTest$ConstructorUnnamedPropertyBean(String) - * + * */ public static String formatInjectionPoint(InjectionPoint injectionPoint) { diff --git a/cdi/src/main/java/io/smallrye/config/inject/MetadataInjectionPoint.java b/cdi/src/main/java/io/smallrye/config/inject/MetadataInjectionPoint.java index 5ea1e70ab..79004f251 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/MetadataInjectionPoint.java +++ b/cdi/src/main/java/io/smallrye/config/inject/MetadataInjectionPoint.java @@ -6,11 +6,11 @@ import java.util.Collections; import java.util.Set; -import javax.enterprise.inject.Default; -import javax.enterprise.inject.spi.Annotated; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.enterprise.util.AnnotationLiteral; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.util.AnnotationLiteral; class MetadataInjectionPoint implements InjectionPoint { @Override diff --git a/cdi/src/main/java/io/smallrye/config/inject/SecuritySupport.java b/cdi/src/main/java/io/smallrye/config/inject/SecuritySupport.java index 7d2cfede2..229133270 100644 --- a/cdi/src/main/java/io/smallrye/config/inject/SecuritySupport.java +++ b/cdi/src/main/java/io/smallrye/config/inject/SecuritySupport.java @@ -19,7 +19,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; -import io.smallrye.config.ConfigLogging; +import io.smallrye.config._private.ConfigLogging; /** * @author Jeff Mesnil (c) 2018 Red Hat inc. diff --git a/cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/cdi/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension similarity index 100% rename from cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension rename to cdi/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension diff --git a/cdi/src/test/java/io/smallrye/config/inject/ConfigInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/ConfigInjectionTest.java index 423fcae61..cfa6ade09 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/ConfigInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/ConfigInjectionTest.java @@ -16,8 +16,8 @@ import java.util.Set; import java.util.function.Supplier; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -57,10 +57,10 @@ void inject() { assertEquals("OK", configBean.getReasonsSupplier().get().get(200)); assertEquals("Created", configBean.getReasonsSupplier().get().get(201)); assertFalse(configBean.getReasonsOptional().isPresent()); - assertEquals(2, configBean.getVersions().size()); + assertEquals(3, configBean.getVersions().size()); assertEquals(new Version(1, "The version 1.2.3"), configBean.getVersions().get("v1")); assertEquals(new Version(2, "The version 2.0.0"), configBean.getVersions().get("v2")); - assertEquals(2, configBean.getVersionsDefault().size()); + assertEquals(3, configBean.getVersionsDefault().size()); assertEquals(new Version(1, "The version 1;2;3"), configBean.getVersionsDefault().get("v1=1;2;3")); assertEquals(new Version(2, "The version 2;1;0"), configBean.getVersionsDefault().get("v2=2;1;0")); assertEquals(1, configBean.getVersionDefault().size()); @@ -90,10 +90,12 @@ void inject() { assertEquals("5678", configBean.getMyPropProfile()); assertThrows(SecurityException.class, () -> configBean.getConfig().getValue("secret", String.class), "Not allowed to access secret key secret"); - assertEquals("1234", configBean.getConfig().getValue("my.prop", String.class)); assertEquals("1234", configBean.getSmallRyeConfig().getRawValue("my.prop")); assertEquals(HyphenatedEnum.A_B, configBean.getHyphenatedEnum()); + assertNull(configBean.getMissingExpression().getValue()); + assertFalse(configBean.getMissingExpressionOptional().isPresent()); + assertFalse(configBean.getMissingExpressionOptionalInt().isPresent()); } @Test @@ -195,6 +197,15 @@ static class ConfigBean { @Inject @ConfigProperty(name = "hyphenated.enum") HyphenatedEnum hyphenatedEnum; + @Inject + @ConfigProperty(name = "missing.expression") + ConfigValue missingExpression; + @Inject + @ConfigProperty(name = "missing.expression") + Optional missingExpressionOptional; + @Inject + @ConfigProperty(name = "missing.expression") + Optional missingExpressionOptionalInt; Optional> getReasonsOptional() { return reasonsOptional; @@ -283,6 +294,18 @@ Optional getConvertedValueOptional() { HyphenatedEnum getHyphenatedEnum() { return hyphenatedEnum; } + + ConfigValue getMissingExpression() { + return missingExpression; + } + + Optional getMissingExpressionOptional() { + return missingExpressionOptional; + } + + Optional getMissingExpressionOptionalInt() { + return missingExpressionOptionalInt; + } } @BeforeAll @@ -290,7 +313,7 @@ static void beforeAll() { SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("my.prop", "1234", "expansion", "${my.prop}", "secret", "12345678", "empty", "", "mp.config.profile", "prof", "my.prop.profile", "1234", "%prof.my.prop.profile", "5678", - "bad.property.expression.prop", "${missing.prop}", "reasons.200", "OK", "reasons.201", "Created", + "missing.expression", "${missing.prop}", "reasons.200", "OK", "reasons.201", "Created", "versions.v1", "1.The version 1.2.3", "versions.v1.2", "1.The version 1.2.0", "versions.v2", "2.The version 2.0.0", "lnums.even", "2,4,6,8", "lnums.odd", "1,3,5,7,9", diff --git a/cdi/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java index 0d2603789..417042181 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java @@ -8,8 +8,8 @@ import java.util.Optional; -import javax.enterprise.inject.spi.CDI; -import javax.inject.Inject; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; diff --git a/cdi/src/test/java/io/smallrye/config/inject/ConfigPropertiesInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/ConfigPropertiesInjectionTest.java index d049a169b..29722b9d2 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/ConfigPropertiesInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/ConfigPropertiesInjectionTest.java @@ -10,13 +10,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; -import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.spi.CDI; -import javax.inject.Inject; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import io.smallrye.config.ConfigMessages; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; @@ -113,9 +112,8 @@ void empty() { assertNull(config.getConfigValue("theHost").getValue()); assertNull(config.getConfigValue("port").getValue()); - assertThrows(NoSuchElementException.class, - () -> CDI.current().select(Server.class, ConfigProperties.Literal.of("")).get(), - () -> ConfigMessages.msg.mappingNotFound(Server.class.getName()).getMessage()); + assertThrows(UnsatisfiedResolutionException.class, + () -> CDI.current().select(Server.class, ConfigProperties.Literal.of("")).get()); } @Dependent diff --git a/cdi/src/test/java/io/smallrye/config/inject/IndexedPropertiesInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/IndexedPropertiesInjectionTest.java index 581ec2736..1cfa9f578 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/IndexedPropertiesInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/IndexedPropertiesInjectionTest.java @@ -15,8 +15,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; diff --git a/cdi/src/test/java/io/smallrye/config/inject/InjectedConfigSerializationTest.java b/cdi/src/test/java/io/smallrye/config/inject/InjectedConfigSerializationTest.java index d653f6786..b28b0adaf 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/InjectedConfigSerializationTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/InjectedConfigSerializationTest.java @@ -10,7 +10,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -73,7 +73,7 @@ void injectableConfigIsSerializable() { /** * Asserts that an object can be serialized and deserialized - * + * * @param o the object * @return the resulting object after serializing and deserializing */ diff --git a/cdi/src/test/java/io/smallrye/config/inject/MapInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/MapInjectionTest.java new file mode 100644 index 000000000..308f82f4e --- /dev/null +++ b/cdi/src/test/java/io/smallrye/config/inject/MapInjectionTest.java @@ -0,0 +1,151 @@ +package io.smallrye.config.inject; + +import static io.smallrye.config.inject.KeyValuesConfigSource.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +@ExtendWith(WeldJunit5Extension.class) +public class MapInjectionTest { + @WeldSetup + WeldInitiator weld = WeldInitiator.from(ConfigExtension.class, MapBean.class) + .addBeans() + .activate(ApplicationScoped.class) + .inject(this) + .build(); + + @Inject + MapBean mapBean; + + @Test + void map() { + assertEquals(3, mapBean.getMap().size()); + assertEquals("value", mapBean.getMap().get("key")); + assertEquals("value", mapBean.getMap().get("key.nested")); + assertEquals("value", mapBean.getMap().get("key.quoted")); + assertEquals("value", mapBean.getDefaults().get("default")); + assertEquals("value", mapBean.getConverted().get(new Key("key")).getValue()); + } + + @Test + void optionals() { + assertFalse(mapBean.getOptionalEmpty().isPresent()); + assertTrue(mapBean.getOptionalDefaults().isPresent()); + assertEquals("value", mapBean.getDefaults().get("default")); + } + + @ApplicationScoped + static class MapBean { + @Inject + @ConfigProperty(name = "map", defaultValue = "default=value") + Map map; + @Inject + @ConfigProperty(name = "map.defaults", defaultValue = "default=value") + Map defaults; + @Inject + @ConfigProperty(name = "converted") + Map converted; + @Inject + @ConfigProperty(name = "optionals.empty") + Optional> optionalEmpty; + @Inject + @ConfigProperty(name = "optionals.defaults", defaultValue = "default=value") + Optional> optionalDefaults; + + Map getMap() { + return map; + } + + Map getDefaults() { + return defaults; + } + + Map getConverted() { + return converted; + } + + Optional> getOptionalEmpty() { + return optionalEmpty; + } + + Optional> getOptionalDefaults() { + return optionalDefaults; + } + } + + @BeforeAll + static void beforeAll() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("map.key", "value", "map.key.nested", "value", "map.\"key.quoted\"", "value")) + .withSources(config("converted.key", "value")) + .build(); + ConfigProviderResolver.instance().registerConfig(config, Thread.currentThread().getContextClassLoader()); + } + + @AfterAll + static void afterAll() { + ConfigProviderResolver.instance().releaseConfig(ConfigProvider.getConfig()); + } + + static class Key { + private final String key; + + public Key(final String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final Key key1 = (Key) o; + return key.equals(key1.key); + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + } + + static class Value { + private final String value; + + public Value(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/cdi/src/test/java/io/smallrye/config/inject/OptionalInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/OptionalInjectionTest.java index ca4c5075d..51bdc29cd 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/OptionalInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/OptionalInjectionTest.java @@ -8,7 +8,7 @@ import java.util.OptionalInt; import java.util.OptionalLong; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; diff --git a/cdi/src/test/java/io/smallrye/config/inject/SupplierInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/SupplierInjectionTest.java index 08d154eb6..fe5bf5ec8 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/SupplierInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/SupplierInjectionTest.java @@ -10,8 +10,8 @@ import java.util.Set; import java.util.function.Supplier; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; diff --git a/cdi/src/test/java/io/smallrye/config/inject/ValidateInjectionTest.java b/cdi/src/test/java/io/smallrye/config/inject/ValidateInjectionTest.java index 0aed6ccec..b026d2def 100644 --- a/cdi/src/test/java/io/smallrye/config/inject/ValidateInjectionTest.java +++ b/cdi/src/test/java/io/smallrye/config/inject/ValidateInjectionTest.java @@ -15,10 +15,10 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperties; @@ -42,53 +42,53 @@ import org.junit.platform.testkit.engine.Event; import io.smallrye.config.ConfigMapping; -import io.smallrye.config.ConfigMessages; import io.smallrye.config.ConfigValidationException; import io.smallrye.config.ConfigValue; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config._private.ConfigMessages; /** * The Exception messages caused by Config CDI should have the format: - * + * * * {@link org.jboss.weld.exceptions.DeploymentException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * - * + * * If n Exception messages are thrown during - * {@link ConfigExtension#validate(javax.enterprise.inject.spi.AfterDeploymentValidation)}, + * {@link ConfigExtension#validate(jakarta.enterprise.inject.spi.AfterDeploymentValidation)}, * as defined by * {@link org.jboss.weld.exceptions.DeploymentException#DeploymentException(List)} * the messages will be bundled together as follows: - * + * * * {@link org.jboss.weld.exceptions.DeploymentException}: Exception List with n exceptions: * Exception 0 : - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * Exception n : - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> * - * + * * where each Exception is a supressedException. */ public class ValidateInjectionTest { @Test - void missingProperty() throws Exception { + void missingProperty() { DeploymentException exception = getDeploymentException(MissingPropertyTest.class); assertThat(exception).hasMessage( "SRCFG02000: Failed to Inject @ConfigProperty for key missing.property into io.smallrye.config.inject.ValidateInjectionTest$MissingPropertyTest$MissingPropertyBean.missingProp since the config property could not be found in any config source"); @@ -99,7 +99,7 @@ void missingProperty() throws Exception { } @Test - void emptyProperty() throws Exception { + void emptyProperty() { DeploymentException exception = getDeploymentException(EmptyPropertyTest.class); assertThat(exception) .hasMessageStartingWith( @@ -113,7 +113,7 @@ void emptyProperty() throws Exception { } @Test - void badProperty() throws Exception { + void badProperty() { DeploymentException exception = getDeploymentException(BadPropertyTest.class); assertThat(exception) .hasMessageStartingWith( @@ -138,7 +138,7 @@ void customConverterMissingProperty() { } @Test - void MissingConverter() { + void missingConverter() { DeploymentException exception = getDeploymentException(MissingConverterTest.class); assertThat(exception).hasMessageStartingWith( "SRCFG02001: Failed to Inject @ConfigProperty for key my.prop into io.smallrye.config.inject.ValidateInjectionTest$MissingConverterTest$MissingConverterBean.myProp SRCFG02007:"); @@ -232,15 +232,15 @@ void missingIndexedPropertiesInjection() { DeploymentException exception = getDeploymentException(MissingIndexedPropertiesInjectionTest.class); assertThat(exception).hasMessage( - "SRCFG02000: Failed to Inject @ConfigProperty for key missing.indexed[0] into io.smallrye.config.inject.ValidateInjectionTest$MissingIndexedPropertiesInjectionTest$MissingIndexedPropertiesBean.missingIndexedProp since the config property could not be found in any config source"); + "SRCFG02000: Failed to Inject @ConfigProperty for key missing.indexed into io.smallrye.config.inject.ValidateInjectionTest$MissingIndexedPropertiesInjectionTest$MissingIndexedPropertiesBean.missingIndexedProp since the config property could not be found in any config source"); assertThat(exception.getCause()).isInstanceOf(ConfigException.class); assertThat(exception.getCause()).hasMessage( - "SRCFG02000: Failed to Inject @ConfigProperty for key missing.indexed[0] into io.smallrye.config.inject.ValidateInjectionTest$MissingIndexedPropertiesInjectionTest$MissingIndexedPropertiesBean.missingIndexedProp since the config property could not be found in any config source"); + "SRCFG02000: Failed to Inject @ConfigProperty for key missing.indexed into io.smallrye.config.inject.ValidateInjectionTest$MissingIndexedPropertiesInjectionTest$MissingIndexedPropertiesBean.missingIndexedProp since the config property could not be found in any config source"); } @Test - void BadIndexedPropertiesInjection() { + void badIndexedPropertiesInjection() { DeploymentException exception = getDeploymentException(BadIndexedPropertiesInjectionTest.class); assertThat(exception) @@ -279,11 +279,11 @@ void manyInjectionExceptions() { void missingSubProperties() { DeploymentException exception = getDeploymentException(MissingSubPropertiesTest.class); assertThat(exception).hasMessageStartingWith( - "SRCFG02001: Failed to Inject @ConfigProperty for key missing.sub.properties into io.smallrye.config.inject.ValidateInjectionTest$MissingSubPropertiesTest$MissingSubPropertiesBean.missingSubProps SRCFG00014"); + "SRCFG02000: Failed to Inject @ConfigProperty for key missing.sub.properties into io.smallrye.config.inject.ValidateInjectionTest$MissingSubPropertiesTest$MissingSubPropertiesBean.missingSubProps since the config property could not be found in any config source"); assertThat(exception.getCause()).isInstanceOf(ConfigException.class); assertThat(exception.getCause()).hasMessageStartingWith( - "SRCFG02001: Failed to Inject @ConfigProperty for key missing.sub.properties into io.smallrye.config.inject.ValidateInjectionTest$MissingSubPropertiesTest$MissingSubPropertiesBean.missingSubProps SRCFG00014"); + "SRCFG02000: Failed to Inject @ConfigProperty for key missing.sub.properties into io.smallrye.config.inject.ValidateInjectionTest$MissingSubPropertiesTest$MissingSubPropertiesBean.missingSubProps since the config property could not be found in any config source"); } @Test @@ -317,7 +317,7 @@ private DeploymentException getDeploymentException(Class clazz) { Throwable exception = failingEvents.get(0) .getPayload(TestExecutionResult.class).get().getThrowable().get(); - assertThat(exception).isInstanceOf(DeploymentException.class); // the exception should be a DeploymentException + assertThat(exception).isInstanceOf(DeploymentException.class); // the exception should be a DeploymentException return (DeploymentException) exception; } @@ -344,7 +344,6 @@ static class MissingPropertyBean { @Inject @ConfigProperty(name = "missing.property") String missingProp; - } } @@ -442,7 +441,6 @@ void fail() { Assertions.fail(); } - @SuppressWarnings("serial") static class MyType implements Converter { @Override public MyType convert(String value) { @@ -617,7 +615,7 @@ void fail() { @ApplicationScoped static class MissingPropertyExpressionBean { @Inject - @ConfigProperty(name = "bad.property.expression.prop") // Exists but contains ${missing.prop} which doesn't + @ConfigProperty(name = "bad.property.expression.prop") // Exists but contains ${missing.prop} which doesn't String missingExpressionProp; } } @@ -642,8 +640,8 @@ void fail() { @ApplicationScoped static class MissingIndexedPropertiesBean { @Inject - @ConfigProperty(name = "missing.indexed[0]") // missing.indexed[0] doesn't exist - String missingIndexedProp; + @ConfigProperty(name = "missing.indexed") // missing.indexed doesn't exist + List missingIndexedProp; } } diff --git a/common/pom.xml b/common/pom.xml index ff8aacdb3..665dd71e1 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,12 +5,12 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config-common - SmallRye: Common classes + SmallRye Config: Common diff --git a/common/src/main/java/io/smallrye/config/common/utils/ConfigSourceUtil.java b/common/src/main/java/io/smallrye/config/common/utils/ConfigSourceUtil.java index 7cd47b53b..189051826 100644 --- a/common/src/main/java/io/smallrye/config/common/utils/ConfigSourceUtil.java +++ b/common/src/main/java/io/smallrye/config/common/utils/ConfigSourceUtil.java @@ -53,8 +53,22 @@ private ConfigSourceUtil() { public static Map propertiesToMap(Properties properties) { Map map = new HashMap<>(); synchronized (properties) { - for (Map.Entry e : properties.entrySet()) { - map.put(String.valueOf(e.getKey()), String.valueOf(e.getValue())); + for (Map.Entry entry : properties.entrySet()) { + String key; + try { + key = String.valueOf(entry.getKey()); + } catch (Exception e) { + continue; + } + + String value; + try { + value = String.valueOf(entry.getValue()); + } catch (Exception e) { + continue; + } + + map.put(key, value); } } return map; diff --git a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java index 37487f443..6faefd8f3 100644 --- a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java +++ b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package io.smallrye.config.common.utils; import java.util.ArrayList; @@ -88,17 +87,27 @@ public static String[] split(String text) { return list.toArray(NO_STRINGS); } + public static boolean isAsciiLetterOrDigit(char c) { + return 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z' || + '0' <= c && c <= '9'; + } + public static String replaceNonAlphanumericByUnderscores(final String name) { + return replaceNonAlphanumericByUnderscores(name, new StringBuilder(name.length())); + } + + public static String replaceNonAlphanumericByUnderscores(final String name, final StringBuilder sb) { int length = name.length(); - StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { char c = name.charAt(i); - if ('a' <= c && c <= 'z' || - 'A' <= c && c <= 'Z' || - '0' <= c && c <= '9') { + if (isAsciiLetterOrDigit(c)) { sb.append(c); } else { sb.append('_'); + if (c == '"' && i + 1 == length) { + sb.append('_'); + } } } return sb.toString(); @@ -106,63 +115,149 @@ public static String replaceNonAlphanumericByUnderscores(final String name) { public static String toLowerCaseAndDotted(final String name) { int length = name.length(); - int beginSegment = 0; + + if (length == 0) { + return name; + } + + byte[] result; + if (length > 1 && name.charAt(length - 1) == '_' && name.charAt(length - 2) == '_') { // last quoted segment + length--; + } + result = new byte[length]; + + int i = 0; + if (name.charAt(0) == '_') { + if (name.length() > 1 && isAsciiLetterOrDigit(name.charAt(1))) { // starting single _ is a profile + result[0] = '%'; + i++; + } + } + boolean quotesOpen = false; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { + for (; i < length; i++) { char c = name.charAt(i); if ('_' == c) { - if (i == 0) { - // leading _ can only mean a profile - sb.append("%"); - continue; - } - - // Do not convert to index if the first segment is a number - if (beginSegment > 0) { - try { - String segment = sb.substring(beginSegment, i); - Integer.parseInt(segment); - sb.replace(beginSegment - 1, beginSegment, "[").append("]"); - - int j = i + 1; - if (j < length) { - if ('_' == name.charAt(j)) { - sb.append("."); + int next = i + 1; + if (quotesOpen) { + if (next == length) { + result[i] = '"'; // ending quotes + } else if (name.charAt(next) == '_') { // double _ end quote + result[i] = '"'; + result[next] = '.'; + i++; + quotesOpen = false; + } else { + result[i] = '.'; + } + } else if (next < length) { + char d = name.charAt(next); + if (Character.isDigit(d)) { // maybe index + result[next] = (byte) d; + int j = next + 1; + for (; j < length; j++) { + d = name.charAt(j); + if (Character.isDigit(d)) { // index + result[j] = (byte) d; + } else if ('_' == d) { // ending index + result[i] = '['; + result[j] = ']'; i = j; + break; + } else { // not an index + result[i] = '.'; + break; } } - continue; - } catch (NumberFormatException e) { - // Ignore, it is not an indexed number - } - } - - int j = i + 1; - if (j < length) { - if ('_' == name.charAt(j) && !quotesOpen) { - sb.append("."); - sb.append("\""); - i = j; + } else if (name.charAt(next) == '_') { // double _ start quote + result[i] = '.'; + result[next] = '"'; + i++; quotesOpen = true; - } else if ('_' == name.charAt(j) && quotesOpen) { - sb.append("\""); - sb.append("."); - i = j; - quotesOpen = false; } else { - sb.append("."); + result[i] = '.'; } } else { - sb.append("."); + result[i] = '.'; } - beginSegment = j; } else { - sb.append(Character.toLowerCase(c)); + result[i] = (byte) Character.toLowerCase(c); } } - return sb.toString(); + + // https://bugs.openjdk.org/browse/JDK-8295496 + return new String(result, 0, 0, result.length); + } + + public static boolean isNumeric(final CharSequence digits) { + return isNumeric(digits, 0, digits.length()); + } + + public static boolean isNumeric(final CharSequence digits, final int begin, final int end) { + if (digits.length() == 0) { + return false; + } + + if (begin == end) { + return false; + } + + for (int i = begin; i < end; i++) { + if (!Character.isDigit(digits.charAt(i))) { + return false; + } + } + return true; + } + + public static String unquoted(final String name) { + return unquoted(name, 0); + } + + public static String unquoted(final String name, final int begin) { + return unquoted(name, begin, name.length()); + } + + public static String unquoted(final String name, final int begin, final int end) { + if (begin < 0 || begin > end || end > name.length()) { + throw new StringIndexOutOfBoundsException("begin " + begin + ", end " + end + ", length " + name.length()); + } + + if (name.length() < 2 || name.length() <= begin) { + return name; + } + + if (name.charAt(begin) == '"' && name.charAt(end - 1) == '"') { + return name.substring(begin + 1, end - 1); + } else { + return name.substring(begin, end); + } + } + + public static int index(final String name) { + if (name.charAt(name.length() - 1) == ']') { + int start = name.lastIndexOf('['); + if (start != -1 && isNumeric(name, start + 1, name.length() - 1)) { + return Integer.parseInt(name.substring(start + 1, name.length() - 1)); + } + } + throw new IllegalArgumentException(); + } + + public static String unindexed(final String name) { + if (name.length() < 3) { + return name; + } + + if (name.charAt(name.length() - 1) == ']') { + int begin = name.lastIndexOf('['); + if (begin != -1 && isNumeric(name, begin + 1, name.length() - 1)) { + return name.substring(0, begin); + } + } + + return name; } public static String skewer(String camelHumps) { @@ -170,77 +265,64 @@ public static String skewer(String camelHumps) { } public static String skewer(String camelHumps, char separator) { - return skewer(camelHumps, 0, camelHumps.length(), new StringBuilder(), separator); - } - - private static String skewer(String camelHumps, int start, int end, StringBuilder b, char separator) { if (camelHumps.isEmpty()) { - throw new IllegalArgumentException("Method seems to have an empty name"); - } - int cp = camelHumps.codePointAt(start); - b.appendCodePoint(Character.toLowerCase(cp)); - start += Character.charCount(cp); - if (start == end) { - // a lonely character at the end of the string - return b.toString(); - } - if (Character.isUpperCase(cp)) { - // all-uppercase words need one code point of lookahead - int nextCp = camelHumps.codePointAt(start); - if (Character.isUpperCase(nextCp)) { - // it's some kind of `WORD` - for (;;) { - b.appendCodePoint(Character.toLowerCase(nextCp)); - start += Character.charCount(cp); - cp = nextCp; - if (start == end) { - return b.toString(); - } - nextCp = camelHumps.codePointAt(start); - // combine non-letters in with this name - if (Character.isLowerCase(nextCp)) { + return camelHumps; + } + + int end = camelHumps.length(); + StringBuilder b = new StringBuilder(); + + for (int i = 0; i < end; i++) { + char c = camelHumps.charAt(i); + if (Character.isLowerCase(c)) { + b.append(c); + } else if (Character.isUpperCase(c)) { + if (i > 0) { + char last = camelHumps.charAt(i - 1); + if (last != '_' && last != '-') { b.append(separator); - return skewer(camelHumps, start, end, b, separator); } } - // unreachable - } else { - // it was the start of a `Word`; continue until we hit the end or an uppercase. - b.appendCodePoint(nextCp); - start += Character.charCount(nextCp); - for (;;) { - if (start == end) { - return b.toString(); + b.append(Character.toLowerCase(c)); + // lookahead for all upper case words, like fooBAR transform to foo_bar and not foo_b_a_r + // move caret by 1 + int j = i + 1; + for (; j < end; j++) { + char u = camelHumps.charAt(j); + if (Character.isUpperCase(u)) { + b.append(Character.toLowerCase(u)); + } else if (Character.isDigit(u) || u == '-') { + // A digit in the middle will break the all upper case word, the main cycle can resume + b.append(u); + } else { + // it is an all upper case word if j > i + 1, the initial value + if (j > i + 1 && u != '_' && !Character.isDigit(b.charAt(b.length() - 1))) { + // all upper case word done, but last upper starts a new word, so we need to insert the separator + b.insert(b.length() - 1, separator); + } + // we don't know what is coming next, so we go back and let the main cycle handle it + j--; + break; } - cp = camelHumps.codePointAt(start); - // combine non-letters in with this name - if (Character.isUpperCase(cp)) { + } + // restore caret + i = j; + } else if (Character.isDigit(c)) { + b.append(c); + } else if (c == '.' || c == '*' || c == '[' || c == ']') { + b.append(c); + } else { + if (i > 0) { + char last = camelHumps.charAt(i - 1); + if (last != '_' && last != '-') { b.append(separator); - return skewer(camelHumps, start, end, b, separator); } - b.appendCodePoint(cp); - start += Character.charCount(cp); - } - // unreachable - } - // unreachable - } else { - // it's some kind of `word` - for (;;) { - cp = camelHumps.codePointAt(start); - // combine non-letters in with this name - if (Character.isUpperCase(cp)) { - b.append(separator); - return skewer(camelHumps, start, end, b, separator); - } - b.appendCodePoint(cp); - start += Character.charCount(cp); - if (start == end) { - return b.toString(); + } else { + b.append(c); } } - // unreachable } - // unreachable + + return b.toString(); } } diff --git a/common/src/test/java/io/smallrye/config/common/utils/ConfigSourceUtilTest.java b/common/src/test/java/io/smallrye/config/common/utils/ConfigSourceUtilTest.java index 719553dbe..2b52e1dc8 100644 --- a/common/src/test/java/io/smallrye/config/common/utils/ConfigSourceUtilTest.java +++ b/common/src/test/java/io/smallrye/config/common/utils/ConfigSourceUtilTest.java @@ -16,6 +16,7 @@ package io.smallrye.config.common.utils; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Map; import java.util.Properties; @@ -35,4 +36,20 @@ void propertiesToMap() { assertEquals("my.value2", map.get("my.key2")); assertEquals("2", map.get("my.key3")); } + + @Test + void unableToConvertToString() { + Properties properties = new Properties(); + properties.put("foo.bar", new UnconvertableString()); + + Map map = ConfigSourceUtil.propertiesToMap(properties); + assertTrue(map.isEmpty()); + } + + private static class UnconvertableString { + @Override + public String toString() { + throw new UnsupportedOperationException(); + } + } } diff --git a/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java b/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java index 5a7d51262..6aca90dfe 100644 --- a/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java +++ b/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java @@ -16,7 +16,8 @@ package io.smallrye.config.common.utils; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -96,19 +97,131 @@ void escapingSituations() { @Test void skewer() { - assertThrows(IllegalArgumentException.class, () -> StringUtil.skewer("")); - assertThrows(IllegalArgumentException.class, () -> StringUtil.skewer("", '.')); + assertEquals("sigusr1", StringUtil.skewer("sigusr1")); + + assertEquals("", StringUtil.skewer("")); + assertEquals(".", StringUtil.skewer(".")); assertEquals("my-property", StringUtil.skewer("myProperty")); assertEquals("my.property", StringUtil.skewer("myProperty", '.')); + assertEquals("-", StringUtil.skewer("-")); + assertEquals("_", StringUtil.skewer("_")); assertEquals("a", StringUtil.skewer("a")); + assertEquals("a", StringUtil.skewer("A")); assertEquals("a", StringUtil.skewer("a", '.')); + assertEquals("a-b", StringUtil.skewer("a-b")); + assertEquals("_a", StringUtil.skewer("_a")); + assertEquals("_a-b", StringUtil.skewer("_a_b")); assertEquals("my-property-abc", StringUtil.skewer("myPropertyABC")); assertEquals("my.property.abc", StringUtil.skewer("myPropertyABC", '.')); - assertEquals("my-property-abc-abc", StringUtil.skewer("myPropertyABCabc")); - assertEquals("my.property.abc.abc", StringUtil.skewer("myPropertyABCabc", '.')); + assertEquals("my-property-ab-cabc", StringUtil.skewer("myPropertyABCabc")); + assertEquals("my.property.ab.cabc", StringUtil.skewer("myPropertyABCabc", '.')); + + assertEquals("is-same-rm-override", StringUtil.skewer("isSameRMOverride")); + assertEquals("http-client-http-conduit-factory", StringUtil.skewer("HttpClientHTTPConduitFactory")); + assertEquals("url-connection-http-conduit-factory", StringUtil.skewer("URLConnectionHTTPConduitFactory")); + assertEquals("abc-default", StringUtil.skewer("ABCDefault")); + assertEquals("abc", StringUtil.skewer("ABC")); + + assertEquals("discard", StringUtil.skewer("discard")); + assertEquals("a-b", StringUtil.skewer("A_B")); + assertEquals("read-uncommitted", StringUtil.skewer("READ_UNCOMMITTED")); + assertEquals("_read-uncommitted", StringUtil.skewer("_READ_UNCOMMITTED")); + assertEquals("read-uncommitted", StringUtil.skewer("READ__UNCOMMITTED")); + assertEquals("_read-uncommitted", StringUtil.skewer("_READ__UNCOMMITTED")); + assertEquals("sigusr1", StringUtil.skewer("SIGUSR1")); + assertEquals("sigusr1", StringUtil.skewer("sigusr1")); + assertEquals("trend-breaker", StringUtil.skewer("TrendBreaker")); + assertEquals("making-life-difficult", StringUtil.skewer("MAKING_LifeDifficult")); + assertEquals("making-life-difficult", StringUtil.skewer("makingLifeDifficult")); + + assertEquals("foo.bar", StringUtil.skewer("foo.bar")); + assertEquals("foo.bar-baz", StringUtil.skewer("foo.barBaz")); + assertEquals("foo.bar-baz[0]", StringUtil.skewer("foo.barBaz[0]")); + assertEquals("foo.bar-baz[*]", StringUtil.skewer("foo.barBaz[*]")); + + assertEquals("across-b2b", StringUtil.skewer("acrossB2b")); + assertEquals("across-b22b", StringUtil.skewer("acrossB22b")); + assertEquals("across-b22b", StringUtil.skewer("acrossB22B")); + assertEquals("across-b22bb", StringUtil.skewer("acrossB22BB")); + assertEquals("across-b22b-bb", StringUtil.skewer("acrossB22BBb")); + } + + @Test + void replaceNonAlphanumericByUnderscores() { + assertEquals("TEST_LANGUAGE__DE_ETR__", + StringUtil.replaceNonAlphanumericByUnderscores("test.language.\"de.etr\"".toUpperCase())); + } + + @Test + void replaceNonAlphanumericByUnderscoresWithStringBuilder() { + StringBuilder builder = new StringBuilder(); + builder.setLength(0); + assertEquals("FOO_BAR", StringUtil.replaceNonAlphanumericByUnderscores("foo.bar", builder).toUpperCase()); + builder.setLength(0); + assertEquals("FOO_BAR_BAZ", StringUtil.replaceNonAlphanumericByUnderscores("foo.bar.baz", builder).toUpperCase()); + builder.setLength(0); + assertEquals("FOO", StringUtil.replaceNonAlphanumericByUnderscores("foo", builder).toUpperCase()); + builder.setLength(0); + assertEquals("TEST_LANGUAGE__DE_ETR__", + StringUtil.replaceNonAlphanumericByUnderscores("test.language.\"de.etr\"".toUpperCase())); + } + + @Test + void toLowerCaseAndDotted() { + assertEquals("", StringUtil.toLowerCaseAndDotted("")); + assertEquals("f", StringUtil.toLowerCaseAndDotted("F")); + assertEquals("foo", StringUtil.toLowerCaseAndDotted("FOO")); + assertEquals("foo.bar", StringUtil.toLowerCaseAndDotted("FOO_BAR")); + assertEquals("foo.bar.baz", StringUtil.toLowerCaseAndDotted("FOO_BAR_BAZ")); + assertEquals("foo.bar.baz2", StringUtil.toLowerCaseAndDotted("FOO_BAR_BAZ2")); + assertEquals("foo.bar.2baz", StringUtil.toLowerCaseAndDotted("FOO_BAR_2BAZ")); + assertEquals("foo.bar.baz20", StringUtil.toLowerCaseAndDotted("FOO_BAR_BAZ20")); + assertEquals("foo.bar.20baz", StringUtil.toLowerCaseAndDotted("FOO_BAR_20BAZ")); + assertEquals("foo.bar.baz[20]", StringUtil.toLowerCaseAndDotted("FOO_BAR_BAZ_20_")); + assertEquals("foo.bar.\"baz\".i[20].e", StringUtil.toLowerCaseAndDotted("FOO_BAR__BAZ__I_20__E")); + assertEquals("test.language.\"de.etr\"", StringUtil.toLowerCaseAndDotted("TEST_LANGUAGE__DE_ETR__")); + assertEquals("%foo.bar", StringUtil.toLowerCaseAndDotted("_FOO_BAR")); + assertEquals(".\"foo.bar", StringUtil.toLowerCaseAndDotted("__FOO_BAR")); + } + + @Test + void isNumeric() { + assertTrue(StringUtil.isNumeric("0")); + assertFalse(StringUtil.isNumeric("false")); + assertTrue(StringUtil.isNumeric("foo[0]", 4, 5)); + assertTrue(StringUtil.isNumeric(new StringBuilder("foo[0]"), 4, 5)); + } + + @Test + void unquoted() { + assertEquals("", StringUtil.unquoted("")); + assertEquals("", StringUtil.unquoted("\"\"")); + assertEquals("a", StringUtil.unquoted("a")); + assertEquals("unquoted", StringUtil.unquoted("\"unquoted\"")); + assertEquals("my.\"unquoted\"", StringUtil.unquoted("my.\"unquoted\"")); + assertEquals("unquoted", StringUtil.unquoted("my.\"unquoted\"", 3, 13)); + assertEquals("unquoted", StringUtil.unquoted("my.unquoted", 3, 11)); + } + + @Test + void index() { + assertEquals(0, StringUtil.index("foo[0]")); + } + + @Test + void unIndexed() { + assertEquals("", StringUtil.unindexed("")); + assertEquals("[]", StringUtil.unindexed("[]")); + assertEquals("", StringUtil.unindexed("[0]")); + assertEquals("", StringUtil.unindexed("[999]")); + assertEquals("[abc]", StringUtil.unindexed("[abc]")); + assertEquals("my.prop", StringUtil.unindexed("my.prop[0]")); + assertEquals("my.prop[]", StringUtil.unindexed("my.prop[]")); + assertEquals("my.prop[", StringUtil.unindexed("my.prop[")); + assertEquals("my.prop]", StringUtil.unindexed("my.prop]")); } } diff --git a/converters/json/pom.xml b/converters/json/pom.xml index 9efd03040..7c6b03f0a 100644 --- a/converters/json/pom.xml +++ b/converters/json/pom.xml @@ -5,13 +5,16 @@ io.smallrye.config smallrye-config-parent - 2.11.1 - ../../ + 3.7.1 + ../../pom.xml smallrye-config-converter-json - - SmallRye: MicroProfile Config Converter - Json + SmallRye Config: Converter - Json + + + 1.1.5 + @@ -36,8 +39,9 @@ test - org.glassfish - jakarta.json + org.eclipse.parsson + parsson + ${version.parsson} test diff --git a/converters/json/src/main/java/io/smallrye/config/converter/json/JsonArrayConverter.java b/converters/json/src/main/java/io/smallrye/config/converter/json/JsonArrayConverter.java index 458e808ac..81b6e3899 100644 --- a/converters/json/src/main/java/io/smallrye/config/converter/json/JsonArrayConverter.java +++ b/converters/json/src/main/java/io/smallrye/config/converter/json/JsonArrayConverter.java @@ -2,15 +2,15 @@ import java.io.StringReader; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonReader; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonReader; import org.eclipse.microprofile.config.spi.Converter; /** * Converts a json string to a JSonArray - * + * * @author Phillip Kruger */ public class JsonArrayConverter implements Converter { diff --git a/converters/json/src/main/java/io/smallrye/config/converter/json/JsonObjectConverter.java b/converters/json/src/main/java/io/smallrye/config/converter/json/JsonObjectConverter.java index 3584c4bf7..ee71b50fc 100644 --- a/converters/json/src/main/java/io/smallrye/config/converter/json/JsonObjectConverter.java +++ b/converters/json/src/main/java/io/smallrye/config/converter/json/JsonObjectConverter.java @@ -2,15 +2,15 @@ import java.io.StringReader; -import javax.json.Json; -import javax.json.JsonObject; -import javax.json.JsonReader; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; import org.eclipse.microprofile.config.spi.Converter; /** * Converts a json string to a JSonObject - * + * * @author Phillip Kruger */ public class JsonObjectConverter implements Converter { diff --git a/converters/json/src/test/java/io/smallrye/config/converter/json/JsonArrayConverterTest.java b/converters/json/src/test/java/io/smallrye/config/converter/json/JsonArrayConverterTest.java index ced868d84..a6dbf5de4 100644 --- a/converters/json/src/test/java/io/smallrye/config/converter/json/JsonArrayConverterTest.java +++ b/converters/json/src/test/java/io/smallrye/config/converter/json/JsonArrayConverterTest.java @@ -4,11 +4,11 @@ import java.io.StringReader; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonReader; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonReader; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.weld.junit5.WeldInitiator; @@ -21,7 +21,7 @@ /** * Testing the injection of a JsonArray - * + * * @author Phillip Kruger */ @ExtendWith(WeldJunit5Extension.class) diff --git a/converters/json/src/test/java/io/smallrye/config/converter/json/JsonObjectConverterTest.java b/converters/json/src/test/java/io/smallrye/config/converter/json/JsonObjectConverterTest.java index 36b6bcaf4..d81817544 100644 --- a/converters/json/src/test/java/io/smallrye/config/converter/json/JsonObjectConverterTest.java +++ b/converters/json/src/test/java/io/smallrye/config/converter/json/JsonObjectConverterTest.java @@ -4,11 +4,11 @@ import java.io.StringReader; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonObject; -import javax.json.JsonReader; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.weld.junit5.WeldInitiator; @@ -21,7 +21,7 @@ /** * Testing the injection of a JsonObject - * + * * @author Phillip Kruger */ @ExtendWith(WeldJunit5Extension.class) diff --git a/coverage/pom.xml b/coverage/pom.xml index 003d09785..fd15b9c42 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -5,7 +5,7 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config-coverage @@ -53,6 +53,23 @@ smallrye-config-source-yaml ${project.version} + + io.smallrye.config + smallrye-config-source-keystore + ${project.version} + + + + + io.smallrye.config + smallrye-config-crypto + ${project.version} + + + io.smallrye.config + smallrye-config-jasypt + ${project.version} + diff --git a/documentation/Pipfile.lock b/documentation/Pipfile.lock index 2930c435b..6486729ca 100644 --- a/documentation/Pipfile.lock +++ b/documentation/Pipfile.lock @@ -33,73 +33,94 @@ }, "importlib-metadata": { "hashes": [ - "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", - "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" + "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed", + "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705" ], "markers": "python_version >= '3.7'", - "version": "==4.12.0" + "version": "==6.6.0" }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", + "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" ], + "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.3" }, "markdown": { "hashes": [ - "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874", - "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621" + "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2", + "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520" ], - "markers": "python_version >= '3.6'", - "version": "==3.3.7" + "markers": "python_version >= '3.7'", + "version": "==3.4.3" }, "markupsafe": { "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", - "version": "==2.1.1" + "version": "==2.1.3" }, "mergedeep": { "hashes": [ @@ -143,43 +164,35 @@ }, "mkdocs-material-extensions": { "hashes": [ - "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44", - "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2" + "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93", + "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945" ], - "markers": "python_version >= '3.6'", - "version": "==1.0.3" + "markers": "python_version >= '3.7'", + "version": "==1.1.1" }, "packaging": { "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], - "markers": "python_version >= '3.6'", - "version": "==21.3" + "markers": "python_version >= '3.7'", + "version": "==23.1" }, "pygments": { "hashes": [ - "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", - "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" - ], - "markers": "python_version >= '3.6'", - "version": "==2.12.0" - }, - "pymdown-extensions": { - "hashes": [ - "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0", - "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4" + "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", + "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" ], "markers": "python_version >= '3.7'", - "version": "==9.5" + "version": "==2.15.1" }, - "pyparsing": { + "pymdown-extensions": { "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + "sha256:9a77955e63528c2ee98073a1fb3207c1a45607bc74a34ef21acd098f46c3aa8a", + "sha256:e6cbe8ace7d8feda30bc4fd6a21a073893a9a0e90c373e92d69ce5b653051f55" ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" + "index": "pypi", + "version": "==10.0" }, "python-dateutil": { "hashes": [ @@ -191,6 +204,7 @@ }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -202,26 +216,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -246,9 +266,11 @@ }, "termcolor": { "hashes": [ - "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b" + "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475", + "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a" ], - "version": "==1.1.0" + "markers": "python_version >= '3.7'", + "version": "==2.3.0" }, "verspec": { "hashes": [ @@ -259,42 +281,44 @@ }, "watchdog": { "hashes": [ - "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412", - "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654", - "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306", - "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33", - "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd", - "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7", - "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892", - "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609", - "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6", - "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1", - "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591", - "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d", - "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d", - "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c", - "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3", - "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39", - "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213", - "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330", - "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428", - "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1", - "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846", - "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153", - "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3", - "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9", - "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658" + "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a", + "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100", + "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8", + "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc", + "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae", + "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41", + "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0", + "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f", + "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c", + "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9", + "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3", + "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709", + "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83", + "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759", + "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9", + "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3", + "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7", + "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f", + "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346", + "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674", + "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397", + "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96", + "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d", + "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a", + "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64", + "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44", + "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33" ], - "markers": "python_version >= '3.6'", - "version": "==2.1.9" + "markers": "python_version >= '3.7'", + "version": "==3.0.0" }, "zipp": { "hashes": [ - "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", - "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" + "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", + "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" ], "markers": "python_version >= '3.7'", - "version": "==3.8.0" + "version": "==3.15.0" } }, "develop": {} diff --git a/documentation/mkdocs.yaml b/documentation/mkdocs.yaml index 49eb791c1..bb1f8509f 100644 --- a/documentation/mkdocs.yaml +++ b/documentation/mkdocs.yaml @@ -18,15 +18,18 @@ nav: - 'Mappings': config/mappings.md - 'Map Support': config/map-support.md - 'Secret Keys': config/secret-keys.md + - 'Customizer': config/customizer.md - 'Configuration Reference': config/configuration.md - Config Sources: - 'Custom': config-sources/custom.md - 'Factories': config-sources/factories.md - 'Locations': config-sources/locations.md - 'YAML': config-sources/yaml.md + - 'JSON': config-sources/json.md - 'FileSystem': config-sources/filesystem.md - 'ZooKeeper': config-sources/zookeeper.md - 'HOCON': config-sources/hocon.md + - 'KeyStore': config-sources/keystore.md - Converters: - 'Custom': converters/custom.md - 'JSON': converters/json.md @@ -87,6 +90,7 @@ theme: markdown_extensions: - toc: + toc_depth: 3 permalink: '#' - admonition - smarty diff --git a/documentation/pom.xml b/documentation/pom.xml index 43c634da4..997faea78 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -5,7 +5,7 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config-documentation @@ -18,7 +18,6 @@ org.apache.maven.plugins maven-resources-plugin - 3.3.0 update-attributes diff --git a/documentation/src/main/docs/assets/extra.css b/documentation/src/main/docs/assets/extra.css index f646e6037..9f8196c18 100644 --- a/documentation/src/main/docs/assets/extra.css +++ b/documentation/src/main/docs/assets/extra.css @@ -1 +1,9 @@ /* Add stuff here if required */ + +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/documentation/src/main/docs/config-sources/custom.md b/documentation/src/main/docs/config-sources/custom.md index ec0595d91..3d8d6852f 100644 --- a/documentation/src/main/docs/config-sources/custom.md +++ b/documentation/src/main/docs/config-sources/custom.md @@ -55,8 +55,7 @@ public class InMemoryConfigSource implements ConfigSource { And registration in: -_META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource_ -```properties +```properties title="META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource" org.acme.config.InMemoryConfigSource ``` @@ -113,8 +112,7 @@ public class URLConfigSourceFactory implements ConfigSourceFactory { And registration in: -_META-INF/services/io.smallrye.config.ConfigSourceFactory_ -```properties +```properties title="META-INF/services/io.smallrye.config.ConfigSourceFactory" org.acme.config.URLConfigSourceFactory ``` diff --git a/documentation/src/main/docs/config-sources/factories.md b/documentation/src/main/docs/config-sources/factories.md index 6afed7b75..3597bc84f 100644 --- a/documentation/src/main/docs/config-sources/factories.md +++ b/documentation/src/main/docs/config-sources/factories.md @@ -41,7 +41,7 @@ import java.util.List; import org.eclipse.microprofile.config.spi.ConfigSource; -import io.smallrye.config.ConfigMessages; +import io.smallrye.config._private.ConfigMessages; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.ConfigSourceContext; import io.smallrye.config.ConfigSourceFactory; @@ -73,10 +73,31 @@ public class FileSystemConfigSourceFactory implements ConfigSourceFactory { ``` And registration in: -_META-INF/services/io.smallrye.config.ConfigSourceFactory_ -```properties + +```properties title="META-INF/services/io.smallrye.config.ConfigSourceFactory" org.acme.config.FileSystemConfigSourceFactory ``` The `FileSystemConfigSourceFactory` look ups the configuration value for `org.acme.config.file.locations`, and uses it to set up an additional `ConfigSource`. + +Alternatively, a `ConfigurableConfigSourceFactory` accepts a `ConfigMapping` interface to configure the `ConfigSource`: + +```java +@ConfigMapping(prefix = "org.acme.config.file") +interface FileSystemConfig { + List locations(); +} +``` + +```java +public class FileSystemConfigurableConfigSourceFactory implements ConfigurableConfigSourceFactory { + @Override + public Iterable getConfigSources(ConfigSourceContext context, FileSystemConfig config) { + + } +} +``` + +With a `ConfigurableConfigSourceFactory` it is not required to look up the configuration values with +`ConfigSourceContext`. The values are automatically mapped with the defined `@ConfigMapping`. diff --git a/documentation/src/main/docs/config-sources/json.md b/documentation/src/main/docs/config-sources/json.md new file mode 100644 index 000000000..53a0e88e5 --- /dev/null +++ b/documentation/src/main/docs/config-sources/json.md @@ -0,0 +1,4 @@ +# JSON Config Source + +The [YAML Config Source](yaml.md) also accepts the JSON format as its contents. The configuration file +`META-INF/microprofile-config.yaml`, still requires to use the `yaml` extension. diff --git a/documentation/src/main/docs/config-sources/keystore.md b/documentation/src/main/docs/config-sources/keystore.md new file mode 100644 index 000000000..62c9f207e --- /dev/null +++ b/documentation/src/main/docs/config-sources/keystore.md @@ -0,0 +1,53 @@ +# KeyStore Config Source + +This Config Source allows to use a Java `KeyStore` to load configuration values. It uses an ordinal of `100`. + +The following dependency is required in the classpath to use the KeyStore Config Source: + +```xml + + io.smallrye.config + smallrye-config-source-keystore + {{attributes['version']}} + +``` + +## Create a KeyStore + +The following command creates a simple KeyStore + +```bash +keytool -importpass -alias my.secret -keystore keystore -storepass secret -storetype PKCS12 -v +``` + +The `-alias my.secret` stores the configuration property name `my.secret` in the KeyStore. The command will +interactively ask for the value to be stored in the KeyStore. + +## Read the KeyStore + +The KeyStore Config Source supports reading multiple keystore files: + +```properties +smallrye.config.source.keystore.one.path=keystore-one +smallrye.config.source.keystore.one.password=password + +smallrye.config.source.keystore.two.path=keystore-two +smallrye.config.source.keystore.two.password=password +``` + +The names are arbitrary and can be any name. The name `one` and `two` are used to distinguish both KeyStores. + +If a stored configuration property requires a [Secret Handler](../config/secret-keys.md) to decode a value, set +the handler name with `smallrye.config.source.keystore."name".handler`. + +## Configuration + +| Configuration Property | Type | Default | +|---------------------------------------------------------------------------------------------------------------------|--- |----| +| `smallrye.config.source.keystore."name".path`The KeyStore path. | String | | +| `smallrye.config.source.keystore."name".password`The KeyStore password. | String | | +| `smallrye.config.source.keystore."name".type`The KeyStore type. | String | `PKCS12` | +| `smallrye.config.source.keystore."name".handler`An Optional secret keys handler. | String | | +| `smallrye.config.source.keystore."name".aliases."key".name`An Optional aliases key name. | String | | +| `smallrye.config.source.keystore."name".aliases."key".password`An Optional aliases key password. | String | | +| `smallrye.config.source.keystore."name".aliases."key".handler`An Optional aliases key secret keys handler. | String | | diff --git a/documentation/src/main/docs/config-sources/locations.md b/documentation/src/main/docs/config-sources/locations.md index 25fbcc06e..684ee7323 100644 --- a/documentation/src/main/docs/config-sources/locations.md +++ b/documentation/src/main/docs/config-sources/locations.md @@ -40,7 +40,7 @@ For relative paths, the JVM `user.dir` property defines the current directory. _A specific file_ -```properties +```properties title="./src/main/resources/additional.properties" smallrye.config.locations=./src/main/resources/additional.properties ``` @@ -48,7 +48,7 @@ If a profile `dev` is active, and an `additional-dev.properties` file exists, th _All `additional.properties` files from the classpath_ -```properties +```properties title="additional.properties" smallrye.config.locations=additional.properties ``` @@ -57,14 +57,14 @@ resource, this will also be loaded. _The `resources.properties` file from a specific jar_ -```properties +```properties title="jar:file:///user/local/app/lib/resources-.jar!/resources.properties" smallrye.config.locations=jar:file:///user/local/app/lib/resources-.jar!/resources.properties ``` -If a profile `test` is active, and an `additional-test.properties` respource exists, this will also be loaded. +If a profile `test` is active, and an `additional-test.properties` resource exists, this will also be loaded. _The `config.properties` file from a web server_ -```properties -smallrye.config.locations=http://localhost:8080/config/config. +```properties title="http://localhost:8080/config/config.properties" +smallrye.config.locations=http://localhost:8080/config/config.properties ``` diff --git a/documentation/src/main/docs/config/configuration.md b/documentation/src/main/docs/config/configuration.md index 2359424fd..29222112f 100644 --- a/documentation/src/main/docs/config/configuration.md +++ b/documentation/src/main/docs/config/configuration.md @@ -1,33 +1,9 @@ # Configuration Reference -## `smallrye.config.profile` - -The main [Profile](profiles.md) to activate. - -> * type: String[] -> * default: - -## `smallrye.config.profile.parent` - -The parent [Profile](profiles.md#parent-profile) to activate. - -> * type: String[] -> * default: - -## `smallrye.config.locations` - -[Additional config locations](../config-sources/locations.md) to be loaded with the Config. The configuration supports -multiple locations separated by a comma and each must represent a valid `java.net.URI`. - -> * type: URI[] -> * default: - -## `smallrye.config.mapping.validate-unknown` - -[Validates](mappings.md#retrieval) that a `@ConfigMapping` maps every available configuration name contained in the -mapping prefix. - -> * type: boolean -> * default: false - - +| Configuration Property | Type | Default | +|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|-----------| +| `smallrye.config.profile`The main [Profile](profiles.md) to activate. | String[] | | +| `smallrye.config.profile.parent`The parent [Profile](profiles.md#parent-profile) to activate. | String | | +| `smallrye.config.locations`[Additional config locations](../config-sources/locations.md) to be loaded with the Config. The configuration supports multiple locations separated by a comma and each must represent a valid `java.net.URI`. | URI[] | | +| `smallrye.config.mapping.validate-unknown`[Validates](mappings.md#retrieval) that a `@ConfigMapping` maps every available configuration name contained in the mapping prefix. | boolean | false | +| `smallrye.config.log.values`Enable logging of configuration values lookup in DEBUG log level. | boolean | false | diff --git a/documentation/src/main/docs/config/customizer.md b/documentation/src/main/docs/config/customizer.md new file mode 100644 index 000000000..55fa78b36 --- /dev/null +++ b/documentation/src/main/docs/config/customizer.md @@ -0,0 +1,43 @@ +# Customizer + +A `SmallRyeConfigBuilderCustomizer` allows to customize a `SmallRyeConfigBuilder`, used to create the `SmallRyeConfig` +instance. + +Registration of a `SmallRyeConfigBuilderCustomizer` is done via the `ServiceLoader` mechanism by providing the +implementation classes in a `META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer` file. Alternatively, +customizers may be registered via the Programmatic API in `SmallRyeConfigBuilder#withCustomizers`. + +The `SmallRyeConfigBuilderCustomizer` may also +assign a priority by overriding the default method `int priority()`. Customizers are sorted by ascending priority and +executed in that order, meaning that higher numeric priorities will execute last, possible overriding values set by +previous customizers. + +```java title="CustomConfigBuilder" +package org.acme.config; + +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilderCustomizer; + +public class CustomConfigBuilder implements SmallRyeConfigBuilderCustomizer { + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + builder.withDefaultValue("my.default", "1234"); + } +} +``` + +And registration in: + +```properties title="META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer" +org.acme.config.CustomConfigBuilder +``` + +The `CustomConfigBuilder` will be executed when creating a new `SmallRyeConfig` from a `SmallRyeConfigBuilder`: + +```java +SmallRyeConfig config = new SmallRyeConfigBuilder().build(); +config.getValue("my.default", int.class); +``` + +A look up to `my.default` returns the value `1234`, registered by the `CustomConfigBuilder`. + diff --git a/documentation/src/main/docs/config/environment-variables.md b/documentation/src/main/docs/config/environment-variables.md index 529fc4fe5..8fc590f3b 100644 --- a/documentation/src/main/docs/config/environment-variables.md +++ b/documentation/src/main/docs/config/environment-variables.md @@ -35,20 +35,21 @@ contains a dash or some other special character, that property name can be speci Source, with the expected dotted format. It will provide additional information to SmallRye Config to perform a two-way conversion and match the property names. -For instance: +Consider: -**Config A** -```console +```console title=".env" FOO_BAR_BAZ=VALUE ``` Will map to `foo.bar.baz` and value `value`. -**Config B** -```console +If `foo.bar-baz` is available in any source: + +```console title=".env" FOO_BAR_BAZ=VALUE ``` -```properties + +```properties title="application.properties" foo.bar-baz=default ``` diff --git a/documentation/src/main/docs/config/getting-started.md b/documentation/src/main/docs/config/getting-started.md index 31c85f0b0..bf01f6792 100644 --- a/documentation/src/main/docs/config/getting-started.md +++ b/documentation/src/main/docs/config/getting-started.md @@ -32,7 +32,7 @@ rules detailed by [Environment Variables](environment-variables.md). The MicroProfile Config configuration file `META-INF/microprofile-config.properties` in the classpath. It follows the standard convention for `properties` files. -```properties +```properties title="META-INF/microprofile-config.properties" greeting.message=hello goodbye.message=bye ``` @@ -99,7 +99,7 @@ SmallRyeConfig config; ``` - If a value if not provided for this `greeting.message`, the application startup fails with a -`javax.enterprise.inject.spi.DeploymentException: No config value of type [class java.lang.String] exists for: greeting.message`. +`jakarta.enterprise.inject.spi.DeploymentException: No config value of type [class java.lang.String] exists for: greeting.message`. - The default value `!` is injected if the configuration does not provide a value for `greeting.suffix`. - The property `greeting.name` is optional - an empty Optional is injected if the configuration does not provide a value for it. @@ -157,6 +157,7 @@ interpreted as `false` * `java.util.UUID` * `java.util.Currency` * `java.util.regex.Pattern` +* `java.nio.file.Path` * Any class with declared static methods `of`, `valueOf` or `parse` that take a `String` or a `CharSequence` * Any class with declared constructors that takes a `String` or a `CharSequence` diff --git a/documentation/src/main/docs/config/map-support.md b/documentation/src/main/docs/config/map-support.md index 5f98f15bf..73b948209 100644 --- a/documentation/src/main/docs/config/map-support.md +++ b/documentation/src/main/docs/config/map-support.md @@ -1,10 +1,11 @@ # Map Support SmallRye Config allows injecting multiple configuration parameters as a `Map`. The configuration value syntax is -represented by `=;=` Consider: +represented by `property.name.map-key=value` Consider: ```properties -server.reasons=200=OK;201=Created +server.reasons.200=OK +server.reasons.201=Created ``` The previous configuration could be injected directly in a CDI Bean: @@ -15,7 +16,7 @@ With `@ConfigProperty` @ApplicationScoped public class ConfigBean { @Inject - @ConfigProperty(name = "server.reasons", defaultValue = "200=OK;201=Created") + @ConfigProperty(name = "server.reasons") Map reasons; } ``` @@ -29,6 +30,8 @@ public class Config { } ``` +The `Map` will contains the keys `200` and `201`, which map to the values `OK` and `Created`. + !!!note Only the direct sub properties will be converted into a `Map` and diff --git a/documentation/src/main/docs/config/mappings.md b/documentation/src/main/docs/config/mappings.md index c3b29c183..1206ef4ba 100644 --- a/documentation/src/main/docs/config/mappings.md +++ b/documentation/src/main/docs/config/mappings.md @@ -68,12 +68,31 @@ Server server = config.getConfigMapping(Server.class); !!! info - Config Mapping instances are cached. They are populated when the `Config` instance is initialized. + Config Mapping instances are cached. They are populated when the `Config` instance is initialized and their values + are not updated on Config Source changes. For a Config Mapping to be valid, it needs to match every configuration property name contained in the `Config` under the specified prefix set in `@ConfigMapping`. This prevents unknown configuration properties in the `Config`. This behaviour can be disabled with the configuration `smallrye.config.mapping.validate-unknown=false`. +## Defaults + +The `io.smallrye.config.WithDefault` annotation allows to set a default property value into a mapping (and prevent +errors if the configuration value is not available in any `ConfigSource`). + +```java +public interface Defaults { + @WithDefault("foo") + String foo(); + + @WithDefault("bar") + String bar(); +} +``` + +No configuration properties are required. The `Defaults#foo()` will return the value `foo` and `Defaults#bar()` will +return the value `bar`. + ## Nested Groups A nested mapping provides a way to map sub-groups of configuration properties. @@ -132,7 +151,7 @@ server.port=8080 ### `@WithParentName` -The `io.smallrye.config.WithParent` annotation allows configurations mappings to inherit its parent container name, +The `io.smallrye.config.WithParentName` annotation allows configurations mappings to inherit its parent container name, simplifying the configuration property name required to match the mapping. ```java @@ -368,38 +387,92 @@ public interface Server { ```properties server.host=localhost server.port=8080 -server.form.login-page=login.html -server.form.error-page=error.html -server.form.landing-page=index.html +server.form.index=index.html +server.form.login.page=login.html +server.form.error.page=error.html server.aliases.localhost[0].name=prod server.aliases.localhost[1].name=127.0.0.1 +server.aliases.\"io.smallrye\"[0].name=smallrye ``` The configuration property name needs to specify an additional segment to act as the map key. The `server.form` matches -the `Server#form` `Map` and the segments `login-page`, `error-page` and `landing-page` represent the `Map` +the `Server#form` `Map` and the segments `index`, `login.page` and `error.page` represent the `Map` keys. For collection types, the key requires the indexed format. The configuration name `server.aliases.localhost[0].name` maps to the `Map> aliases()` member, where `localhost` is the `Map` key, `[0]` is the index of the -`List` where the `Alias` element will be stored, containing the name `prod`. +`List` collection where the `Alias` element will be stored, containing the name `prod`. -## Defaults +!!! info -The `io.smallrye.config.WithDefault` annotation allows to set a default property value into a mapping (and prevent -errors if the configuration value is not available in any `ConfigSource`). + They `Map` key part in the configuration property name may require quotes to delimit the key. + +### `@WithUnnamedKey` + +The `io.smallrye.config.WithUnnamedKey` annotation allows to omit a single map key in the configuration path: ```java -public interface Defaults { - @WithDefault("foo") - String foo(); +@ConfigMapping(prefix = "server") +public interface Server { + @WithUnnamedKey("localhost") + Map aliases(); + + interface Alias { + String name(); + } +} +``` - @WithDefault("bar") - String bar(); +```properties +server.aliases.name=localhost +server.aliases.prod.name=prod +``` + +The `sever.aliases.name` is an unnamed `Map` property, because it does not contain the `Map` key to populate the `Map` +entry. Due to `@WithUnnamedKey("localhost")` the `Map` key is not required in the configuration path. The key used to +look up the Map entry is given by `io.smallrye.config.WithUnnamedKey#value`: + +```java +Server server = config.getConfigMapping(Server.class); +Map localhost = server.aliases.get("localhost"); +``` + +!!! warning + + If the unnamed key (in this case `localhost`) is explicitly set in a property name, the mapping will throw an error. + +### `@WithDefaults` + +The `io.smallrye.config.WithDefaults` is a marker annotation to use only in a `Map` to return the default value for +the value element on any key lookup: + +```java +@ConfigMapping(prefix = "server") +public interface Server { + @WithDefaults + Map aliases(); + + interface Alias { + @WithDefault("localhost") + String name(); + } } ``` -No configuration properties are required. The `Defaults#foo()` will return the value `foo` and `Defaults#bar()` will -return the value `bar`. +```properties +server.aliases.prod.name=prod +``` + +A look up to the `aliases` `Map` with the key `localhost`, `any` or any other key, returns a `Alias` instance, where +`Alias.name` is `localhost`, because that is the default value. A look up to `prod` returns a `Alias` instance, where +`Alias.name` is `prod` because the property is defined in the configuration as `server.aliases.prod.name=prod`. + +```java +Server server = config.getConfigMapping(Server.class); +Map localhost = server.aliases.get("localhost"); +Map any = server.aliases.get("any"); +Map any = server.aliases.get("prod"); +``` ## ToString @@ -408,7 +481,7 @@ implementation of the `toString` method. !!! caution - Do not include a `toString` declaration in a config mapping with sensitive information + Do not include a `toString` declaration in a config mapping with sensitive information. ## Validation diff --git a/documentation/src/main/docs/config/profiles.md b/documentation/src/main/docs/config/profiles.md index 5194b2e05..aaf5eb0d1 100644 --- a/documentation/src/main/docs/config/profiles.md +++ b/documentation/src/main/docs/config/profiles.md @@ -9,12 +9,10 @@ in the same file or separate files and select between them via a profile name. To be able to set properties with the same name, each property needs to be prefixed with a percentage sign `%` followed by the profile name and a dot `.` in the syntax `%{profile-name}.config.name`: -!!! example - - ```properties - http.port=8080 - %dev.http.port=8181 - ``` +```properties title="META-INF/microprofile-config.properties" +http.port=8080 +%dev.http.port=8181 +``` To activate the profile `dev`, the configuration `smallrye.config.profile=dev` has to be set into any valid `ConfigSource`. @@ -28,16 +26,13 @@ active, yields the value `8181`. Properties for a specific profile may reside in a `microprofile-config-{profile}.properties` named file. The previous example can be expressed as: -!!! example - - - ```properties title="microprofile-config.properties" - http.port=8080 - ``` +```properties title="META-INF/microprofile-config.properties" +http.port=8080 +``` - ```properties title="microprofile-config-dev.properties" - http.port=8181 - ``` +```properties title="META-INF/microprofile-config-dev.properties" +http.port=8181 +``` In this style, the property names in the profile aware file do not need to be prefixed with the profile name. @@ -55,17 +50,15 @@ In this style, the property names in the profile aware file do not need to be pr Profile lookups are only valid if the `ConfigSource` has a higher ordinal than a lookup to the regular configuration name. Consider: -!!! example +```properties title="main.properties" +config_ordinal=1000 +http.port=8080 +``` - ```properties title="main.properties" - config_ordinal=1000 - http.port=8080 - ``` - - ```properties title="profile.properties" - config_ordinal=100 - %dev.http.port=8181 - ``` +```properties title="profile.properties" +config_ordinal=100 +%dev.http.port=8181 +``` Even with the profile `dev` active, the lookup value for `my.prop` is `1234`. This prevents lower ordinal sources to set a profile property value that cannot be overridden unless the profile property is also overridden. @@ -78,19 +71,17 @@ list of profile names: `smallrye.config.profile=common,dev`. Both `common` and ` When multiple profiles are active, the rules for profile configuration are the same. If two profiles define the same configuration, then the last listed profile has priority. Consider: -!!! example - - ```properties - smallrye.config.profile=common,dev - - my.prop=1234 - %common.my.prop=0 - %dev.my.prop=5678 - - %common.commom.prop=common - %dev.dev.prop=dev - %test.test.prop=test - ``` +```properties +smallrye.config.profile=common,dev + +my.prop=1234 +%common.my.prop=1234 +%dev.my.prop=5678 + +%common.commom.prop=common +%dev.dev.prop=dev +%test.test.prop=test +``` Then @@ -99,6 +90,26 @@ Then - `my.prop` value is `5678` - `test.prop` does not have a value +It is also possible to define multiple profile properties, with a comma-separated list of profile names: + + +```properties +%prod,dev.my.prop=1234 +``` + +The property name `common.prop` is active in both `dev` and `prod` profile. If the same property name exists in +multiple profile properties then, the property name with the most specific profile wins: + +```properties +smallrye.config.profile=dev + +%prod,dev.my.prop=1234 + +%dev.my.prop=5678 +``` + +Then `my.prop` value is `5678`. + ## Parent Profile A Parent Profile adds multiple levels of hierarchy to the current profile. The configuration @@ -107,20 +118,18 @@ A Parent Profile adds multiple levels of hierarchy to the current profile. The c When the Parent Profile is active, if a property cannot be found in the current active Profile, the config lookup fallbacks to the Parent Profile. Consider: -!!! example - - ```properties - smallrye.config.profile=dev - smallrye.config.profile.parent=common - - my.prop=1234 - %common.my.prop=0 - %dev.my.prop=5678 - - %common.commom.prop=common - %dev.dev.prop=dev - %test.test.prop=test - ``` +```properties +smallrye.config.profile=dev +smallrye.config.profile.parent=common + +my.prop=1234 +%common.my.prop=0 +%dev.my.prop=5678 + +%common.commom.prop=common +%dev.dev.prop=dev +%test.test.prop=test +``` Then @@ -138,15 +147,13 @@ Then The Parent Profile also supports multiple levels of hierarchies: -!!! example - - ```properties - smallrye.config.profile=child - %child.smallrye.config.profile.parent=parent - %parent.smallrye.config.profile.parent=grandparent - %grandparent.smallrye.config.profile.parent=greatgrandparent - %greatgrandparent.smallrye.config.profile.parent=end - ``` +```properties +smallrye.config.profile=child +%child.smallrye.config.profile.parent=parent +%parent.smallrye.config.profile.parent=grandparent +%grandparent.smallrye.config.profile.parent=greatgrandparent +%greatgrandparent.smallrye.config.profile.parent=end +``` Will load the following profiles in order: `child`, `parent`, `grandparent`, `greatgrandparent`, `end` diff --git a/documentation/src/main/docs/config/secret-handlers/encryptor.java b/documentation/src/main/docs/config/secret-handlers/encryptor.java new file mode 100644 index 000000000..1def1aee6 --- /dev/null +++ b/documentation/src/main/docs/config/secret-handlers/encryptor.java @@ -0,0 +1,90 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//DEPS info.picocli:picocli:4.5.0 +//DEPS org.jasypt:jasypt:1.9.3 + +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.concurrent.Callable; + +@Command(name = "encryptor", mixinStandardHelpOptions = true) +class encryptor implements Callable { + @Option(names = {"-s", "--secret" }, description = "Secret", required = true) + String secret; + @Option(names = {"-k", "--key" }, description = "Encryption Key") + String encryptionKey; + @Option(names = { "-f", "--format" }, description = "Encryption Key Format (base64 / plain)", defaultValue = "base64") + KeyFormat encryptionKeyFormat; + @Option(names = {"-a", "--algorithm" }, description = "Algorithm", defaultValue = "AES", hidden = true) + String algorithm; + @Option(names = {"-m", "--mode" }, description = "Mode", defaultValue = "GCM", hidden = true) + String mode; + @Option(names = {"-p", "--padding" }, description = "Padding", defaultValue = "NoPadding", hidden = true) + String padding; + + public static void main(String... args) { + int exitCode = new CommandLine(new encryptor()).execute(args); + System.exit(exitCode); + } + + @Override + public Integer call() throws Exception { + if (encryptionKey == null) { + encryptionKey = encodeToString(generateEncryptionKey().getEncoded()); + } else { + if (encryptionKeyFormat.equals(KeyFormat.base64)) { + encryptionKey = encodeToString(encryptionKey.getBytes()); + } + } + + Cipher cipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding); + byte[] iv = new byte[12]; + new SecureRandom().nextBytes(iv); + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(encryptionKey.getBytes(StandardCharsets.UTF_8)); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sha256.digest(), "AES"), new GCMParameterSpec(128, iv)); + + byte[] encrypted = cipher.doFinal(secret.getBytes(StandardCharsets.UTF_8)); + + ByteBuffer message = ByteBuffer.allocate(1 + iv.length + encrypted.length); + message.put((byte) iv.length); + message.put(iv); + message.put(encrypted); + + String encrypt = Base64.getUrlEncoder().withoutPadding().encodeToString((message.array())); + System.out.println("${aes-gcm-nopadding::" + encrypt + "}"); + System.out.println("smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=" + encryptionKey); + + return 0; + } + + private SecretKey generateEncryptionKey() { + try { + return KeyGenerator.getInstance(algorithm).generateKey(); + } catch (Exception e) { + System.err.println("Error while generating the encryption key: " + e); + System.exit(-1); + } + return null; + } + + private static String encodeToString(byte[] data) { + return Base64.getUrlEncoder().withoutPadding().encodeToString(data); + } + + public enum KeyFormat { + base64, + plain + } +} diff --git a/documentation/src/main/docs/config/secret-handlers/jasypt.java b/documentation/src/main/docs/config/secret-handlers/jasypt.java new file mode 100644 index 000000000..423f699b7 --- /dev/null +++ b/documentation/src/main/docs/config/secret-handlers/jasypt.java @@ -0,0 +1,43 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//DEPS info.picocli:picocli:4.5.0 +//DEPS org.jasypt:jasypt:1.9.3 + +import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; +import org.jasypt.iv.RandomIvGenerator; +import org.jasypt.properties.PropertyValueEncryptionUtils; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.util.concurrent.Callable; +import java.util.logging.Logger; + +@Command(name = "jasypt", mixinStandardHelpOptions = true) +class jasypt implements Callable { + @Option(names = {"-s", "--secret" }, description = "Secret", required = true) + private String secret; + @Option(names = {"-p", "--password" }, description = "Password", required = true) + private String password; + @Option(names = {"-a", "--algorithm" }, description = "Algorithm", defaultValue = "PBEWithHMACSHA512AndAES_256") + private String algorithm; + + public static void main(String... args) { + int exitCode = new CommandLine(new jasypt()).execute(args); + System.exit(exitCode); + } + + @Override + public Integer call() { + StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); + encryptor.setPassword(password); + encryptor.setAlgorithm(algorithm); + encryptor.setIvGenerator(new RandomIvGenerator()); + encryptor.initialize(); + + String encrypt = PropertyValueEncryptionUtils.encrypt(secret, encryptor); + System.out.println("${jasypt::" + encrypt + "}"); + + return 0; + } +} diff --git a/documentation/src/main/docs/config/secret-keys.md b/documentation/src/main/docs/config/secret-keys.md index 706910aec..376713da2 100644 --- a/documentation/src/main/docs/config/secret-keys.md +++ b/documentation/src/main/docs/config/secret-keys.md @@ -1,27 +1,149 @@ # Secret Keys +## Secret Keys Expressions + +In SmallRye Config, a secret configuration may be expressed as `${handler::value}`, where the `handler` is the name of +a `io.smallrye.config.SecretKeysHandler` to decode or decrypt the `value` separated by a double colon `::`. + +It is possible to create a custom `SecretKeysHandler` and provide different ways to decode or decrypt configuration +values. + +A custom `SecretKeysHandler` requires an implementation of `io.smallrye.config.SecretKeysHandler` or +`io.smallrye.config.SecretKeysHandlerFactory`. Each implementation requires registration via the `ServiceLoader` +mechanism, either in `META-INF/services/io.smallrye.config.SecretKeysHandler` or +`META-INF/services/io.smallrye.config.SecretKeysHandlerFactory` files. + +!!!danger + + It is not possible to mix Secret Keys Expressions with Property Expressions. + +### Crypto + +The `smallrye-config-crypto` artifact contains a few out-of-the-box `SecretKeysHandler`s ready for use. It requires +the following dependency: + +```xml + + io.smallrye.config + smallrye-config-crypto + {{attributes['version']}} + +``` + +#### AES/GCM/NoPadding `${aes-gcm-nopadding::...}` + +- The encoding length is 128. +- The secret and the encryption key (without padding) must be base 64 encoded. + +!!! example + + ```properties title="application.properties" + smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=DDne5obnfH1RSeTg71xSZg + + my.secret=${aes-gcm-nopadding::DLTb_9zxThxeT5iAQqswEl5Dn1ju4FdM9hIyVip35t5V} + ``` + + The `${aes-gcm-nopadding::...}` `SecretKeyHandler` requires + `smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key` configuration to state the encryption key to be + used by the `aes-gcm-nopaddin` handler. + + A lookup to `my.secret` will use the `SecretKeysHandler` name `aes-gcm-nopadding` to decode the value + `DJNrZ6LfpupFv6QbXyXhvzD8eVDnDa_kTliQBpuzTobDZxlg`. + +!!! info + + It is possible to generate the encrypted secret with the following [JBang](http://jbang.dev/) script: + + ```shell + jbang https://raw.githubusercontent.com/smallrye/smallrye-config/main/documentation/src/main/docs/config/secret-handlers/encryptor.java -s= -k=` + ``` + +##### Configuration + +| Configuration Property | Type | Default | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-----------| +| `smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key`The encryption key to use to decode secrets encoded by the `AES/GCM/NoPadding` algorithm. | String | | +| `"smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key-decode"`Decode the encryption key in Base64, if the plain text key was used to encrypt the secret. | boolean | false | + +### Jasypt + +[Jasypt](http://www.jasypt.org) is a java library which allows the developer to add basic encryption capabilities. Add +the following dependency in your project to use it: + +```xml + + io.smallrye.config + smallrye-config-jasypt + {{attributes['version']}} + +``` + +#### Jasypt `${jasypt::...}` + +!!! example + + ```properties title="application.properties" + smallrye.config.secret-handler.jasypt.password=jasypt + smallrye.config.secret-handler.jasypt.algorithm=PBEWithHMACSHA512AndAES_256 + + my.secret=${jasypt::ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)} + ``` + The `${jasypt::...}` `SecretKeyHandler` requires both `smallrye.config.secret-handler.jasypt.password` and + `smallrye.config.secret-handler.jasypt.algorithm` configurations to state the password and the algorithm to be + used by the Jasypt encryptor. + + Jasypt encrypted values must be set with the handler expression as `${jasypt::ENC(value)}`. Note that the + encrypted value must be generated using the proper Jasypt encryptor with the same password and algorithm set in + the confguration. + + A possible encrypted value for `12345678` is `ENC(wqp8zDeiCQ5JaFvwDtoAcr2WMLdlD0rjwvo8Rh0thG5qyTQVGxwJjBIiW26y0dtU)` + + Lookups to the configuration `my.secret` will automatically decrypt the value with Jasypt and provide the original + `12345678` string. + +!!! info + + It is possible to generate the encrypted secret with the following [JBang](http://jbang.dev/) script: + + ```shell + jbang https://raw.githubusercontent.com/smallrye/smallrye-config/main/documentation/src/main/docs/config/secret-handlers/jasypt.java -s= -p= + ``` + +##### Configuration + +| Configuration Property | Type | Default | +|--- |--- |--- | +| `smallrye.config.secret-handler.jasypt.password`The Jasypt password to use | String | | +| `smallrye.config.secret-handler.jasypt.algorithm`The Jasypt algorithm to use | String | | + +## Secret Keys Names + When configuration properties contain passwords or other kinds of secrets, Smallrye Config can hide them to prevent accidental exposure of such values. **This is no way a replacement for securing secrets.** Proper security mechanisms must still be used to secure -secrets. However, there is still the basic problem that passwords and secrets are generally encoded simply as strings. -Secret Keys provides a way to "lock" the configuration so that secrets do not appear unless explicitly enabled. +secrets. However, there is still the fundamental problem that passwords and secrets are generally encoded simply as +strings. Secret Keys provides a way to "lock" the configuration so that secrets do not appear unless explicitly enabled. -Secret Keys requires the list of the configuration property names that must be hidden. This can be supplied -in `SmallRyeConfigBuilder#withSecretKeys`. +To mark specific keys as secrets, register an instance of `io.smallrye.config.SecretKeysConfigSourceInterceptor` by +using the interceptor factory as follows: ```java -SmallRyeConfig config = new SmallRyeConfigBuilder() - .addDefaultSources() - .addDefaultInterceptors() - .withSources(KeyValuesConfigSource.config(keyValues)) - .withSecretKeys("secret") - .build() +public class SecretKeysConfigInterceptorFactory implements ConfigSourceInterceptorFactory { + @Override + public ConfigSourceInterceptor getInterceptor(ConfigSourceInterceptorContext context) { + return new SecretKeysConfigSourceInterceptor(Set.of("secret")); + } +} ``` +Register the factory so that it can be found at runtime by creating a +`META-INF/services/io.smallrye.config.ConfigSourceInterceptorFactory` file that contains the fully qualified name of +this factory class. + From this point forward, every lookup to the configuration name `secret` will throw a `SecurityException`. -Access to the Secret Keys, is available via the APIs `io.smallrye.config.SecretKeys#doUnlocked(java.lang.Runnable)` +Access the Secret Keys using the APIs `io.smallrye.config.SecretKeys#doUnlocked(java.lang.Runnable)` and `io.smallrye.config.SecretKeys#doUnlocked(java.util.function.Supplier)`. ```java @@ -30,5 +152,5 @@ String secretValue = SecretKeys.doUnlocked(() -> { }); ``` -Secret Keyes are only unlocked in the context of `doUnlocked`. Once the execution completes, the secrets become locked +Secret Keys are only unlocked in the context of `doUnlocked`. Once the execution completes, the secrets become locked again. diff --git a/documentation/src/main/docs/converters/custom.md b/documentation/src/main/docs/converters/custom.md index 1e1da94e8..818aae130 100644 --- a/documentation/src/main/docs/converters/custom.md +++ b/documentation/src/main/docs/converters/custom.md @@ -42,8 +42,7 @@ public class CustomValueConverter implements Converter { And registration in: -_META-INF/services/org.eclipse.microprofile.config.spi.Converter -```properties +```properties title="META-INF/services/org.eclipse.microprofile.config.spi.Converter" org.acme.config.CustomValue ``` @@ -59,14 +58,14 @@ Config config = ConfigProvider.getConfig(); CustomValue value = config.getValue("custom.value", CustomValue.class); ```` -The `javax.annotation.Priority` annotation overrides the `Converter` priority and change converters precedence to fine +The `jakarta.annotation.Priority` annotation overrides the `Converter` priority and change converters precedence to fine tune the execution order. By default, if no `@Priority` is specified by the `Converter`, the converter is registered with a priority of `100`. Consider: ```java package org.acme.config; -import javax.annotation.Priority; +import jakarta.annotation.Priority; import org.eclipse.microprofile.config.spi.Converter; @Priority(150) diff --git a/documentation/src/main/docs/extensions/fallback.md b/documentation/src/main/docs/extensions/fallback.md index 6ac76a50b..2c3ce1a16 100644 --- a/documentation/src/main/docs/extensions/fallback.md +++ b/documentation/src/main/docs/extensions/fallback.md @@ -8,25 +8,41 @@ fall back to provide the same expected behavior. The fallback function is only a configuration name is not found and resolved to the fallback name. ```java -package org.acme; +package org.acme.config; import java.util.function.Function; import io.smallrye.config.FallbackConfigSourceInterceptor; public class MicroProfileConfigFallbackInterceptor extends FallbackConfigSourceInterceptor { public MicroProfileConfigFallbackInterceptor(final Function mapping) { - super(name -> name.startsWith("microprofile.config") ? - name.replaceAll("microprofile\\.config", "smallrye.config") : + super(name -> name.startsWith("mp.config") ? + name.replaceAll("mp\\.config", "smallrye.config") : name); } } ``` And registration in: -_META-INF/services/io.smallrye.config.ConfigSourceInterceptor_ -```properties -io.smallrye.config.MicroProfileConfigFallbackInterceptor + +```properties title="META-INF/services/io.smallrye.config.ConfigSourceInterceptor" +org.acme.config.MicroProfileConfigFallbackInterceptor ``` -The `MicroProfileConfigFallbackInterceptor` can fallback configuration names in the `microprofile.config` namespace +The `MicroProfileConfigFallbackInterceptor` can fallback configuration names in the `mp.config` namespace to the `smallrye.config` namespace. + +!!! example + + ```properties title="application.properties" + mp.config.profile=test + smallrye.config.profile=prod + ``` + + A lookup to `mp.config.profile` returns the value `test`. + + ```properties title="application.properties" + smallrye.config.profile=prod + ``` + + A lookup to `mp.config.profile` returns the value `prod`. The config is not able to find a value for + `mp.config.profile`, so the interceptor fallbacks and lookups the value of `smallrye.config.profile`. diff --git a/documentation/src/main/docs/extensions/interceptors.md b/documentation/src/main/docs/extensions/interceptors.md index 60eab6bdc..bb579c3c4 100644 --- a/documentation/src/main/docs/extensions/interceptors.md +++ b/documentation/src/main/docs/extensions/interceptors.md @@ -26,10 +26,12 @@ origin and ordinal. ```java package org.acme.config; -import javax.annotation.Priority; +import static io.smallrye.config.SecretKeys.doLocked; + +import jakarta.annotation.Priority; import io.smallrye.config.ConfigSourceInterceptor; -import io.smallrye.config.ConfigLogging; +import io.smallrye.config._private.ConfigLogging; @Priority(Priorities.LIBRARY + 200) public class LoggingConfigSourceInterceptor implements ConfigSourceInterceptor { @@ -49,8 +51,8 @@ public class LoggingConfigSourceInterceptor implements ConfigSourceInterceptor { ``` And registration in: -_META-INF/services/io.smallrye.config.ConfigSourceInterceptor_ -```properties + +```properties title="META-INF/services/io.smallrye.config.ConfigSourceInterceptor" org.acme.config.LoggingConfigSourceInterceptor ``` @@ -66,7 +68,7 @@ The `ConfigSourceInterceptorFactory` can initialize an interceptor with access t (so it can be used to configure the interceptor and retrieve configuration values) and set the priority. A `ConfigSourceInterceptor` implementation class can specify a priority by way of the standard -`javax.annotation.Priority` annotation. If no priority is explicitly assigned, the default priority value of +`jakarta.annotation.Priority` annotation. If no priority is explicitly assigned, the default priority value of `io.smallrye.config.Priorities.APPLICATION` is assumed. If multiple interceptors are registered with the same priority, then their execution order may be non-deterministic. diff --git a/documentation/src/main/docs/extensions/logging.md b/documentation/src/main/docs/extensions/logging.md index 880464a8e..0a606a236 100644 --- a/documentation/src/main/docs/extensions/logging.md +++ b/documentation/src/main/docs/extensions/logging.md @@ -1,7 +1,7 @@ ## LoggingConfigSourceInterceptor The `io.smallrye.config.LoggingConfigSourceInterceptor` logs lookups of configuration names in the provided logging -platform. The log information includes config name and value, the config source origing and location if exists. +platform. The log information includes config name and value, the config source origin and location if it exists. The log is done as `debug`, so the debug threshold must be set to `debug` for the `io.smallrye.config` appender to display the logs. diff --git a/documentation/src/main/docs/extensions/relocate.md b/documentation/src/main/docs/extensions/relocate.md index fd13f6076..c0d3e0f8a 100644 --- a/documentation/src/main/docs/extensions/relocate.md +++ b/documentation/src/main/docs/extensions/relocate.md @@ -8,25 +8,35 @@ config sources are not updated yet. The relocation function gives priority to th resolves to the old name if no value is found under the new relocation name. ```java -package org.acme; +package org.acme.config; import java.util.function.Function; import io.smallrye.config.RelocateConfigSourceInterceptor; public class MicroProfileConfigRelocateInterceptor extends RelocateConfigSourceInterceptor { - public MicroProfileConfigFallbackInterceptor(final Function mapping) { - super(name -> name.startsWith("microprofile.config") ? - name.replaceAll("microprofile\\.config", "smallrye.config") : + public MicroProfileConfigRelocateInterceptor(final Function mapping) { + super(name -> name.startsWith("mp.config") ? + name.replaceAll("mp\\.config", "smallrye.config") : name); } } ``` And registration in: -_META-INF/services/io.smallrye.config.ConfigSourceInterceptor_ -```properties -io.smallrye.config.MicroProfileConfigRelocateInterceptor + +```properties title="META-INF/services/io.smallrye.config.ConfigSourceInterceptor" +org.acme.config.MicroProfileConfigRelocateInterceptor ``` -The `MicroProfileConfigRelocateInterceptor` can relocate configuration names in the `microprofile.config` namespace +The `MicroProfileConfigRelocateInterceptor` can relocate configuration names in the `mp.config` namespace to the `smallrye.config` namespace. + +!!! example + + ```properties title="application.properties" + mp.config.profile=test + smallrye.config.profile=prod + ``` + + A lookup to `mp.config.profile` returns the value `prod`. The config finds a valid value in the relocated name + `smallrye.config.profile`, so the interceptor will use this value instead of the one in `mp.config.profile`. diff --git a/documentation/src/main/docs/index.md b/documentation/src/main/docs/index.md index e7582f10d..659c2a518 100644 --- a/documentation/src/main/docs/index.md +++ b/documentation/src/main/docs/index.md @@ -32,19 +32,19 @@ Add the _dependency_ to your project using your preferred build tool: === "Gradle (Groovy)" ```groovy - implementation 'io.smallrye.config:smallrye-config:{{ attributes.versions }}' + implementation 'io.smallrye.config:smallrye-config:{{attributes['version']}}' ``` === "Gradle (Kotlin)" ```kotlin - implementation("io.smallrye.config:smallrye-config:{{ attributes.versions }}") + implementation("io.smallrye.config:smallrye-config:{{attributes['version']}}") ``` === "JBang" ```java - //DEPS io.smallrye.config:smallrye-config:{{ attributes.versions }} + //DEPS io.smallrye.config:smallrye-config:{{attributes['version']}} ``` And retrieve a `SmallRyeConfig` instance with: diff --git a/examples/configmap/pom.xml b/examples/configmap/pom.xml index dae619918..3f0892c48 100644 --- a/examples/configmap/pom.xml +++ b/examples/configmap/pom.xml @@ -4,19 +4,33 @@ io.smallrye smallrye-parent - 35 + 42 + io.smallrye.config.examples configmap - 2.11.1 + 3.7.1 SmallRye Config Examples: ConfigMap true + 2.3.0 + + + + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom + + + + jakarta.annotation @@ -47,7 +61,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0 + 3.5.2 package @@ -55,6 +69,7 @@ shade + false ${project.artifactId}-app @@ -70,7 +85,7 @@ io.fabric8 docker-maven-plugin - 0.40.2 + 0.44.0 true docker-registry:5000 @@ -110,6 +125,6 @@ - 2.11.1 + 3.7.1 diff --git a/examples/expansion/pom.xml b/examples/expansion/pom.xml index d0bcc26e7..66434da10 100644 --- a/examples/expansion/pom.xml +++ b/examples/expansion/pom.xml @@ -20,19 +20,33 @@ io.smallrye smallrye-parent - 35 + 42 + io.smallrye.config.examples expansion - 2.11.1 + 3.7.1 SmallRye Config Examples: Property Expansion true + 2.3.0 + + + + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom + + + + io.smallrye.config @@ -58,6 +72,6 @@ - 2.11.1 + 3.7.1 diff --git a/examples/expansion/src/main/java/io/smallrye/config/examples/expansion/ExampleExpansionBean.java b/examples/expansion/src/main/java/io/smallrye/config/examples/expansion/ExampleExpansionBean.java index 4f5f488af..c444284a8 100644 --- a/examples/expansion/src/main/java/io/smallrye/config/examples/expansion/ExampleExpansionBean.java +++ b/examples/expansion/src/main/java/io/smallrye/config/examples/expansion/ExampleExpansionBean.java @@ -1,7 +1,7 @@ package io.smallrye.config.examples.expansion; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; diff --git a/examples/expansion/src/test/java/io/smallrye/config/examples/expansion/ExampleExpansionBeanTest.java b/examples/expansion/src/test/java/io/smallrye/config/examples/expansion/ExampleExpansionBeanTest.java index 8bf8f5ad9..023796366 100644 --- a/examples/expansion/src/test/java/io/smallrye/config/examples/expansion/ExampleExpansionBeanTest.java +++ b/examples/expansion/src/test/java/io/smallrye/config/examples/expansion/ExampleExpansionBeanTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.jboss.weld.junit5.WeldInitiator; import org.jboss.weld.junit5.WeldJunit5Extension; diff --git a/examples/interceptors/pom.xml b/examples/interceptors/pom.xml index c7b1aa92b..123ce4be5 100644 --- a/examples/interceptors/pom.xml +++ b/examples/interceptors/pom.xml @@ -20,19 +20,33 @@ io.smallrye smallrye-parent - 35 + 42 + io.smallrye.config.examples interceptors - 2.11.1 + 3.7.1 SmallRye Config Examples: Interceptors true + 2.3.0 + + + + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom + + + + io.smallrye.config @@ -53,6 +67,6 @@ - 2.11.1 + 3.7.1 diff --git a/examples/mapping/pom.xml b/examples/mapping/pom.xml index a33846d84..20ecd19b7 100644 --- a/examples/mapping/pom.xml +++ b/examples/mapping/pom.xml @@ -20,19 +20,33 @@ io.smallrye smallrye-parent - 35 + 42 + io.smallrye.config.examples mapping - 2.11.1 + 3.7.1 SmallRye Config Examples: Config Mapping true + 2.3.0 + + + + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom + + + + io.smallrye.config @@ -58,6 +72,6 @@ - 2.11.1 + 3.7.1 diff --git a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/MappingSmallRyeConfigFactory.java b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/MappingSmallRyeConfigFactory.java index 66ff872e4..f4d8e4880 100644 --- a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/MappingSmallRyeConfigFactory.java +++ b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/MappingSmallRyeConfigFactory.java @@ -11,6 +11,7 @@ public SmallRyeConfig getConfigFor( return configProviderResolver.getBuilder() .forClassLoader(classLoader) + .addDiscoveredCustomizers() .addDefaultSources() .addDefaultInterceptors() .addDiscoveredSources() diff --git a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMapping.java b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMapping.java index 1acc03189..3352e6705 100644 --- a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMapping.java +++ b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMapping.java @@ -6,7 +6,7 @@ public class ServerMapping { public static Server getServer() { - SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); return config.getConfigMapping(Server.class); } } diff --git a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMappingBean.java b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMappingBean.java index 3ee009351..5708cbb9d 100644 --- a/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMappingBean.java +++ b/examples/mapping/src/main/java/io/smallrye/config/examples/mapping/ServerMappingBean.java @@ -1,7 +1,7 @@ package io.smallrye.config.examples.mapping; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; @ApplicationScoped public class ServerMappingBean { diff --git a/examples/mapping/src/test/java/io/smallrye/config/examples/mapping/ServerMappingBeanTest.java b/examples/mapping/src/test/java/io/smallrye/config/examples/mapping/ServerMappingBeanTest.java index fb6094ad4..717c53456 100644 --- a/examples/mapping/src/test/java/io/smallrye/config/examples/mapping/ServerMappingBeanTest.java +++ b/examples/mapping/src/test/java/io/smallrye/config/examples/mapping/ServerMappingBeanTest.java @@ -6,8 +6,8 @@ import java.time.Duration; import java.util.stream.Stream; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.jboss.weld.junit5.WeldInitiator; import org.jboss.weld.junit5.WeldJunit5Extension; diff --git a/examples/pom.xml b/examples/pom.xml index d798f769f..7272bf31a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -20,7 +20,7 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config-examples diff --git a/examples/profiles/pom.xml b/examples/profiles/pom.xml index 1d2858323..0d87ce89a 100644 --- a/examples/profiles/pom.xml +++ b/examples/profiles/pom.xml @@ -20,19 +20,33 @@ io.smallrye smallrye-parent - 35 + 42 + io.smallrye.config.examples profiles - 2.11.1 + 3.7.1 SmallRye Config Examples: Profiles true + 2.3.0 + + + + io.smallrye.testing + smallrye-testing-bom + ${version.smallrye.testing} + import + pom + + + + io.smallrye.config @@ -72,6 +86,6 @@ - 2.11.1 + 3.7.1 diff --git a/examples/profiles/src/main/java/io/smallrye/config/examples/profiles/ExampleProfilesBean.java b/examples/profiles/src/main/java/io/smallrye/config/examples/profiles/ExampleProfilesBean.java index 01eea6658..876d5b7da 100644 --- a/examples/profiles/src/main/java/io/smallrye/config/examples/profiles/ExampleProfilesBean.java +++ b/examples/profiles/src/main/java/io/smallrye/config/examples/profiles/ExampleProfilesBean.java @@ -1,7 +1,7 @@ package io.smallrye.config.examples.profiles; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; diff --git a/examples/profiles/src/test/java/io/smallrye/config/examples/profiles/ExampleProfilesBeanTest.java b/examples/profiles/src/test/java/io/smallrye/config/examples/profiles/ExampleProfilesBeanTest.java index 98249c24a..7af94eb8f 100644 --- a/examples/profiles/src/test/java/io/smallrye/config/examples/profiles/ExampleProfilesBeanTest.java +++ b/examples/profiles/src/test/java/io/smallrye/config/examples/profiles/ExampleProfilesBeanTest.java @@ -2,8 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.jboss.weld.junit5.WeldInitiator; import org.jboss.weld.junit5.WeldJunit5Extension; diff --git a/implementation/pom.xml b/implementation/pom.xml index cf286f95b..a4fcc9aa1 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,12 +20,12 @@ io.smallrye.config smallrye-config-parent - 2.11.1 + 3.7.1 smallrye-config-core - SmallRye: MicroProfile Config Core Implementation + SmallRye Config: Core diff --git a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceFactory.java b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceFactory.java index c71ef0cf1..847e004da 100644 --- a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceFactory.java +++ b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceFactory.java @@ -9,17 +9,19 @@ import org.eclipse.microprofile.config.spi.ConfigSource; -import io.smallrye.common.annotation.Experimental; - /** * This {@code AbstractLocationConfigSourceFactory} allows to initialize additional config locations with the * configuration {@link SmallRyeConfig#SMALLRYE_CONFIG_LOCATIONS}. The configuration support multiple * locations separated by a comma and each must represent a valid {@link URI}. */ -@Experimental("Loads additional config locations") public abstract class AbstractLocationConfigSourceFactory extends AbstractLocationConfigSourceLoader implements ConfigSourceFactory { + @Override + protected boolean failOnMissingFile() { + return true; + } + @Override public Iterable getConfigSources(final ConfigSourceContext context) { final ConfigValue value = context.getValue(SMALLRYE_CONFIG_LOCATIONS); diff --git a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java index fb6ae2687..68c7f09ba 100644 --- a/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java +++ b/implementation/src/main/java/io/smallrye/config/AbstractLocationConfigSourceLoader.java @@ -24,7 +24,7 @@ import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.common.annotation.Experimental; +import io.smallrye.config._private.ConfigMessages; /** * This {@link AbstractLocationConfigSourceLoader} loads {@link ConfigSource}s from a list of specific @@ -46,10 +46,20 @@ * location. This is to keep a consistent loading order and match with the unprofiled resource. Profiles are not * taken into account if the location is a directory. */ -@Experimental("Loads sources by location") public abstract class AbstractLocationConfigSourceLoader { private static final Converter URI_CONVERTER = new URIConverter(); + /** + * If the lookup from an {@link URL} which the scheme {@code file:} should fail. By default, a failed load does not + * throw an exception. In situations where the resource is required, a return value of {@code true} enables the + * exception. + * + * @return {@code true} if file lookup should fail with an exception, {@code false} otherwise. + */ + protected boolean failOnMissingFile() { + return false; + } + /** * The file extensions to filter the locations to load. It does not require to include the dot separator. * @@ -65,16 +75,16 @@ public abstract class AbstractLocationConfigSourceLoader { * @param ordinal the ordinal of the {@link ConfigSource}. * * @return the loaded {@link ConfigSource}. - * @throws IOException if an error occurred when reading from the the {@link URL}. + * @throws IOException if an error occurred when reading from the {@link URL}. */ protected abstract ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException; protected List loadConfigSources(final String location, final int ordinal) { - return loadConfigSources(new String[] { location }, ordinal); + return loadConfigSources(location != null ? new String[] { location } : null, ordinal); } protected List loadConfigSources(final String location, final int ordinal, final ClassLoader classLoader) { - return loadConfigSources(new String[] { location }, ordinal, classLoader); + return loadConfigSources(location != null ? new String[] { location } : null, ordinal, classLoader); } protected List loadConfigSources(final String[] locations, final int ordinal) { @@ -116,8 +126,10 @@ protected List tryFileSystem(final URI uri, final int ordinal) { addConfigSource(path.toUri(), ordinal, configSources); } } catch (IOException e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, uri.toString()); } + } else if ("file".equals(uri.getScheme()) && Files.notExists(urlPath) && failOnMissingFile()) { + throw ConfigMessages.msg.failedToLoadResource(new FileNotFoundException(uri.toString()), uri.toString()); } return configSources; } @@ -128,7 +140,7 @@ protected List tryClassPath(final URI uri, final int ordinal, fina try { consumeAsPaths(useClassloader, uri.getPath(), new ConfigSourcePathConsumer(ordinal, configSources)); } catch (IOException e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, uri.toString()); } catch (IllegalArgumentException e) { configSources.addAll(fallbackToUnknownProtocol(uri, ordinal, useClassloader)); } @@ -140,32 +152,34 @@ protected List tryJar(final URI uri, final int ordinal) { try { consumeAsPath(toURL(uri), new ConfigSourcePathConsumer(ordinal, configSources)); } catch (Exception e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, uri.toString()); } return configSources; } protected List fallbackToUnknownProtocol(final URI uri, final int ordinal, final ClassLoader classLoader) { - final List configSources = new ArrayList<>(); + List configSources = new ArrayList<>(); try { Enumeration resources = classLoader.getResources(uri.toString()); while (resources.hasMoreElements()) { - final URL resourceUrl = resources.nextElement(); + URL resourceUrl = resources.nextElement(); if (validExtension(resourceUrl.getFile())) { - final ConfigSource mainSource = addConfigSource(resourceUrl, ordinal, configSources); + ConfigSource mainSource = addConfigSource(resourceUrl, ordinal, configSources); configSources.add(new ConfigurableConfigSource((ProfileConfigSourceFactory) profiles -> { - final List profileSources = new ArrayList<>(); + List profileSources = new ArrayList<>(); for (int i = profiles.size() - 1; i >= 0; i--) { - final int mainOrdinal = mainSource.getOrdinal() + profiles.size() - i + 1; - final URI profileUri = addProfileName(uri, profiles.get(i)); - try { - final Enumeration profileResources = classLoader.getResources(profileUri.toString()); - while (profileResources.hasMoreElements()) { - final URL profileUrl = profileResources.nextElement(); - addProfileConfigSource(profileUrl, mainOrdinal, profileSources); + int mainOrdinal = mainSource.getOrdinal() + profiles.size() - i + 1; + for (String fileExtension : getFileExtensions()) { + URI profileUri = addProfileName(uri, profiles.get(i), fileExtension); + try { + Enumeration profileResources = classLoader.getResources(profileUri.toString()); + while (profileResources.hasMoreElements()) { + final URL profileUrl = profileResources.nextElement(); + addProfileConfigSource(profileUrl, mainOrdinal, profileSources); + } + } catch (IOException e) { + // It is ok to not find the resource here, because it is an optional profile resource. } - } catch (IOException e) { - // It is ok to not find the resource here, because it is an optional profile resource. } } return profileSources; @@ -173,7 +187,7 @@ protected List fallbackToUnknownProtocol(final URI uri, final int } } } catch (IOException e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, uri.toString()); } return configSources; } @@ -188,15 +202,18 @@ protected List tryHttpResource(final URI uri, final int ordinal) { } protected List tryProfiles(final URI uri, final ConfigSource mainSource) { - final List configSources = new ArrayList<>(); + List configSources = new ArrayList<>(); configSources.add(new ConfigurableConfigSource(new ProfileConfigSourceFactory() { @Override public Iterable getProfileConfigSources(final List profiles) { - final List profileSources = new ArrayList<>(); + List profileSources = new ArrayList<>(); for (int i = profiles.size() - 1; i >= 0; i--) { - final int ordinal = mainSource.getOrdinal() + profiles.size() - i; - final URI profileUri = addProfileName(uri, profiles.get(i)); - AbstractLocationConfigSourceLoader.this.addProfileConfigSource(toURL(profileUri), ordinal, profileSources); + int ordinal = mainSource.getOrdinal() + profiles.size() - i; + for (String fileExtension : getFileExtensions()) { + URI profileUri = addProfileName(uri, profiles.get(i), fileExtension); + AbstractLocationConfigSourceLoader.this.addProfileConfigSource(toURL(profileUri), ordinal, + profileSources); + } } return profileSources; } @@ -227,7 +244,7 @@ private ConfigSource addConfigSource(final URL url, final int ordinal, final Lis configSources.add(configSource); return configSource; } catch (IOException e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, url.toString()); } } @@ -238,7 +255,7 @@ private void addProfileConfigSource(final URL profileToFileName, final int ordin } catch (FileNotFoundException | NoSuchFileException e) { // It is ok to not find the resource here, because it is an optional profile resource. } catch (IOException e) { - throw ConfigMessages.msg.failedToLoadResource(e); + throw ConfigMessages.msg.failedToLoadResource(e, profileToFileName.toString()); } } @@ -247,7 +264,13 @@ private boolean validExtension(final Path fileName) { } private boolean validExtension(final String resourceName) { - for (String s : getFileExtensions()) { + String[] fileExtensions = getFileExtensions(); + + if (fileExtensions.length == 0) { + return true; + } + + for (String s : fileExtensions) { if (resourceName.endsWith(s)) { return true; } @@ -255,9 +278,9 @@ private boolean validExtension(final String resourceName) { return false; } - private static URI addProfileName(final URI uri, final String profile) { + private static URI addProfileName(final URI uri, final String profile, final String fileExtension) { if ("jar".equals(uri.getScheme())) { - return URI.create("jar:" + addProfileName(URI.create(decodeIfNeeded(uri).getRawSchemeSpecificPart()), profile)); + return URI.create("jar:" + addProfileName(URI.create(uri.getRawSchemeSpecificPart()), profile, fileExtension)); } final String fileName = uri.getPath(); @@ -266,7 +289,7 @@ private static URI addProfileName(final URI uri, final String profile) { final int dot = fileName.lastIndexOf("."); final String fileNameProfile; if (dot != -1 && dot != 0 && fileName.charAt(dot - 1) != '/') { - fileNameProfile = fileName.substring(0, dot) + "-" + profile + fileName.substring(dot); + fileNameProfile = fileName.substring(0, dot) + "-" + profile + "." + fileExtension; } else { fileNameProfile = fileName + "-" + profile; } @@ -310,19 +333,9 @@ public ConfigSourcePathConsumer(final int ordinal, final List conf public void accept(final Path path) { final AbstractLocationConfigSourceLoader loader = AbstractLocationConfigSourceLoader.this; if (loader.validExtension(path.getFileName().toString())) { - final ConfigSource mainSource = loader.addConfigSource(decodeIfNeeded(path.toUri()), ordinal, configSources); + final ConfigSource mainSource = loader.addConfigSource(path.toUri(), ordinal, configSources); configSources.addAll(loader.tryProfiles(path.toUri(), mainSource)); } } } - - // https://bugs.openjdk.java.net/browse/JDK-8131067 - For Java 8 - @Deprecated - private static URI decodeIfNeeded(final URI uri) { - if (uri.getScheme().equals("jar")) { - return URI.create(uri.getScheme() + ":" + uri.getSchemeSpecificPart()); - } else { - return uri; - } - } } diff --git a/implementation/src/main/java/io/smallrye/config/AbstractMappingConfigSourceInterceptor.java b/implementation/src/main/java/io/smallrye/config/AbstractMappingConfigSourceInterceptor.java index 56455a454..17568f402 100644 --- a/implementation/src/main/java/io/smallrye/config/AbstractMappingConfigSourceInterceptor.java +++ b/implementation/src/main/java/io/smallrye/config/AbstractMappingConfigSourceInterceptor.java @@ -1,6 +1,5 @@ package io.smallrye.config; -import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -17,7 +16,12 @@ public AbstractMappingConfigSourceInterceptor(final Function map } public AbstractMappingConfigSourceInterceptor(final Map mappings) { - this((Serializable & Function) name -> mappings.getOrDefault(name, name)); + this(new Function() { + @Override + public String apply(final String name) { + return mappings.getOrDefault(name, name); + } + }); } @Override @@ -35,30 +39,6 @@ public Iterator iterateNames(final ConfigSourceInterceptorContext contex return names.iterator(); } - @Override - public Iterator iterateValues(final ConfigSourceInterceptorContext context) { - final Set values = new HashSet<>(); - final Iterator valuesIterator = context.iterateValues(); - while (valuesIterator.hasNext()) { - final ConfigValue value = valuesIterator.next(); - values.add(value); - final String mappedName = mapping.apply(value.getName()); - if (mappedName != null) { - values.add(ConfigValue.builder() - .withName(mappedName) - .withValue(value.getValue()) - .withRawValue(value.getRawValue()) - .withProfile(value.getProfile()) - .withConfigSourceName(value.getConfigSourceName()) - .withConfigSourcePosition(value.getConfigSourcePosition()) - .withConfigSourceOrdinal(value.getConfigSourceOrdinal()) - .withLineNumber(value.getLineNumber()) - .build()); - } - } - return values.iterator(); - } - protected Function getMapping() { return mapping; } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java index 027146b67..9805f4802 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java @@ -8,8 +8,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.Function; -import io.smallrye.common.annotation.Experimental; +import io.smallrye.config.common.utils.StringUtil; /** * This annotation may be placed in interfaces to group configuration properties with a common prefix. @@ -20,7 +21,7 @@ * @ConfigMapping(prefix = "server") * public interface Server { * public String host(); // maps the property name server.host - * + * * public int port(); // maps to the property name server.port * } * @@ -32,7 +33,6 @@ @Documented @Target({ FIELD, PARAMETER, TYPE }) @Retention(RetentionPolicy.RUNTIME) -@Experimental("ConfigMapping API to group configuration properties") public @interface ConfigMapping { /** * The prefix of the configuration properties. @@ -53,14 +53,28 @@ enum NamingStrategy { /** * The method name is used as is to map the configuration property. */ - VERBATIM, + VERBATIM(name -> name), /** * The method name is derived by replacing case changes with a dash to map the configuration property. */ - KEBAB_CASE, + KEBAB_CASE(name -> { + return StringUtil.skewer(name, '-'); + }), /** * The method name is derived by replacing case changes with an underscore to map the configuration property. */ - SNAKE_CASE + SNAKE_CASE(name -> { + return StringUtil.skewer(name, '_'); + }); + + private final Function function; + + private NamingStrategy(Function function) { + this.function = function; + } + + public String apply(final String name) { + return function.apply(name); + } } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java index f5b73fe37..384ed5a36 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java @@ -1,176 +1,93 @@ package io.smallrye.config; -import static io.smallrye.config.ConfigMappingInterface.LeafProperty; -import static io.smallrye.config.ConfigMappingInterface.MapProperty; -import static io.smallrye.config.ConfigMappingInterface.PrimitiveProperty; -import static io.smallrye.config.ConfigMappingInterface.Property; -import static io.smallrye.config.ConfigMappingInterface.getConfigurationInterface; -import static io.smallrye.config.ConfigMappingInterface.rawTypeOf; -import static io.smallrye.config.ConfigMappingInterface.typeOfParameter; import static io.smallrye.config.ConfigValidationException.Problem; +import static io.smallrye.config.common.utils.StringUtil.unindexed; +import static java.util.Collections.EMPTY_MAP; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.ConfigMappingInterface.CollectionProperty; -import io.smallrye.config.ConfigMappingInterface.NamingStrategy; +import io.smallrye.config.ConfigMapping.NamingStrategy; +import io.smallrye.config._private.ConfigMessages; /** * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration * mapping has completed. */ public final class ConfigMappingContext { - private final Map, Map>> enclosedThings = new IdentityHashMap<>(); + private final SmallRyeConfig config; private final Map, Map> roots = new IdentityHashMap<>(); - private final Map, Map
* {@link org.jboss.weld.exceptions.DeploymentException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... *
* {@link org.jboss.weld.exceptions.DeploymentException}: Exception List with n exceptions: * Exception 0 : - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * Exception n : - * {@link io.smallrye.config.inject.ConfigInjectionException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> + * {@link io.smallrye.config.inject.ConfigException}: SRCFG0200X: < a SmallRye {@link InjectionMessages}> (+ where appropriate) SRCFG0000X: < a SmallRye {@link ConfigMessages}> * ... * caused by: (where appropriate) * the.root.cause.Exception: SRCFG0000X: < a SmallRye {@link ConfigMessages}> *