From 0dccb68e5dd6693c82d91449d379f01e22ce4bbf Mon Sep 17 00:00:00 2001 From: Mark Struberg Date: Tue, 4 Sep 2018 19:58:30 +0200 Subject: [PATCH 1/4] fix wording. --- .../org/eclipse/microprofile/config/Config.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/eclipse/microprofile/config/Config.java b/api/src/main/java/org/eclipse/microprofile/config/Config.java index 3136d4c7..22c4e5bc 100644 --- a/api/src/main/java/org/eclipse/microprofile/config/Config.java +++ b/api/src/main/java/org/eclipse/microprofile/config/Config.java @@ -26,6 +26,9 @@ * Extracted the Config part out of Apache DeltaSpike and proposed as Microprofile-Config * 2016-11-14 - Emily Jiang / IBM Corp * Experiments with separate methods per type, JavaDoc, method renaming + * 2018-04-04 - Mark Struberg, Manfred Huber, Alex Falb, Gerhard Petracek + * ConfigSnapshot added. Initially authored in Apache DeltaSpike fdd1e3dcd9a12ceed831dd + * Additional reviews and feedback by Tomas Langer. * *******************************************************************************/ @@ -44,13 +47,11 @@ *

If multiple {@link ConfigSource ConfigSources} are specified with * the same ordinal, the {@link ConfigSource#getName()} will be used for sorting. *

- * The config objects produced via the injection model

@Inject Config
are guaranteed to be serializable, while + * The config objects produced via the injection model {@code @Inject Config} are guaranteed to be serializable, while * the programmatically created ones are not required to be serializable. *

- * If one or more converters are registered for a class of a requested value then one of the registered converters - * which has the highest priority is used to convert the string value retrieved from config sources. - * The highest priority means the highest priority number. - * For more information about converters, see {@link org.eclipse.microprofile.config.spi.Converter} + * If one or more converters are registered for a class of a requested value then the registered {@link org.eclipse.microprofile.config.spi.Converter} + * which has the highest {@code @javax.annotation.Priority} is used to convert the string value retrieved from the config sources. * *

Usage

* @@ -90,7 +91,7 @@ public interface Config { /** * Return the resolved property value with the specified type for the * specified property name from the underlying {@link ConfigSource ConfigSources}. - * + * * If this method gets used very often then consider to locally store the configured value. * * @param @@ -108,7 +109,7 @@ public interface Config { /** * Return the resolved property value with the specified type for the * specified property name from the underlying {@link ConfigSource ConfigSources}. - * + * * If this method is used very often then consider to locally store the configured value. * * @param From a0ae5959c77a5cf1db66553a79a394821baab874 Mon Sep 17 00:00:00 2001 From: Mark Struberg Date: Tue, 4 Sep 2018 21:50:42 +0200 Subject: [PATCH 2/4] This feature got jointly developed in the DeltaSpike and ConfigJSR projects. All ALv2, authors noted in source and NOTICE files. --- api/pom.xml | 5 +- .../eclipse/microprofile/config/Config.java | 74 ++++- .../microprofile/config/ConfigAccessor.java | 253 ++++++++++++++++++ .../microprofile/config/ConfigSnapshot.java | 42 +++ .../config/inject/ConfigProperty.java | 23 ++ .../microprofile/config/package-info.java | 2 +- .../microprofile/config/spi/ConfigSource.java | 28 +- api/src/main/resources/META-INF/NOTICE | 2 + 8 files changed, 419 insertions(+), 10 deletions(-) create mode 100644 api/src/main/java/org/eclipse/microprofile/config/ConfigAccessor.java create mode 100644 api/src/main/java/org/eclipse/microprofile/config/ConfigSnapshot.java diff --git a/api/pom.xml b/api/pom.xml index d98a82ba..c2ed4918 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -96,13 +96,15 @@ + diff --git a/api/src/main/java/org/eclipse/microprofile/config/Config.java b/api/src/main/java/org/eclipse/microprofile/config/Config.java index 22c4e5bc..9e1fed02 100644 --- a/api/src/main/java/org/eclipse/microprofile/config/Config.java +++ b/api/src/main/java/org/eclipse/microprofile/config/Config.java @@ -64,6 +64,11 @@ * Integer archivePort = cfg.getValue("my.project.archive.port", Integer.class); * * + *

For accessing a configuration in a dynamic way you can also use {@link #access(String)}. + * This method returns a builder-style {@link ConfigAccessor} instance for the given key. + * You can further specify a Type of the underlying configuration, a cache time, lookup paths and + * many more. + * *

It is also possible to inject the Config if a DI container is available: * *

@@ -73,7 +78,7 @@
  * }
  * 
* - *

See {@link #getValue(String, Class)} and {@link #getOptionalValue(String, Class)} for accessing a configured value. + *

See {@link #getValue(String, Class)} and {@link #getOptionalValue(String, Class)} and {@link #access(String)} for accessing a configured value. * *

Configured values can also be accessed via injection. * See {@link org.eclipse.microprofile.config.inject.ConfigProperty} for more information. @@ -83,6 +88,8 @@ * @author Ron Smeral * @author Emily Jiang * @author Gunnar Morling + * @author Manfred Huber + * @author Alex Falb * */ @org.osgi.annotation.versioning.ProviderType @@ -94,8 +101,9 @@ public interface Config { * * If this method gets used very often then consider to locally store the configured value. * - * @param - * The property type + *

Note that no variable replacement like in {@link ConfigAccessor#evaluateVariables(boolean)} will be performed! + * + * @param The property type * @param propertyName * The configuration propertyName. * @param propertyType @@ -112,8 +120,9 @@ public interface Config { * * If this method is used very often then consider to locally store the configured value. * - * @param - * The property type + *

Note that no variable replacement like in {@link ConfigAccessor#evaluateVariables(boolean)} will be performed! + * + * @param The property type * @param propertyName * The configuration propertyName. * @param propertyType @@ -125,7 +134,60 @@ public interface Config { Optional getOptionalValue(String propertyName, Class propertyType); /** - * Return a collection of property names. + * Create a {@link ConfigAccessor} to access the underlying configuration. + * + * @param propertyName the property key + * @return a {@code ConfigAccessor} to access the given propertyName + */ + ConfigAccessor access(String propertyName); + + /** + *

This method can be used to access multiple + * {@link ConfigAccessor} which must be consistent. + * The returned {@link ConfigSnapshot} is an immutable object which contains all the + * resolved values at the time of calling this method. + * + *

An example would be to access some {@code 'myapp.host'} and {@code 'myapp.port'}: + * The underlying values are {@code 'oldserver'} and {@code '8080'}. + * + *

+     *     // get the current host value
+     *     ConfigAccessor<String> hostCfg config.resolve("myapp.host")
+     *              .cacheFor(60, TimeUnit.MINUTES);
+     *
+     *     // and right inbetween the underlying values get changed to 'newserver' and port 8082
+     *
+     *     // get the current port for the host
+     *     ConfigAccessor<Integer> portCfg config.resolve("myapp.port")
+     *              .as(Integer.class)
+     *              .cacheFor(60, TimeUnit.MINUTES);
+     * 
+ * + * In ths above code we would get the combination of {@code 'oldserver'} but with the new port {@code 8081}. + * And this will obviously blow up because that host+port combination doesn't exist. + * + * To consistently access n different config values we can start a {@link ConfigSnapshot} for those values. + * + *
+     *     ConfigSnapshot cfgSnap = config.createSnapshot(hostCfg, portCfg);
+     *
+     *     String host = hostCfg.getValue(cfgSnap);
+     *     Integer port = portCfg.getValue(cfgSnap);
+     * 
+ * + * Note that there is no close on the snapshot. + * They should be used as local variables inside a method. + * Values will not be reloaded for an open {@link ConfigSnapshot}. + * + * @param configValues the list of {@link ConfigAccessor} to be accessed in an atomic way + * + * @return a new {@link ConfigSnapshot} which holds the resolved values of all the {@code configValues}. + */ + ConfigSnapshot snapshotFor(ConfigAccessor... configValues); + + + /** + * Return all property names used in any of the underlying {@link ConfigSource ConfigSources}. * @return the names of all configured keys of the underlying configuration. */ Iterable getPropertyNames(); diff --git a/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessor.java b/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessor.java new file mode 100644 index 00000000..36482bce --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessor.java @@ -0,0 +1,253 @@ +/* + ****************************************************************************** + * Copyright (c) 2009-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * http://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. + * + * Contributors: + * 2011-12-28 - Mark Struberg & Gerhard Petracek + * Contributed to Apache DeltaSpike fb0131106481f0b9a8fd + * 2016-07-07 - Mark Struberg + * Extracted the Config part out of DeltaSpike and proposed as Microprofile-Config 8ff76eb3bcfaf4fd + * + *******************************************************************************/ +package org.eclipse.microprofile.config; + + +import org.eclipse.microprofile.config.spi.Converter; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + + +/** + * Accessor to a configured value. + * + * It follows a builder-like pattern to define in which ways to access the configured value + * of a certain property name. + * + * Accessing the configured value is finally done via {@link #getValue()} + * + * @author Mark Struberg + * @author Gerhard Petracek + * @author Tomas Langer + */ +public interface ConfigAccessor { + + /** + * Sets the type of the configuration entry to the given class and returns this builder. + * The default type of a ConfigAccessor is {@code String}. + * + *

Usage: + *

+     * Integer timeout = config.access("some.timeout")
+     *                         .as(Integer.class)
+     *                         .getValue();
+     * 
+ * + * Attention: this method should always be the first to be used and might + * return a new {@code ConfigAccessor} for the given target clazz. + * + * + * @param clazz The target type + * @param The target type + * @return This builder as a typed ConfigAccessor + */ + ConfigAccessor as(Class clazz); + + /** + * Declare the ConfigAccessor to return a List of the given Type. + * When getting value it will be split on each comma (',') character. + * If a comma is contained in the values it must get escaped with a preceding backslash ("\,"). + * Any backslash needs to get escaped via double-backslash ("\\"). + * Note that in property files this leads to "\\\\" as properties escape themselves. + * + * @return a ConfigAccessor for a list of configured comma separated values + */ + ConfigAccessor> asList(); + + /** + * Declare the ConfigAccessor to return a Set of the given Type. + * The notation and escaping rules are the same like explained in {@link #asList()} + * + * @return a ConfigAccessor for a list of configured comma separated values + */ + ConfigAccessor> asSet(); + + /** + * Defines a specific {@link Converter} to be used instead of applying the default Converter resolving logic. + * + * @param converter The converter for the target type + * @return This builder as a typed ConfigAccessor + */ + ConfigAccessor useConverter(Converter converter); + + /** + * Sets the default value to use in case the resolution returns null. + * @param value the default value + * @return This builder + */ + ConfigAccessor withDefault(T value); + + /** + * Sets the default value to use in case the resolution returns null. Converts the given String to the type of + * this resolver using the same method as used for the configuration entries. + * @param value string value to be converted and used as default + * @return This builder + */ + ConfigAccessor withStringDefault(String value); + + /** + * Specify that a resolved value will get cached for a certain maximum amount of time. + * After the time expires the next {@link #getValue()} will again resolve the value + * from the underlying {@link org.eclipse.microprofile.config.Config}. + * + * Note that that the cache will get flushed if a {@code ConfigSource} notifies + * the underlying {@link Config} about a value change. + * This is done by invoking the callback provided to the {@code ConfigSource} via + * {@link org.eclipse.microprofile.config.spi.ConfigSource#setOnAttributeChange(java.util.function.Consumer)}. + * + * @param value the amount of the TimeUnit to wait + * @param timeUnit the TimeUnit for the value + * @return This builder + */ + ConfigAccessor cacheFor(long value, TimeUnit timeUnit); + + /** + * Whether to evaluate variables in configured values. + * A variable starts with '${' and ends with '}', e.g. + *
+     * mycompany.some.url=${myserver.host}/some/path
+     * myserver.host=http://localhost:8081
+     * 
+ * If 'evaluateVariables' is enabled, the result for the above key + * {@code "mycompany.some.url"} would be: + * {@code "http://localhost:8081/some/path"} + * + *

ATTENTION: This defaults to {@code true}! That means variable replacement is enabled by default!

+ * + * @param evaluateVariables whether to evaluate variables in values or not + * + * @return This builder + */ + ConfigAccessor evaluateVariables(boolean evaluateVariables); + + /** + * The methods {@link #addLookupSuffix(String)} and {@link #addLookupSuffix(ConfigAccessor)} + * append the given parameters as optional suffixes to the {@link #getPropertyName()}. + * Those methods can be called multiple times. + * Each time the given suffix will be added to the end of suffix chain. + * + * This very version + * + *

Usage: + *

+     * String tenant = getCurrentTenant();
+     *
+     * Integer timeout = config.access("some.server.url")
+     *                         .addLookupSuffix(tenant)
+     *                         .addLookupSuffix(config.access("javax.config.projectStage"))
+     *                         .getValue();
+     * 
+ * + * Given the current tenant name is 'myComp' and the property + * {@code javaconfig.projectStage} is 'Production' this would lead to the following lookup order: + * + *
    + *
  • "some.server.url.myComp.Production"
  • + *
  • "some.server.url.myComp"
  • + *
  • "some.server.url.Production"
  • + *
  • "some.server.url"
  • + *
+ * + * The algorithm to use in {@link #getValue()} is a binary count down. + * Every parameter is either available (1) or not (0). + * Having 3 parameters, we start with binary {@code 111} and count down to zero. + * The first combination which resolves to a result is being treated as result. + * + * @param suffixValue fixed String to be used as suffix + * @return This builder + */ + ConfigAccessor addLookupSuffix(String suffixValue); + + /** + * + * @param suffixAccessor {@link ConfigAccessor} to be used to resolve the suffix. + * @return This builder + * @see #addLookupSuffix(String) + */ + ConfigAccessor addLookupSuffix(ConfigAccessor suffixAccessor); + + /** + * Returns the converted resolved filtered value. + * @return the resolved value + * + * @throws IllegalArgumentException if the property cannot be converted to the specified type. + * @throws java.util.NoSuchElementException if the property isn't present in the configuration. + */ + T getValue(); + + + /** + * Returns the value from a previously taken {@link ConfigSnapshot}. + * + * @param configSnapshot previously taken via {@link Config#snapshotFor(ConfigAccessor[])} + * @return the resolved Value + * @see Config#snapshotFor(ConfigAccessor...) + * @throws IllegalArgumentException if the {@link ConfigSnapshot} hasn't been resolved + * for this {@link ConfigAccessor} + */ + T getValue(ConfigSnapshot configSnapshot); + + /** + * Returns the converted resolved filtered value. + * @return the resolved value as Optional + * + * @throws IllegalArgumentException if the property cannot be converted to the specified type. + */ + Optional getOptionalValue(); + + /** + * Returns the property name key given in {@link Config#access(String)}. + * @return the original property name + */ + String getPropertyName(); + + /** + * Returns the actual key which led to successful resolution and corresponds to the resolved value. + * This is useful when {@link #addLookupSuffix(String)} is used. + * Otherwise the resolved key should always be equal to the original key. + * This method is provided for cases, when parameterized resolution is + * requested and some of the fallback keys is used. + * + * This should be called only after calling {@link #getValue()} otherwise the value is undefined (but likely + * null). + * + * Note that this will only give you the resolved key from the last non-cached value resolving. + * @return the actual property name which led to successful resolution and corresponds to the resolved value. + */ + String getResolvedPropertyName(); + + /** + * Returns the default value provided by {@link #withDefault(Object)} or {@link #withStringDefault(String)}. + * + * @return the default value or {@code null} if no default was provided. + */ + T getDefaultValue(); + +} diff --git a/api/src/main/java/org/eclipse/microprofile/config/ConfigSnapshot.java b/api/src/main/java/org/eclipse/microprofile/config/ConfigSnapshot.java new file mode 100644 index 00000000..9eaa3e30 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/config/ConfigSnapshot.java @@ -0,0 +1,42 @@ +/* + ******************************************************************************* + * Copyright (c) 2011-2018 Contributors to the Eclipse Foundation and others + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * http://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. + * + * Contributors: + * 2018-04-04 - Mark Struberg, Manfred Huber, Alex Falb, Gerhard Petracek + * Initially authored in Apache DeltaSpike as ConfigSnapshot fdd1e3dcd9a12ceed831dd7460492b6dd788721c + * Additional reviews and feedback by Tomas Langer. + * + *******************************************************************************/ + +package org.eclipse.microprofile.config; + +/** + * A value holder for ConfigAccessor values which all got resolved in a guaranteed atomic way. + * + * @see Config#snapshotFor(ConfigAccessor...) + * @see ConfigAccessor#getValue(ConfigSnapshot) + * + * @author Mark Struberg + * @author Manfred Huber + * @author Alex Falb + * @author Gerhard Petracek + * @author Romain Manni-Bucau + */ +public interface ConfigSnapshot { +} diff --git a/api/src/main/java/org/eclipse/microprofile/config/inject/ConfigProperty.java b/api/src/main/java/org/eclipse/microprofile/config/inject/ConfigProperty.java index 2c156731..7896d343 100644 --- a/api/src/main/java/org/eclipse/microprofile/config/inject/ConfigProperty.java +++ b/api/src/main/java/org/eclipse/microprofile/config/inject/ConfigProperty.java @@ -27,6 +27,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; import javax.enterprise.util.Nonbinding; import javax.inject.Qualifier; @@ -96,6 +97,7 @@ * @author Ondrej Mihalyi * @author Emily Jiang * @author Mark Struberg + * @author Tomas Langer */ @Qualifier @Retention(RUNTIME) @@ -125,4 +127,25 @@ */ @Nonbinding String defaultValue() default UNCONFIGURED_VALUE; + + /** + * @see org.eclipse.microprofile.config.ConfigAccessor#evaluateVariables(boolean) + * @return whether variable replacement is enabled. Defaults to {@code true}. + */ + @Nonbinding + boolean evaluateVariables() default true; + + /** + * Only valid for injection of dynamically readable values, e.g. {@code Provider}! + * @return {@code TimeUnit} for {@link #cacheFor()} + */ + @Nonbinding + TimeUnit cacheTimeUnit() default TimeUnit.SECONDS; + + /** + * Only valid for injection of dynamically readable values, e.g. {@code Provider}! + * @return how long should dynamic values be locally cached. Measured in {@link #cacheTimeUnit()}. + */ + @Nonbinding + long cacheFor() default 0L; } diff --git a/api/src/main/java/org/eclipse/microprofile/config/package-info.java b/api/src/main/java/org/eclipse/microprofile/config/package-info.java index 152cc24a..b108eb42 100644 --- a/api/src/main/java/org/eclipse/microprofile/config/package-info.java +++ b/api/src/main/java/org/eclipse/microprofile/config/package-info.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (c) 2016-2017 Contributors to the Eclipse Foundation + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. diff --git a/api/src/main/java/org/eclipse/microprofile/config/spi/ConfigSource.java b/api/src/main/java/org/eclipse/microprofile/config/spi/ConfigSource.java index 449f4f9f..490dd910 100644 --- a/api/src/main/java/org/eclipse/microprofile/config/spi/ConfigSource.java +++ b/api/src/main/java/org/eclipse/microprofile/config/spi/ConfigSource.java @@ -1,6 +1,6 @@ /* ****************************************************************************** - * Copyright (c) 2009-2017 Contributors to the Eclipse Foundation + * Copyright (c) 2009-2018 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -32,6 +32,7 @@ import java.util.Map; import java.util.Set; +import java.util.function.Consumer; /** *

Implement this interfaces to provide a ConfigSource. @@ -42,7 +43,16 @@ * The default config sources always available by default are: *

    *
  1. System properties (ordinal=400)
  2. - *
  3. Environment properties (ordinal=300)
  4. + *
  5. Environment properties (ordinal=300) + *

    Depending on the operating system type, environment variables with '.' are not always allowed. + * This ConfigSource searches 3 environment variables for a given property name (e.g. {@code "com.ACME.size"}):

    + *
      + *
    1. Exact match (i.e. {@code "com.ACME.size"})
    2. + *
    3. Replace all '.' by '_' (i.e. {@code "com_ACME_size"})
    4. + *
    5. Replace all '.' by '_' and convert to upper case (i.e. {@code "COM_ACME_SIZE"})
    6. + *
    + *

    The first environment variable that is found is returned by this ConfigSource.

    + *
  6. *
  7. /META-INF/microprofile-config.properties (ordinal=100)
  8. *
* @@ -56,6 +66,10 @@ *

Adding a dynamic amount of custom config sources can be done programmatically via * {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider}. * + *

If a ConfigSource implements the {@link AutoCloseable} interface + * then the {@link AutoCloseable#close()} method will be called when + * the underlying {@link org.eclipse.microprofile.config.Config} is being released. + * * @author Mark Struberg * @author Gerhard Petracek * @author Emily Jiang @@ -146,4 +160,14 @@ default int getOrdinal() { */ String getName(); + /** + * This callback should get invoked if an attribute change got detected inside the ConfigSource. + * + * @param reportAttributeChange will be set by the {@link org.eclipse.microprofile.config.Config} after this + * {@code ConfigSource} got created and before any configured values + * get served. + */ + default void setOnAttributeChange(Consumer> reportAttributeChange) { + // do nothing by default. Just for compat with older ConfigSources. + } } diff --git a/api/src/main/resources/META-INF/NOTICE b/api/src/main/resources/META-INF/NOTICE index 545dd5f7..1ac81145 100644 --- a/api/src/main/resources/META-INF/NOTICE +++ b/api/src/main/resources/META-INF/NOTICE @@ -24,4 +24,6 @@ Ron Smeral rsmeral@apache.org, Emily Jiang emijiang@uk.ibm.com, Ondrej Mihalyi ondrej.mihalyi@gmail.com, Gunnar Morling gunnar@hibernate.org +Manfred Huber manfred.huber@downdrown.at +Alex Falb elexx@apache.org From 442525fd8aafc08c3c77858c11096b930d5dba2d Mon Sep 17 00:00:00 2001 From: Mark Struberg Date: Tue, 4 Sep 2018 22:25:14 +0200 Subject: [PATCH 3/4] refs: #384 backport spec wording fixes and enhancements from ConfigJSR All changes originally by me, except 1 smallish fixes by Tomas Langer. --- spec/src/main/asciidoc/architecture.asciidoc | 12 ++-- .../src/main/asciidoc/configaccessor.asciidoc | 65 +++++++++++++++++ .../src/main/asciidoc/configexamples.asciidoc | 10 ++- spec/src/main/asciidoc/configsources.asciidoc | 12 +++- spec/src/main/asciidoc/converters.asciidoc | 70 +++++++++++++------ spec/src/main/asciidoc/license-alv2.asciidoc | 2 +- .../microprofile-config-spec.asciidoc | 4 +- spec/src/main/asciidoc/release_notes.asciidoc | 17 +++++ 8 files changed, 159 insertions(+), 33 deletions(-) create mode 100644 spec/src/main/asciidoc/configaccessor.asciidoc diff --git a/spec/src/main/asciidoc/architecture.asciidoc b/spec/src/main/asciidoc/architecture.asciidoc index e1a33aef..d279a71c 100644 --- a/spec/src/main/asciidoc/architecture.asciidoc +++ b/spec/src/main/asciidoc/architecture.asciidoc @@ -23,21 +23,21 @@ It also defines ways to extend the configuration mechanism itself via a SPI (Ser === Rationale -Released binaries often contain functionality which need to behave slightly differently depending on the deployment. -This might be different REST endpoints to talk to (e.g. depending on the customer for whom a WAR is deployed). +Released binaries often contain functionality which needs to behave slightly differently depending on the deployment. +This might be the port numbers and URLs of REST endpoints to talk to (e.g. depending on the customer for whom a WAR is deployed). Or it might even be whole features which need to be switched on and off depending on the installation. All this must be possible without the need to re-package the whole application binary. Microprofile-Config provides a way to achieve this goal by aggregating configuration from many different <> and presents a single merged view to the user. This allows the application to bundle default configuration within the application. -It also allows to override the defaults from outside, e.g. via an environment variable a Java system property or via Docker. +It also allows to override the defaults from outside, e.g. via an environment variable a Java system property or via a container like Docker. Microprofile-Config also allows to implement and register own configuration sources in a portable way, e.g. for reading configuration values from a shared database in an application cluster. Internally, the core Microprofile-Config mechanism is purely String/String based. -Type-safety is only provided on top of that by using the proper <> before handing the value out to the caller. +Type-safety is intentionally only provided on top of that by using the proper <> before handing the value out to the caller. -The configuration key might use dot-separated to prevent name conflicts. This is similar to Java package namespacing: +The configuration key might use dot-separated blocks to prevent name conflicts. This is similar to Java package namespacing: [source, text] ---- @@ -50,3 +50,5 @@ some.library.own.config=some value ---- +TIP: while the above example is in the java property file syntax the actual content could also e.g. be read from a database. + diff --git a/spec/src/main/asciidoc/configaccessor.asciidoc b/spec/src/main/asciidoc/configaccessor.asciidoc new file mode 100644 index 00000000..3fe8d264 --- /dev/null +++ b/spec/src/main/asciidoc/configaccessor.asciidoc @@ -0,0 +1,65 @@ +// +// Copyright (c) 2016-2018 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// 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 +// +// http://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. +// Contributors: +// Mark Struberg + +[[configaccessor]] +== ConfigAccessor + + +The `ConfigAccessor` API is intended for typed configuration values and precise control over resolution. + +=== ConfigAccessor Usage + +The simplest usage of the API is resolution of a String property, equivalent to a call to `Config.getValue(propertyKey, String.class)`. +This can also be handled via `ConfigAccessor` as shown in the following example: + +.Simple example of ConfigAccessor +[source,java] +----------------------------------------------------------------- +String userName = config.access("user.name").getValue(); +----------------------------------------------------------------- + +Additional to this the `ConfigAccessor` also allows for far more complex control over the access to configured values. + +The call to `config.access(..)` returns a `ConfigAccessor`. +This is basically a builder which has methods to refine the resolution, including the following: + +* `as(Class clazz)` -- defines the return type of the property +* `asList()` -- Declare the ConfigAccessor to return a List of the given Type. +* `evaluateVariables(boolean evaluateVariables)` +* `useConverter(Converter converter)` -- Defines a specific {@link Converter} to be used instead of applying the default Converter resolving logic. +* `withLookupSuffix(String postfixName)` -- sets a parameter for the resolution of a 'postfix' for the resolution +* `withLookupSuffix(ConfigAccessor)` -- you can also use a `ConfigAccessor` to determine the 'postfix' for the resolution +* `withDefault(T value)` -- sets the default value, used in case the resolution returns `null` +* `getValue()` -- returns the resolved value with the appropriate type + +.A more complete example of ConfigAccessor +[source,java] +----------------------------------------------------------------- +Config config = ConfigProvider.getConfig(); + +ConfigAccessor dbPortCfg + = config.access("db.port") + .as(Integer.class) + .addLookupSuffix(config.access("project.projectStage").cacheFor(12, TimeUnit.HOURS)) + .addLookupSuffix("database.vendor") + .withDefault(3306); +... +Integer port = dbPortCfg.getValue(); +----------------------------------------------------------------- diff --git a/spec/src/main/asciidoc/configexamples.asciidoc b/spec/src/main/asciidoc/configexamples.asciidoc index 67944fe5..fadad21d 100644 --- a/spec/src/main/asciidoc/configexamples.asciidoc +++ b/spec/src/main/asciidoc/configexamples.asciidoc @@ -20,10 +20,12 @@ // Emily Jiang [[configexamples]] -== Configuration Usage Examples +== Config Usage Examples -A configuration object can be obtained programmatically via the `ConfigProvider` or automatically via `@Inject Config`. An application can then access its configured values via a `Config` instance. +An application can obtain it's configuration programmatically via the `ConfigProvider`. +In CDI enabled beans it can also get injected via `@Inject Config`. +An application can then access its configured values via this `Config` instance. === Simple Programmatic Example @@ -42,7 +44,7 @@ public class ConfigUsageSample { } ---- -If you need to access a different server then you can e.g. change the configuration via a `-D` system property: +If you need to access a different server then you can e.g. change the configuration via a Java `-D` system property: [source, text] ---- @@ -77,12 +79,14 @@ public class InjectedConfigUsageSample { @Inject @ConfigProperty(name="myprj.some.url") private String someUrl; + //The following code injects an Optional value of myprj.some.port property. //Contrary to natively injecting the configured value this will not lead to a //DeploymentException if the configured value is missing. @Inject @ConfigProperty(name="myprj.some.port") private Optional somePort; + //Injects a Provider for the value of myprj.some.dynamic.timeout property to //resolve the property dynamically. Each invocation to Provider#get() will //resolve the latest value from underlying Config. diff --git a/spec/src/main/asciidoc/configsources.asciidoc b/spec/src/main/asciidoc/configsources.asciidoc index 5088d84d..a5612028 100644 --- a/spec/src/main/asciidoc/configsources.asciidoc +++ b/spec/src/main/asciidoc/configsources.asciidoc @@ -36,6 +36,13 @@ It can also be used to implement a drop-in configuration approach. Simply create a jar containing a `ConfigSource` with a higher ordinal and override configuration values in it. If the jar is present on the classpath then it will override configuration values from <> with lower `ordinal` values. +=== Manually defining the Ordinal of a built-in ConfigSource + +Note that a special property `config_ordinal` can be set within any built-in `ConfigSource` implementation. +The default implementation of `getOrdinal()` will attempt to read this value. +If found and a valid integer, the value will be used. +Otherwise the respective default value will be used. + [source, text] ---- config_ordinal = 120 @@ -43,8 +50,6 @@ com.acme.myproject.someserver.url = http://more_important.server/some/endpoint ---- -Note that `config_ordinal` can be set within any `ConfigSource` implementation. The default implementation of `getOrdinal()` will attempt to read this value, if found and a valid integer, the value will be used, otherwise the default of `100` will be returned. - [[default_configsources]] === Default ConfigSources @@ -142,6 +147,9 @@ public class ExampleYamlConfigSourceProvider Please note that a single `ConfigSource` should be either registered directly or via a `ConfigSourceProvider`, but never both ways. +=== Cleaning up a ConfigSource + +If a `ConfigSource` implements the `java.lang.AutoCloseable` interface then the `close()` method will be called when the underlying `Config` is being released. === ConfigSource and Mutable Data diff --git a/spec/src/main/asciidoc/converters.asciidoc b/spec/src/main/asciidoc/converters.asciidoc index a58d7849..ce0f3382 100644 --- a/spec/src/main/asciidoc/converters.asciidoc +++ b/spec/src/main/asciidoc/converters.asciidoc @@ -37,6 +37,7 @@ The following `Converter` s are provided by Microprofile-Config by default: * `float` and `Float` , a dot '.' is used to separate the fractional digits * `double` and `Double` , a dot '.' is used to separate the fractional digits * `Class` based on the result of `Class.forName` +* `URL` All built-in `Converter` have the `@Priority` of `1`. @@ -54,41 +55,68 @@ The `Config` will use the `Converter` with the highest `Priority` for each targe A custom `Converter` for a target type of any of the built-in Converters will overwrite the default Converter. Converters can be added to the `ConfigBuilder` programmatically via `ConfigBuilder#withConverters(Converter... converters)` -where the type of the converters can be obtained via reflection. However, this is not possible for a lambda converter. In this case, use the method `ConfigBuilder#withConverter(Class type, int priority, Converter converter)`. +where the type of the converters can be obtained via reflection. However, this is not possible for a lambda converter. +In this case, use the method `ConfigBuilder#withConverter(Class type, int priority, Converter converter)`. + +=== Implicit Converters + +If no built-in nor custom `Converter` for a requested Type `T`, an implicit Converter is automatically provided if the following conditions are met: + +!TODO rule option A: +* The target type {@code T} has a {@code public static T of(String)} method, or +* The target type {@code T} has a {@code public static T valueOf(String)} method, or +* The target type {@code T} has a public Constructor with a String parameter, or +* The target type {@code T} has a {@code public static T parse(CharSequence)} method + +!TODO rule option B: +* The target type `T` has a Constructor with a String parameter, or +* the target type `T` has a Constructor with a CharSequence parameter, or +* the target type `T` has a `static T valueOf(String)` method, or +* the target type `T` has a `static T valueOf(CharSequence)` method, or +* the target type `T` has a `static T parse(String)` method, or +* the target type `T` has a `static T parse(CharSequence)` method, or + +The lookup will be done in the order of the above list. + +Note that every `java.time` type has a `parse(CharSequence)` method and every enum has a generated 'valueOf(String)' method. +They are thus all covered by an implicit converter! + +If an Implicit Converter cannot convert a value, a `java.lang.IllegalArgumentException` is to be thrown. === Array Converters -For the built-in converters and custom converters, the corresponding Array converters are provided -by default. The delimiter for the config value is ",". The escape character is "\". -e.g. With this config `myPets=dog,cat,dog\\,cat`, the values as an array will be -`{"dog", "cat", "dog,cat"}`. +For the built-in converters and custom converters, the corresponding Array converters are provided by default. +The delimiter for the config value is ",". +The escape character is "\". +e.g. With this config `myPets=dog,cat,dog\,cat`, the values as an array will be `{"dog", "cat", "dog,cat"}`. -* Programmatic lookup - An array as a class is supported in the programmatic lookup. +==== Programmatic lookup +Array as a class type is supported in the programmatic lookup. +[source, java] ---- - private String[] myPets = config.getValue("myPets", String[].class); + String[] myPets = config.getValue("myPets", String[].class); ---- - myPets will be "dog", "cat", "dog,cat" as an array -* Injection model - For the property injection, Array, List and Set should be supported. +myPets will be "dog", "cat", "dog,cat" as an array + +==== Injection model +For the property injection, Array, List and Set are supported. +[source, java] ---- - @Inject @ConfigProperty(name="myPets") private String[] myArrayPets; - @Inject @ConfigProperty(name="myPets") private List myListPets; - @Inject @ConfigProperty(name="myPets") private Set mySetPets; +@Inject @ConfigProperty(name="myPets") String[] myPetsArray; +@Inject @ConfigProperty(name="myPets") List myPetsList; +@Inject @ConfigProperty(name="myPets") Set myPetsSet; ---- - myPets will be "dog", "cat", "dog,cat" as an array, List or Set. -=== Automatic Converters +myPets will be "dog", "cat", "dog,cat" as an array, List or Set. + +=== Cleaning up a Converter + +If a `Converter` implements the `java.lang.AutoCloseable` interface then the `close()` method will be called when the underlying `Config` is being released. -If no built-in nor custom `Converter` for a requested Type `T`, an implicit Converter is automatically provided if the following conditions are met: -* The target type {@code T} has a {@code public static T of(String)} method, or -* The target type {@code T} has a {@code public static T valueOf(String)} method, or -* The target type {@code T} has a public Constructor with a String parameter, or -* The target type {@code T} has a {@code public static T parse(CharSequence)} method diff --git a/spec/src/main/asciidoc/license-alv2.asciidoc b/spec/src/main/asciidoc/license-alv2.asciidoc index 8b17e6bd..3824b5b0 100644 --- a/spec/src/main/asciidoc/license-alv2.asciidoc +++ b/spec/src/main/asciidoc/license-alv2.asciidoc @@ -26,7 +26,7 @@ Status: {revremark} Release: {revdate} -Copyright (c) 2016-2017 Contributors to the Eclipse Foundation +Copyright (c) 2016-2018 Contributors to the Eclipse Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/spec/src/main/asciidoc/microprofile-config-spec.asciidoc b/spec/src/main/asciidoc/microprofile-config-spec.asciidoc index b27da67b..dba13e33 100644 --- a/spec/src/main/asciidoc/microprofile-config-spec.asciidoc +++ b/spec/src/main/asciidoc/microprofile-config-spec.asciidoc @@ -1,5 +1,5 @@ // -// Copyright (c) 2016-2017 Eclipse Microprofile Contributors: +// Copyright (c) 2016-2018 Eclipse Microprofile Contributors: // Mark Struberg // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,4 +46,6 @@ include::configsources.asciidoc[] include::converters.asciidoc[] +include::configaccessor.asciidoc[] + include::release_notes.asciidoc[] diff --git a/spec/src/main/asciidoc/release_notes.asciidoc b/spec/src/main/asciidoc/release_notes.asciidoc index 48fb3326..961935e6 100644 --- a/spec/src/main/asciidoc/release_notes.asciidoc +++ b/spec/src/main/asciidoc/release_notes.asciidoc @@ -108,3 +108,20 @@ More CTS were added: Java2 security related change (link:https://github.com/eclipse/microprofile-config/issues/343[#343]) + +[[release_notes_14]] +== Release Notes for MicroProfile Config 1.4 + +The following changes occurred in the 1.4 release, compared to 1.2 + +A full list of changes may be found on the link:https://github.com/eclipse/microprofile-config/milestone/5?closed=1[MicroProfile Config 1.4 Milestone] + +=== API/SPI Changes + +MicroProfile 1.4 introduced a way to deal with dynamic values. +To support this feature from the user side we introduced the `ConfigAccessor`. +We also introduced a way to have `ConfigSources` notify the `Config` about an underlying attribute change. + +=== Functional Changes + +OSGi compatibility got improved. \ No newline at end of file From a6717b5feddfe3e39bed51d3a659064298952cde Mon Sep 17 00:00:00 2001 From: Mark Struberg Date: Thu, 4 Oct 2018 13:42:15 +0200 Subject: [PATCH 4/4] refs: #384 backport tck for ConfigAccessor and fix TCK setup This also includes a fix to avoid OS specific hacks (envconfig.* properties) --- tck/running_the_tck.asciidoc | 16 +- .../tck/CDIPropertyNameMatchingTest.java | 70 ++--- .../config/tck/ConfigAccessorTest.java | 261 ++++++++++++++++++ .../config/tck/SimpleValuesBean.java | 52 ---- .../ConfigurableConfigSource.java | 80 ++++++ .../META-INF/microprofile-config.properties | 19 +- 6 files changed, 397 insertions(+), 101 deletions(-) create mode 100644 tck/src/main/java/org/eclipse/microprofile/config/tck/ConfigAccessorTest.java delete mode 100644 tck/src/main/java/org/eclipse/microprofile/config/tck/SimpleValuesBean.java create mode 100644 tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java diff --git a/tck/running_the_tck.asciidoc b/tck/running_the_tck.asciidoc index d1b53838..1ae9b49c 100644 --- a/tck/running_the_tck.asciidoc +++ b/tck/running_the_tck.asciidoc @@ -54,10 +54,10 @@ To enable the tests in your project you need to add the following dependency to Some tests are asserting statements related to environment variables. The following environment variable and their values must be present in the test runner environment: -* `my_int_property` with the value `45` -* `MY_BOOLEAN_PROPERTY` with the value `true` -* `my_string_property` with the value `haha` -* `MY_STRING_PROPERTY` with the value `woohoo` +* `envconfig_my_int_property` with the value `45` +* `ENVCONFIG_MY_BOOLEAN_PROPERTY` with the value `true` +* `envconfig_my_string_property` with the value `haha` +* `ENVCONFIG_MY_STRING_PROPERTY` with the value `woohoo` See below for an example configuration to provide these environment variables in a Maven pom.xml. @@ -97,10 +97,10 @@ If you use Apache Maven then the tests are run via the `maven-surefire-plugin` - 45 - true - haha - woohoo + 45 + true + haha + woohoo diff --git a/tck/src/main/java/org/eclipse/microprofile/config/tck/CDIPropertyNameMatchingTest.java b/tck/src/main/java/org/eclipse/microprofile/config/tck/CDIPropertyNameMatchingTest.java index ebd90078..105c2b68 100644 --- a/tck/src/main/java/org/eclipse/microprofile/config/tck/CDIPropertyNameMatchingTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/config/tck/CDIPropertyNameMatchingTest.java @@ -19,19 +19,21 @@ package org.eclipse.microprofile.config.tck; -import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import javax.enterprise.context.Dependent; import javax.inject.Inject; import javax.enterprise.inject.spi.CDI; -import org.eclipse.microprofile.config.Config; + +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.testng.Arquillian; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Assert; @@ -51,13 +53,16 @@ */ public class CDIPropertyNameMatchingTest extends Arquillian { - private @Inject Config config; @Deployment public static Archive deployment() { JavaArchive testJar = ShrinkWrap .create(JavaArchive.class, "CDIPropertyNameMatchingTest.jar") .addClasses(CDIPropertyNameMatchingTest.class, SimpleValuesBean.class) + .addAsManifestResource(new StringAsset( + "envconfig.my.int/property=3"+ + "\nenvconfig.my.string/property=fake"), + "javaconfig.properties") .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml") .as(JavaArchive.class); @@ -71,30 +76,17 @@ public static Archive deployment() { public void checkSetup() { //check whether the environment variables were populated by the executor correctly - if (!"45".equals(System.getenv("my_int_property"))) { - Assert.fail("Before running this test, the environment variable \"my_int_property\" must be set with the value of 45"); + if (!"45".equals(System.getenv("envconfig_my_int_property"))) { + Assert.fail("Before running this test, the environment variable \"envconfig_my_int_property\" must be set with the value of 45"); } - if (!"true".equals(System.getenv("MY_BOOLEAN_PROPERTY"))) { - Assert.fail("Before running this test, the environment variable \"MY_BOOLEAN_Property\" must be set with the value of true"); + if (!"true".equals(System.getenv("ENVCONFIG_MY_BOOLEAN_PROPERTY"))) { + Assert.fail("Before running this test, the environment variable \"ENVCONFIG_MY_BOOLEAN_PROPERTY\" must be set with the value of true"); } - - // Environment variables are case insensitive on Windows platforms - if(System.getProperty("os.name").contains("Windows")) { - String myStringProp = System.getenv("MY_STRING_PROPERTY"); - if (!"woohoo".equals(myStringProp) && !"haha".equals(myStringProp)) { - Assert.fail("Before running this test on a Windows platform, " + - "the environment variable \"MY_STRING_PROPERTY\" must be set with the value of woohoo or haha"); - } + if (!"haha".equals(System.getenv("envconfig_my_string_property"))) { + Assert.fail("Before running this test, the environment variable \"envconfig_my_string_property\" must be set with the value of haha"); } - else { // Not operating on Windows platform - if (!"haha".equals(System.getenv("my_string_property"))) { - Assert.fail("Before running this test on a non-Windows platform, " + - "the environment variable \"my_string_property\" must be set with the value of haha"); - } - if (!"woohoo".equals(System.getenv("MY_STRING_PROPERTY"))) { - Assert.fail("Before running this test on a non-Windows platform, " + - "the environment variable \"MY_STRING_PROPERTY\" must be set with the value of woohoo"); - } + if (!"woohoo".equals(System.getenv("ENVCONFIG_MY_STRING_PROPERTY"))) { + Assert.fail("Before running this test, the environment variable \"ENVCONFIG_MY_STRING_PROPERTY\" must be set with the value of woohoo"); } } @@ -103,17 +95,9 @@ public void checkSetup() { public void testPropertyFromEnvironmentVariables() { SimpleValuesBean bean = getBeanOfType(SimpleValuesBean.class); - // Environment variables are case insensitive on Windows platforms, use Config to - // retrieve the "os.name" System property. - if(config.getValue("os.name", String.class).contains("Windows")) { - assertThat(bean.getStringProperty(), anyOf(equalTo("haha"),equalTo("woohoo")) ); - } - else { // non-Windows - assertThat(bean.getStringProperty(), is(equalTo("haha"))); - } - - assertThat(bean.getBooleanProperty(), is(true)); - assertThat(bean.getIntProperty(), is(equalTo(45))); + assertThat(bean.stringProperty, is(equalTo("haha"))); + assertThat(bean.booleanProperty, is(true)); + assertThat(bean.intProperty, is(equalTo(45))); } @@ -121,4 +105,20 @@ public void testPropertyFromEnvironmentVariables() { private T getBeanOfType(Class beanClass) { return CDI.current().select(beanClass).get(); } + + @Dependent + public static class SimpleValuesBean { + + @Inject + @ConfigProperty(name="envconfig.my.string/property") + private String stringProperty; + + @Inject + @ConfigProperty(name="envconfig.my.boolean/property") + private boolean booleanProperty; + + @Inject + @ConfigProperty(name="envconfig.my.int/property") + private int intProperty; + } } diff --git a/tck/src/main/java/org/eclipse/microprofile/config/tck/ConfigAccessorTest.java b/tck/src/main/java/org/eclipse/microprofile/config/tck/ConfigAccessorTest.java new file mode 100644 index 00000000..86b08cba --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/config/tck/ConfigAccessorTest.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * http://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 org.eclipse.microprofile.config.tck; + +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigAccessor; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.tck.base.AbstractTest; +import org.eclipse.microprofile.config.tck.configsources.ConfigurableConfigSource; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * @author Mark Struberg + */ +public class ConfigAccessorTest extends Arquillian { + + private @Inject Config config; + + @Deployment + public static WebArchive deploy() { + JavaArchive testJar = ShrinkWrap + .create(JavaArchive.class, "configValueTest.jar") + .addPackage(AbstractTest.class.getPackage()) + .addClass(ConfigAccessorTest.class) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml") + .addAsServiceProvider(ConfigSource.class, ConfigurableConfigSource.class) + .as(JavaArchive.class); + + AbstractTest.addFile(testJar, "META-INF/microprofile-config.properties"); + + WebArchive war = ShrinkWrap + .create(WebArchive.class, "configValueTest.war") + .addAsLibrary(testJar); + return war; + } + + + @Test + public void testGetValue() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.key1").getValue(), "value1"); + } + + @Test + public void testGetValueWithDefault() { + ConfigAccessor cfga = config.access("tck.config.test.javaconfig.configvalue.withdefault.notexisting") + .as(Integer.class) + .withDefault(Integer.valueOf(1234)); + + Assert.assertEquals(cfga.getValue(), Integer.valueOf(1234)); + } + + @Test + public void testGetValueWithStringDefault() { + ConfigAccessor cfga = config.access("tck.config.test.javaconfig.configvalue.withdefault.notexisting") + .as(Integer.class) + .withStringDefault("1234"); + + Assert.assertEquals(cfga.getValue(), Integer.valueOf(1234)); + } + + /** + * Checks whether variable substitution works. + * The following situation is configured in javaconfig.properties: + *

+     * tck.config.variable.baseHost = some.host.name
+     * tck.config.variable.firstEndpoint = http://${tck.config.variable.baseHost}/endpointOne
+     * tck.config.variable.secondEndpoint = http://${tck.config.variable.baseHost}/endpointTwo
+     * 
+ */ + @Test + public void testVariableReplacement() { + Assert.assertEquals(config.access("tck.config.variable.firstEndpoint").getValue(), + "http://some.host.name/endpointOne"); + + Assert.assertEquals(config.access("tck.config.variable.secondEndpoint").getValue(), + "http://some.host.name/endpointTwo"); + + // variables in Config.getValue and getOptionalValue do not get evaluated otoh + Assert.assertEquals(config.getValue("tck.config.variable.firstEndpoint", String.class), + "http://${tck.config.variable.baseHost}/endpointOne"); + + Assert.assertEquals(config.getOptionalValue("tck.config.variable.firstEndpoint", String.class).get(), + "http://${tck.config.variable.baseHost}/endpointOne"); + } + + @Test + public void testLookupChain() { + // set the projectstage to 'Production' + ConfigurableConfigSource.configure(config, "javaconfig.projectStage", "Production"); + + /** + * 1 1 -> com.foo.myapp.mycorp.Production + * 1 0 -> com.foo.myapp.mycorp + * 0 1 -> com.foo.myapp.Production + * 0 0 -> com.foo.myapp + * + */ + ConfigAccessor cv = config.access("com.foo.myapp") + .addLookupSuffix("mycorp") + .addLookupSuffix(config.access("javaconfig.projectStage")); + + Assert.assertFalse(cv.getOptionalValue().isPresent()); + + ConfigurableConfigSource.configure(config, "com.foo.myapp", "TheDefault"); + Assert.assertEquals(cv.getValue(), "TheDefault"); + Assert.assertEquals(cv.getResolvedPropertyName(), "com.foo.myapp"); + + ConfigurableConfigSource.configure(config, "com.foo.myapp.Production", "BasicWithProjectStage"); + Assert.assertEquals(cv.getValue(), "BasicWithProjectStage"); + Assert.assertEquals(cv.getResolvedPropertyName(), "com.foo.myapp.Production"); + + ConfigurableConfigSource.configure(config, "com.foo.myapp.mycorp", "WithTenant"); + Assert.assertEquals(cv.getValue(), "WithTenant"); + Assert.assertEquals(cv.getResolvedPropertyName(), "com.foo.myapp.mycorp"); + + ConfigurableConfigSource.configure(config, "com.foo.myapp.mycorp.Production", "WithTenantAndProjectStage"); + Assert.assertEquals(cv.getValue(), "WithTenantAndProjectStage"); + Assert.assertEquals(cv.getResolvedPropertyName(), "com.foo.myapp.mycorp.Production"); + } + + @Test + public void testIntegerConverter() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.integer").as(Integer.class).getValue(), + Integer.valueOf(1234)); + } + + @Test + public void testLongConverter() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.long").as(Long.class).getValue(), + Long.valueOf(1234567890123456L)); + } + + @Test + public void testFloatConverter() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.float").as(Float.class).getValue(), + Float.valueOf(12.34f)); + } + + @Test + public void testDoubleonverter() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.double").as(Double.class).getValue(), + Double.valueOf(12.34567890123456)); + } + + @Test + public void testBooleanConverter() { + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true_uppercase").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true_mixedcase").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.false").as(Boolean.class).getValue(), + Boolean.FALSE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.one").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.zero").as(Boolean.class).getValue(), + Boolean.FALSE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.seventeen").as(Boolean.class).getValue(), + Boolean.FALSE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes_uppercase").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes_mixedcase").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.y").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.y_uppercase").as(Boolean.class).getValue(), + Boolean.TRUE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.no").as(Boolean.class).getValue(), + Boolean.FALSE); + + Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.no_mixedcase").as(Boolean.class).getValue(), + Boolean.FALSE); + + } + + @Test + public void testCacheFor() throws Exception { + String key = "tck.config.test.javaconfig.cachefor.key"; + System.setProperty(key, "firstvalue"); + ConfigAccessor val = config.access(key).cacheFor(30, TimeUnit.MILLISECONDS); + Assert.assertEquals(val.getValue(), "firstvalue"); + + // immediately change the value + System.setProperty(key, "secondvalue"); + + // we should still see the first value, because it is cached! + Assert.assertEquals(val.getValue(), "firstvalue"); + + // but now let's wait a bit + Thread.sleep(60); + Assert.assertEquals(val.getValue(), "secondvalue"); + } + + @Test + public void testDefaultValue() { + String key = "tck.config.test.javaconfig.somerandom.default.key"; + + ConfigAccessor val = config.access(key); + Assert.assertNull(val.getDefaultValue()); + + ConfigAccessor val2 = config.access(key).withDefault("abc"); + Assert.assertEquals(val2.getDefaultValue(), "abc"); + Assert.assertEquals(val2.getValue(), "abc"); + + ConfigAccessor vali = config.access(key).as(Integer.class).withDefault(123); + Assert.assertEquals(vali.getDefaultValue(), Integer.valueOf(123)); + Assert.assertEquals(vali.getValue(), Integer.valueOf(123)); + + ConfigAccessor vali2 = config.access(key).as(Integer.class).withStringDefault("123"); + Assert.assertEquals(vali2.getDefaultValue(), Integer.valueOf(123)); + Assert.assertEquals(vali2.getValue(), Integer.valueOf(123)); + + System.setProperty(key, "666"); + Assert.assertEquals(vali2.getDefaultValue(), Integer.valueOf(123)); + Assert.assertEquals(vali2.getValue(), Integer.valueOf(666)); + + + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/config/tck/SimpleValuesBean.java b/tck/src/main/java/org/eclipse/microprofile/config/tck/SimpleValuesBean.java deleted file mode 100644 index fee2d09d..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/config/tck/SimpleValuesBean.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016-2017 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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 - * - * http://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 org.eclipse.microprofile.config.tck; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import javax.enterprise.context.Dependent; -import javax.inject.Inject; - -@Dependent -public class SimpleValuesBean { - - @Inject - @ConfigProperty(name="my.string/property") - private String stringProperty; - - @Inject - @ConfigProperty(name="my.boolean.property") - private boolean booleanProperty; - - @Inject - @ConfigProperty(name="my.int/property") - private int intProperty; - - public String getStringProperty(){ - return this.stringProperty; - } - - public boolean getBooleanProperty(){ - return this.booleanProperty; - } - - public int getIntProperty(){ - return this.intProperty; - } -} diff --git a/tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java b/tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java new file mode 100644 index 00000000..519e7bbe --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * http://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 org.eclipse.microprofile.config.tck.configsources; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigSource; + + +/** + * A ConfigSource which can be used to play through multiple + * different scenarios in our TCK. + * + * @author Mark Struberg + */ +public class ConfigurableConfigSource implements ConfigSource { + + private Consumer> reportAttributeChange; + + private Map properties = new HashMap<>(); + + @Override + public int getOrdinal() { + return 110; + } + + @Override + public Map getProperties() { + return properties; + } + + @Override + public String getValue(String propertyName) { + return properties.get(propertyName); + } + + @Override + public String getName() { + return this.getClass().getName(); + } + + @Override + public void setOnAttributeChange(Consumer> reportAttributeChange) { + this.reportAttributeChange = reportAttributeChange; + } + + public static void configure(Config cfg, String propertyName, String value) { + for (ConfigSource configSource : cfg.getConfigSources()) { + if (configSource instanceof ConfigurableConfigSource) { + ((ConfigurableConfigSource) configSource).properties.put(propertyName, value); + ((ConfigurableConfigSource) configSource).reportAttributeChange.accept(Collections.singleton(propertyName)); + return; + } + } + + throw new IllegalStateException("This Config doesn't contain a ConfigurableConfigSource!"); + } +} diff --git a/tck/src/main/resources/internal/META-INF/microprofile-config.properties b/tck/src/main/resources/internal/META-INF/microprofile-config.properties index f8185061..9365bc75 100644 --- a/tck/src/main/resources/internal/META-INF/microprofile-config.properties +++ b/tck/src/main/resources/internal/META-INF/microprofile-config.properties @@ -28,23 +28,19 @@ tck.config.test.overwritten.in.custompropertyfile.key1=value from microprofile-c tck.config.test.javaconfig.converter.integervalue = 1234 tck.config.test.javaconfig.converter.integervalue.broken = xxx - tck.config.test.javaconfig.converter.longvalue = 1234567890 tck.config.test.javaconfig.converter.longvalue.broken = xxx - tck.config.test.javaconfig.converter.floatvalue = 12.34 tck.config.test.javaconfig.converter.floatvalue.broken = alfasdf - tck.config.test.javaconfig.converter.doublevalue = 12.34 tck.config.test.javaconfig.converter.doublevalue.broken = alfasdf - +# the following are actually using implicit converters now: tck.config.test.javaconfig.converter.durationvalue = PT15M tck.config.test.javaconfig.converter.durationvalue.broken = alfasdf - tck.config.test.javaconfig.converter.localtimevalue = 10:37 tck.config.test.javaconfig.converter.localtimevalue.broken = alfasdf @@ -68,6 +64,7 @@ tck.config.test.javaconfig.converter.instantvalue.broken = alfasdf tck.config.test.javaconfig.configvalue.key1=value1 + # test BooleanConverter START tck.config.test.javaconfig.configvalue.boolean.true=true tck.config.test.javaconfig.configvalue.boolean.true_uppercase=TRUE @@ -82,6 +79,7 @@ tck.config.test.javaconfig.configvalue.boolean.yes=yes tck.config.test.javaconfig.configvalue.boolean.yes_uppercase=YES tck.config.test.javaconfig.configvalue.boolean.yes_mixedcase=Yes tck.config.test.javaconfig.configvalue.boolean.no=no +tck.config.test.javaconfig.configvalue.boolean.no_mixedcase=No tck.config.test.javaconfig.configvalue.boolean.y=y tck.config.test.javaconfig.configvalue.boolean.y_uppercase=Y @@ -115,10 +113,14 @@ tck.config.test.javaconfig.converter.urivalue=http://microprofile.io tck.config.test.javaconfig.converter.urivalue.broken=space is an illegal uri character # implicit Converter tests +tck.config.test.javaconfig.converter.implicit.enumvalue = FOO +tck.config.test.javaconfig.converter.implicit.enumvalue.broken = foobar +tck.config.test.javaconfig.converter.implicit.charSequenceCt=charSequenceCt +tck.config.test.javaconfig.converter.implicit.charSequenceParse=charSequenceParse +tck.config.test.javaconfig.converter.implicit.charSequenceValueOf=charSequenceValueOf tck.config.test.javaconfig.converter.implicit.stringCt=stringCt tck.config.test.javaconfig.converter.implicit.stringValueOf=stringValueOf tck.config.test.javaconfig.converter.implicit.stringOf=stringOf -tck.config.test.javaconfig.converter.implicit.charSequenceParse=charSequenceParse tck.config.test.javaconfig.converter.implicit.charSequenceParse.yearmonth=2017-12 tck.config.test.javaconfig.converter.implicit.enumValueOf=BAZ @@ -143,3 +145,8 @@ tck.config.test.javaconfig.converter.urlvalues=http://microprofile.io,http://ope tck.config.test.javaconfig.converter.class=org.eclipse.microprofile.config.tck.ClassConverterTest tck.config.test.javaconfig.converter.class.array=org.eclipse.microprofile.config.tck.ClassConverterTest,java.lang.String + +# variable replacement rests +tck.config.variable.baseHost = some.host.name +tck.config.variable.firstEndpoint = http://${tck.config.variable.baseHost}/endpointOne +tck.config.variable.secondEndpoint = http://${tck.config.variable.baseHost}/endpointTwo \ No newline at end of file