From bd3861678443e58220907171277f8063143b4fed Mon Sep 17 00:00:00 2001 From: altro3 Date: Sun, 29 Sep 2024 11:15:40 +0700 Subject: [PATCH] Add ability to choose property catalog for EachProperty annotation --- .../micronaut/core/value/PropertyCatalog.java | 40 +++++++++++++++++ .../core/value/PropertyResolver.java | 23 ++++++++++ .../EachBeanParameterSpec.groovy | 45 +++++++++++++++++++ .../EachPropertyPropertiesDefault.java | 44 ++++++++++++++++++ .../EachPropertyPropertiesGenerated.java | 45 +++++++++++++++++++ .../EachPropertyPropertiesNormalized.java | 45 +++++++++++++++++++ .../EachPropertyPropertiesRaw.java | 45 +++++++++++++++++++ .../context/DefaultApplicationContext.java | 7 +++ .../context/annotation/EachProperty.java | 8 ++++ .../context/env/ConfigurationPath.java | 11 +++++ .../context/env/DefaultConfigurationPath.java | 12 ++++- .../env/PropertySourcePropertyResolver.java | 12 ++++- 12 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/io/micronaut/core/value/PropertyCatalog.java create mode 100644 inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesDefault.java create mode 100644 inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesGenerated.java create mode 100644 inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesNormalized.java create mode 100644 inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesRaw.java diff --git a/core/src/main/java/io/micronaut/core/value/PropertyCatalog.java b/core/src/main/java/io/micronaut/core/value/PropertyCatalog.java new file mode 100644 index 00000000000..3802068069f --- /dev/null +++ b/core/src/main/java/io/micronaut/core/value/PropertyCatalog.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2024 original authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micronaut.core.value; + +/** + * The property catalog to use. + * + * @since 4.7.0 + */ +public enum PropertyCatalog { + /** + * The catalog that contains the raw keys. + */ + RAW, + /** + * The catalog that contains normalized keys. A key is normalized into + * lower case hyphen separated form. For example an environment variable {@code FOO_BAR} would be + * normalized to {@code foo.bar}. + */ + NORMALIZED, + /** + * The catalog that contains normalized keys and also generated keys. A synthetic key can be generated from + * an environment variable such as {@code FOO_BAR_BAZ} which will produce the following keys: {@code foo.bar.baz}, + * {@code foo.bar-baz}, and {@code foo-bar.baz}. + */ + GENERATED +} diff --git a/core/src/main/java/io/micronaut/core/value/PropertyResolver.java b/core/src/main/java/io/micronaut/core/value/PropertyResolver.java index a2c7d80f17d..6b584f194d7 100644 --- a/core/src/main/java/io/micronaut/core/value/PropertyResolver.java +++ b/core/src/main/java/io/micronaut/core/value/PropertyResolver.java @@ -89,6 +89,29 @@ public interface PropertyResolver extends ValueResolver { return Collections.emptySet(); } + /** + * Returns a collection of properties entries under the given key, but . + * For example, if you set {@code PropertyCatalog.RAW} then the following keys: + * + *
+     * datasource.MyDs-1.url=localhost
+     * datasource.MyDs-2.url=someother
+     * 
+ * + * Calling {@code getPropertyEntries(String)} with a value of {@code datasource} will result in a collection + * containing {@code MyDs-1} and {@code MyDs-2} (without normalization). + * + * @param name The name to resolve + * @param propertyCatalog property catalog to use + * @return The property entries. + * + * @since 4.7.0 + */ + @NonNull + default Collection getPropertyEntries(@NonNull String name, @NonNull PropertyCatalog propertyCatalog) { + return Collections.emptySet(); + } + /** *

Resolve the given property for the given name, type and generic type arguments.

* diff --git a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachBeanParameterSpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachBeanParameterSpec.groovy index 45792975bfd..6fe53d8ae8f 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachBeanParameterSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachBeanParameterSpec.groovy @@ -3,6 +3,8 @@ package io.micronaut.inject.configproperties.eachbeanparameter import io.micronaut.annotation.processing.test.AbstractTypeElementSpec import io.micronaut.context.ApplicationContext import io.micronaut.context.Qualifier +import io.micronaut.context.env.PropertySource +import io.micronaut.context.env.SystemPropertiesPropertySource import io.micronaut.context.exceptions.NonUniqueBeanException import io.micronaut.inject.qualifiers.PrimaryQualifier import io.micronaut.inject.qualifiers.Qualifiers @@ -59,4 +61,47 @@ class EachBeanParameterSpec extends AbstractTypeElementSpec { cleanup: ctx.close() } + + void 'test disabled normalization property keys'() { + when: + Map datasourcesConfiguration = [ + 'app.raw.Raw_1.url': 'url1', + 'app.raw.Raw_2.url': 'url2', + 'app.normalized.Normalized_1.url': 'url1', + 'app.normalized.Normalized_2.url': 'url2', + 'app.default.Default_1.url': 'url1', + 'app.default.Default_2.url': 'url2', + 'app.generated.Generated_1.url': 'url1', + 'app.generated.Generated_2.url': 'url2', + ] + ApplicationContext ctx = ApplicationContext.builder() + .propertySources(PropertySource.of(PropertySource.CONTEXT, ['spec': 'DisabledNormalizationSpec'] + datasourcesConfiguration, SystemPropertiesPropertySource.POSITION + 100)) + .start() + + then: + ctx + + def eachPropertyPropertiesRawBeans = ctx.getBeansOfType(EachPropertyPropertiesRaw) + eachPropertyPropertiesRawBeans.size() == 2 + ctx.getBean(EachPropertyPropertiesRaw, Qualifiers.byName("Raw_1")).name == "Raw_1" + ctx.getBean(EachPropertyPropertiesRaw, Qualifiers.byName("Raw_2")).name == "Raw_2" + + def eachPropertyPropertiesNormalizedBeans = ctx.getBeansOfType(EachPropertyPropertiesNormalized) + eachPropertyPropertiesNormalizedBeans.size() == 2 + ctx.getBean(EachPropertyPropertiesNormalized, Qualifiers.byName("normalized-1")).name == "normalized-1" + ctx.getBean(EachPropertyPropertiesNormalized, Qualifiers.byName("normalized-2")).name == "normalized-2" + + def eachPropertyPropertiesDefaultBeans = ctx.getBeansOfType(EachPropertyPropertiesDefault) + eachPropertyPropertiesDefaultBeans.size() == 2 + ctx.getBean(EachPropertyPropertiesDefault, Qualifiers.byName("default-1")).name == "default-1" + ctx.getBean(EachPropertyPropertiesDefault, Qualifiers.byName("default-2")).name == "default-2" + + def eachPropertyPropertiesGeneratedBeans = ctx.getBeansOfType(EachPropertyPropertiesGenerated) + eachPropertyPropertiesGeneratedBeans.size() == 2 + ctx.getBean(EachPropertyPropertiesGenerated, Qualifiers.byName("generated-1")).name == "generated-1" + ctx.getBean(EachPropertyPropertiesGenerated, Qualifiers.byName("generated-2")).name == "generated-2" + + cleanup: + ctx.close() + } } diff --git a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesDefault.java b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesDefault.java new file mode 100644 index 00000000000..48d308aabd4 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesDefault.java @@ -0,0 +1,44 @@ +package io.micronaut.inject.configproperties.eachbeanparameter; + +import io.micronaut.context.annotation.ConfigurationInject; +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Requires; + +@Requires(property = "spec", value = "DisabledNormalizationSpec") +@EachProperty(value = "app.default") +public class EachPropertyPropertiesDefault { + + private String name; + private String url; + private int serverPort; + + @ConfigurationInject + public EachPropertyPropertiesDefault(@Parameter String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesGenerated.java b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesGenerated.java new file mode 100644 index 00000000000..407186bd390 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesGenerated.java @@ -0,0 +1,45 @@ +package io.micronaut.inject.configproperties.eachbeanparameter; + +import io.micronaut.context.annotation.ConfigurationInject; +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.value.PropertyCatalog; + +@Requires(property = "spec", value = "DisabledNormalizationSpec") +@EachProperty(value = "app.generated", catalog = PropertyCatalog.GENERATED) +public class EachPropertyPropertiesGenerated { + + private String name; + private String url; + private int serverPort; + + @ConfigurationInject + public EachPropertyPropertiesGenerated(@Parameter String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesNormalized.java b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesNormalized.java new file mode 100644 index 00000000000..bb2ef150494 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesNormalized.java @@ -0,0 +1,45 @@ +package io.micronaut.inject.configproperties.eachbeanparameter; + +import io.micronaut.context.annotation.ConfigurationInject; +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.value.PropertyCatalog; + +@Requires(property = "spec", value = "DisabledNormalizationSpec") +@EachProperty(value = "app.normalized", catalog = PropertyCatalog.NORMALIZED) +public class EachPropertyPropertiesNormalized { + + private String name; + private String url; + private int serverPort; + + @ConfigurationInject + public EachPropertyPropertiesNormalized(@Parameter String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesRaw.java b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesRaw.java new file mode 100644 index 00000000000..a7f919378f4 --- /dev/null +++ b/inject-java/src/test/groovy/io/micronaut/inject/configproperties/eachbeanparameter/EachPropertyPropertiesRaw.java @@ -0,0 +1,45 @@ +package io.micronaut.inject.configproperties.eachbeanparameter; + +import io.micronaut.context.annotation.ConfigurationInject; +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.value.PropertyCatalog; + +@Requires(property = "spec", value = "DisabledNormalizationSpec") +@EachProperty(value = "app.raw", catalog = PropertyCatalog.RAW) +public class EachPropertyPropertiesRaw { + + private String name; + private String url; + private int serverPort; + + @ConfigurationInject + public EachPropertyPropertiesRaw(@Parameter String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java b/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java index 4eeded5f72b..a5e735c2b2d 100644 --- a/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java +++ b/inject/src/main/java/io/micronaut/context/DefaultApplicationContext.java @@ -43,6 +43,7 @@ import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArgumentUtils; import io.micronaut.core.util.StringUtils; +import io.micronaut.core.value.PropertyCatalog; import io.micronaut.inject.BeanConfiguration; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.BeanDefinitionReference; @@ -253,6 +254,12 @@ public Collection getPropertyEntries(@NonNull String name) { return getEnvironment().getPropertyEntries(name); } + @NonNull + @Override + public Collection getPropertyEntries(@NonNull String name, @NonNull PropertyCatalog propertyCatalog) { + return getEnvironment().getPropertyEntries(name, propertyCatalog); + } + @NonNull @Override public Map getProperties(@Nullable String name, @Nullable StringConvention keyFormat) { diff --git a/inject/src/main/java/io/micronaut/context/annotation/EachProperty.java b/inject/src/main/java/io/micronaut/context/annotation/EachProperty.java index ed667078c3c..1db371dbfeb 100644 --- a/inject/src/main/java/io/micronaut/context/annotation/EachProperty.java +++ b/inject/src/main/java/io/micronaut/context/annotation/EachProperty.java @@ -15,6 +15,7 @@ */ package io.micronaut.context.annotation; +import io.micronaut.core.value.PropertyCatalog; import jakarta.inject.Singleton; import java.lang.annotation.Documented; @@ -92,6 +93,13 @@ @AliasFor(annotation = ConfigurationReader.class, member = ConfigurationReader.PREFIX) String value(); + /** + * @return property catalog to use. By default, uses NORMALIZATION catalog + * + * @since 4.7.0 + */ + PropertyCatalog catalog() default PropertyCatalog.NORMALIZED; + /** * @return The name of the key returned by {@link #value()} that should be regarded as the {@link Primary} bean */ diff --git a/inject/src/main/java/io/micronaut/context/env/ConfigurationPath.java b/inject/src/main/java/io/micronaut/context/env/ConfigurationPath.java index 1cddb9fa4d8..f60f9b5bb08 100644 --- a/inject/src/main/java/io/micronaut/context/env/ConfigurationPath.java +++ b/inject/src/main/java/io/micronaut/context/env/ConfigurationPath.java @@ -20,6 +20,7 @@ import io.micronaut.context.annotation.EachProperty; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.value.PropertyCatalog; import io.micronaut.core.value.PropertyResolver; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.qualifiers.Qualifiers; @@ -120,6 +121,16 @@ static ConfigurationPath of(BeanDefinition... definitions) { */ int index(); + /** + * @return the current property catalog + * + * @since 4.7.0 + */ + @NonNull + default PropertyCatalog propertyCatalog() { + return PropertyCatalog.NORMALIZED; + } + /** * @return The qualifier. * @param The bean type diff --git a/inject/src/main/java/io/micronaut/context/env/DefaultConfigurationPath.java b/inject/src/main/java/io/micronaut/context/env/DefaultConfigurationPath.java index f5184353132..031511e159d 100644 --- a/inject/src/main/java/io/micronaut/context/env/DefaultConfigurationPath.java +++ b/inject/src/main/java/io/micronaut/context/env/DefaultConfigurationPath.java @@ -19,6 +19,7 @@ import io.micronaut.context.annotation.EachProperty; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.util.StringUtils; +import io.micronaut.core.value.PropertyCatalog; import io.micronaut.core.value.PropertyResolver; import io.micronaut.inject.BeanDefinition; @@ -38,6 +39,7 @@ final class DefaultConfigurationPath implements ConfigurationPath { private final LinkedList list = new LinkedList<>(); private String computedPrefix; private boolean hasDynamicSegments = false; + private PropertyCatalog propertyCatalog; DefaultConfigurationPath() { recomputeState(); @@ -140,6 +142,12 @@ public int index() { return -1; } + @NonNull + @Override + public PropertyCatalog propertyCatalog() { + return propertyCatalog; + } + @Override public String simpleName() { ConfigurationSegment segment = peekLast(); @@ -198,7 +206,7 @@ private static void traversePath(ConfigurationPath thisPath, PropertyResolver pr ConfigurationSegment.ConfigurationKind kind = thisPath.kind(); switch (kind) { case MAP -> { - Collection entries = propertyResolver.getPropertyEntries(thisPath.prefix()); + Collection entries = propertyResolver.getPropertyEntries(thisPath.prefix(), thisPath.propertyCatalog()); for (String key : entries) { ConfigurationPath newPath = thisPath.copy(); newPath.pushConfigurationSegment(key); @@ -258,6 +266,8 @@ public void pushEachPropertyRoot(@NonNull BeanDefinition beanDefinition) { this.hasDynamicSegments = true; } + propertyCatalog = beanDefinition.enumValue(EachProperty.class, "catalog", PropertyCatalog.class).orElse(PropertyCatalog.NORMALIZED); + boolean isList = beanDefinition.booleanValue(EachProperty.class, "list").orElse(false); String prefix = beanDefinition.stringValue(ConfigurationReader.class, ConfigurationReader.PREFIX).orElse(null); if (prefix != null) { diff --git a/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java b/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java index 60d32870e5e..88f917ba9ec 100644 --- a/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java +++ b/inject/src/main/java/io/micronaut/context/env/PropertySourcePropertyResolver.java @@ -213,10 +213,16 @@ public boolean containsProperties(@Nullable String name) { @NonNull @Override public Collection getPropertyEntries(@NonNull String name) { + return getPropertyEntries(name, io.micronaut.core.value.PropertyCatalog.NORMALIZED); + } + + @NonNull + @Override + public Collection getPropertyEntries(@NonNull String name, @NonNull io.micronaut.core.value.PropertyCatalog propertyCatalog) { if (StringUtils.isEmpty(name)) { return Collections.emptySet(); } - Map entries = resolveEntriesForKey(name, false, PropertyCatalog.NORMALIZED); + Map entries = resolveEntriesForKey(name, false, PropertyCatalog.valueOf(propertyCatalog.name())); if (entries == null) { return Collections.emptySet(); } @@ -835,10 +841,12 @@ public void close() throws Exception { } } - /** * The property catalog to use. + * + * @deprecated Replaced by {@link io.micronaut.core.value.PropertyCatalog} */ + @Deprecated(forRemoval = true) protected enum PropertyCatalog { /** * The catalog that contains the raw keys.