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 3136d4c7..7d5f8b1f 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
*
@@ -63,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, Class)}.
+ * This method returns a builder {@link ConfigAccessorBuilder} 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:
*
*
@@ -72,7 +78,8 @@
* }
*
*
- * 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, Class)} for accessing a configured value.
*
*
Configured values can also be accessed via injection.
* See {@link org.eclipse.microprofile.config.inject.ConfigProperty} for more information.
@@ -82,6 +89,8 @@
* @author Ron Smeral
* @author Emily Jiang
* @author Gunnar Morling
+ * @author Manfred Huber
+ * @author Alex Falb
*
*/
@org.osgi.annotation.versioning.ProviderType
@@ -90,17 +99,18 @@ 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
- * The property type
+ * Note that no variable replacement like in {@link ConfigAccessorBuilder#evaluateVariables(boolean)} will be performed!
+ *
+ * @param the property type
* @param propertyName
* The configuration propertyName.
* @param propertyType
* The type into which the resolve property value should get converted
* @return the resolved property value as an object of the requested type.
- * @throws java.lang.IllegalArgumentException if the property cannot be converted to the specified type.
+ * @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(String propertyName, Class propertyType);
@@ -108,29 +118,86 @@ 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
- * The property type
+ * Note that no variable replacement like in {@link ConfigAccessorBuilder#evaluateVariables(boolean)} will be performed!
+ *
+ * @param the property type
* @param propertyName
* The configuration propertyName.
* @param propertyType
* The type into which the resolve property value should be converted
- * @return The resolved property value as an Optional of the requested type.
+ * @return the resolved property value as an Optional of the requested type.
*
- * @throws java.lang.IllegalArgumentException if the property cannot be converted to the specified type.
+ * @throws IllegalArgumentException if the property cannot be converted to the specified type.
*/
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
+ * @param type type into which the resolve property value should get converted
+ * @param the property type
+ * @return a {@code ConfigAccessor} to access the given propertyName
+ */
+ ConfigAccessorBuilder access(String propertyName, Class type);
+
+ /**
+ * 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.access("myapp.host", String.class)
+ * .cacheFor(60, TimeUnit.MINUTES)
+ * .build();
+ *
+ * // and right in between the underlying values get changed to 'newserver' and port 8082
+ *
+ * // get the current port for the host
+ * ConfigAccessor<Integer> portCfg config.access("myapp.port", Integer.class)
+ * .cacheFor(60, TimeUnit.MINUTES)
+ * .build();
+ *
+ *
+ * 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();
/**
- * @return all currently registered {@link ConfigSource configsources} sorted with descending ordinal and ConfigSource name
+ * @return all currently registered {@link ConfigSource ConfigSources} sorted by descending ordinal and ConfigSource name
*/
Iterable getConfigSources();
}
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..bdf0b737
--- /dev/null
+++ b/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessor.java
@@ -0,0 +1,118 @@
+/*
+ ******************************************************************************
+ * 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 java.util.Optional;
+
+
+/**
+ * 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 {
+
+ /**
+ * 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 value from a previously taken {@link ConfigSnapshot}.
+ *
+ * @param configSnapshot previously taken via {@link Config#snapshotFor(ConfigAccessor[])}
+ * @return the resolved value as Optional
+ * @see Config#snapshotFor(ConfigAccessor...)
+ * @throws IllegalArgumentException if the {@link ConfigSnapshot} hasn't been resolved
+ * for this {@link ConfigAccessor}
+ */
+ Optional getOptionalValue(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, Class)}.
+ * @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 ConfigAccessorBuilder#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 ConfigAccessorBuilder#withDefault(Object)}
+ * or {@link ConfigAccessorBuilder#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/ConfigAccessorBuilder.java b/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessorBuilder.java
new file mode 100644
index 00000000..042e2ff5
--- /dev/null
+++ b/api/src/main/java/org/eclipse/microprofile/config/ConfigAccessorBuilder.java
@@ -0,0 +1,162 @@
+/*
+ ******************************************************************************
+ * 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 java.time.temporal.ChronoUnit;
+
+import org.eclipse.microprofile.config.spi.Converter;
+
+
+
+/**
+ * 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 ConfigAccessor#getValue()}
+ *
+ * @author Mark Struberg
+ * @author Gerhard Petracek
+ * @author Tomas Langer
+ * @author Emily Jiang
+ */
+public interface ConfigAccessorBuilder {
+
+
+ /**
+ * 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
+ */
+ ConfigAccessorBuilder useConverter(Converter converter);
+
+ /**
+ * Sets the default value to use in case the resolution returns null.
+ * @param value the default value
+ * @return This builder
+ */
+ ConfigAccessorBuilder 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
+ */
+ ConfigAccessorBuilder 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 ConfigAccessor#getValue()} will again resolve the value
+ * from the underlying {@link 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#onAttributeChange(java.util.function.Consumer)}.
+ *
+ * @param value the amount of the TimeUnit to wait
+ * @param timeUnit the TimeUnit for the value
+ * @return This builder
+ */
+ ConfigAccessorBuilder cacheFor(long value, ChronoUnit 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
+ */
+ ConfigAccessorBuilder evaluateVariables(boolean evaluateVariables);
+
+ /**
+ * The methods {@link ConfigAccessorBuilder#addLookupSuffix(String)}
+ * append the given parameters as optional suffixes to the {@link ConfigAccessor#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", Integer.class)
+ * .addLookupSuffix(tenant)
+ * .addLookupSuffix(config.access("config.projectStage", String.class).build())
+ * .build()
+ * .getValue();
+ *
+ *
+ * Given the current tenant name is 'myComp' and the property
+ * {@code config.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 ConfigAccessor#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
+ */
+ ConfigAccessorBuilder addLookupSuffix(String suffixValue);
+
+ /**
+ *
+ * @param suffixAccessor {@link ConfigAccessor} to be used to resolve the suffix.
+ * @return This builder
+ * @see #addLookupSuffix(String)
+ */
+ ConfigAccessorBuilder addLookupSuffix(ConfigAccessor suffixAccessor);
+ /**
+ * Build a ConfigAccessor
+ * @return the configAccessor
+ */
+ ConfigAccessor build();
+
+}
diff --git a/api/src/main/java/org/eclipse/microprofile/config/ConfigProvider.java b/api/src/main/java/org/eclipse/microprofile/config/ConfigProvider.java
index 81c176c9..35b61219 100644
--- a/api/src/main/java/org/eclipse/microprofile/config/ConfigProvider.java
+++ b/api/src/main/java/org/eclipse/microprofile/config/ConfigProvider.java
@@ -79,7 +79,6 @@ private ConfigProvider() {
/**
* Provide a {@link Config} based on all {@link org.eclipse.microprofile.config.spi.ConfigSource ConfigSources} of the
* current Thread Context ClassLoader (TCCL)
- *
*
*
*
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 70143a3d..55b44b60 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)
@@ -135,4 +137,25 @@
*/
@Nonbinding
String defaultValue() default UNCONFIGURED_VALUE;
+
+ /**
+ * @see org.eclipse.microprofile.config.ConfigAccessorBuilder#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..582e2892 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.
@@ -26,12 +26,16 @@
* Extracted the Config part out of DeltaSpike and proposed as Microprofile-Config cf41cf130bcaf5447ff8
* 2016-11-14 - Emily Jiang / IBM Corp
* Methods renamed, JavaDoc and cleanup
+ *
*
*******************************************************************************/
package org.eclipse.microprofile.config.spi;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
+
+
/**
* Implement this interfaces to provide a ConfigSource.
@@ -42,7 +46,16 @@
* The default config sources always available by default are:
*
* - System properties (ordinal=400)
- * - Environment properties (ordinal=300)
+ * - 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"}):
+ *
+ * - Exact match (i.e. {@code "com.ACME.size"})
+ * - Replace all '.' by '_' (i.e. {@code "com_ACME_size"})
+ * - Replace all '.' by '_' and convert to upper case (i.e. {@code "COM_ACME_SIZE"})
+ *
+ * The first environment variable that is found is returned by this ConfigSource.
+ *
* - /META-INF/microprofile-config.properties (ordinal=100)
*
*
@@ -56,10 +69,15 @@
* 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
* @author John D. Ament
+ * @author Tomas Langer
*
*/
public interface ConfigSource {
@@ -109,7 +127,7 @@ default Set getPropertyNames() {
* The first environment variable that is found is returned by this ConfigSource.
*
* /META-INF/microprofile-config.properties (default ordinal=100)
- *
+ *
*
*
* Any ConfigSource part of an application will typically use an ordinal between 0 and 200.
@@ -146,4 +164,57 @@ default int getOrdinal() {
*/
String getName();
+ /**
+ * Determines if this config source should be scanned for its list of properties.
+ *
+ * Generally, slow ConfigSources should return false here.
+ *
+ * @return {@code true} if this ConfigSource should be scanned for its list of properties,
+ * {@code false} if it should not be scanned.
+ */
+ default boolean isScannable() {
+ return true;
+ }
+
+ /**
+ * The callback should get invoked if an attribute change got detected inside the ConfigSource.
+ *
+ * @param callback will be set by the {@link org.eclipse.microprofile.config.Config} after this
+ * {@code ConfigSource} got created and before any configured values
+ * get served.
+ * @return ChangeSupport informing the {@link org.eclipse.microprofile.config.Config} implementation about support for changes by this source
+ * @see ChangeSupport
+ */
+ default ChangeSupport onAttributeChange(Consumer> callback) {
+ // do nothing by default. Just for compat with older ConfigSources.
+ // return unsupported to tell config that it must re-query this source every time
+ return ChangeSupport.UNSUPPORTED;
+ }
+
+ /**
+ * What kind of change support this config source has.
+ *
+ * {@link org.eclipse.microprofile.config.Config} implementations may use this information for internal optimizations.
+ */
+ enum ChangeSupport {
+ /**
+ * Config change is supported, this config source will invoke the callback provided by
+ * {@link ConfigSource#onAttributeChange(Consumer)}.
+ *
+ * Example: File based config source that watches the file for changes
+ */
+ SUPPORTED,
+ /**
+ * Config change is not supported. Configuration values can change, though this change is not reported back.
+ *
+ * Example: LDAP based config source
+ */
+ UNSUPPORTED,
+ /**
+ * Configuration values cannot change for the lifetime of this {@link ConfigSource}.
+ *
+ * Example: Environment variables config source, classpath resource config source
+ */
+ IMMUTABLE
+ }
}
diff --git a/api/src/main/resources/META-INF/NOTICE b/api/src/main/resources/META-INF/NOTICE
index 545dd5f7..aa3d3a34 100644
--- a/api/src/main/resources/META-INF/NOTICE
+++ b/api/src/main/resources/META-INF/NOTICE
@@ -24,4 +24,7 @@ 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
+Tomas Langer tomas.langer@oracle.com
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..c806a6cc
--- /dev/null
+++ b/spec/src/main/asciidoc/configaccessor.asciidoc
@@ -0,0 +1,64 @@
+//
+// 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
+// Emily Jiang
+
+[[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", String.class).build().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 `ConfigAccessorBuilder`.
+This is basically a builder which has methods to refine the resolution, including the following:
+
+
+* `evaluateVariables(boolean evaluateVariables)`
+* `useConverter(Converter converter)` -- Defines a specific {@link Converter} to be used instead of applying the default Converter resolving logic.
+* `addLookupSuffix(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", Integer.class)
+ .addLookupSuffix(config.access("project.projectStage").cacheFor(12, TimeUnit.HOURS))
+ .addLookupSuffix("database.vendor")
+ .withDefault(3306).build();
+...
+Integer port = dbPortCfg.getValue();
+-----------------------------------------------------------------
diff --git a/spec/src/main/asciidoc/configexamples.asciidoc b/spec/src/main/asciidoc/configexamples.asciidoc
index 99ddf335..75c89990 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 0b133c21..35425bc5 100644
--- a/spec/src/main/asciidoc/converters.asciidoc
+++ b/spec/src/main/asciidoc/converters.asciidoc
@@ -58,40 +58,49 @@ 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)`.
=== 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.
-If no built-in nor custom `Converter` for a requested Type `T`, an implicit Converter is automatically provided if the following conditions are met:
+=== Automatic 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:
* 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
+* The target type {@code T} has a {@code public static T parse(CharSequence)} metho
+
+=== 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.
+
diff --git a/spec/src/main/asciidoc/license-alv2.asciidoc b/spec/src/main/asciidoc/license-alv2.asciidoc
index 8b17e6bd..bd91533a 100644
--- a/spec/src/main/asciidoc/license-alv2.asciidoc
+++ b/spec/src/main/asciidoc/license-alv2.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");
@@ -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
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..ac2d314d
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/config/tck/ConfigAccessorTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.time.temporal.ChronoUnit;
+
+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/javaconfig.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", String.class).build().getValue(), "value1");
+ }
+
+ @Test
+ public void testGetValueWithDefault() {
+ ConfigAccessor cfga = config.access("tck.config.test.javaconfig.configvalue.withdefault.notexisting", Integer.class)
+ .withDefault(Integer.valueOf(1234)).build();
+
+ Assert.assertEquals(cfga.getValue(), Integer.valueOf(1234));
+ }
+
+ @Test
+ public void testGetValueWithStringDefault() {
+ ConfigAccessor cfga = config.access("tck.config.test.javaconfig.configvalue.withdefault.notexisting", Integer.class)
+ .withStringDefault("1234").build();
+
+ 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", String.class).build().getValue(),
+ "http://some.host.name/endpointOne");
+
+ Assert.assertEquals(config.access("tck.config.variable.secondEndpoint", String.class).build().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", String.class)
+ .addLookupSuffix("mycorp")
+ .addLookupSuffix(config.access("javaconfig.projectStage", String.class).build().getValue()).build();
+
+ 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", Integer.class).build().getValue(),
+ Integer.valueOf(1234));
+ }
+
+ @Test
+ public void testLongConverter() {
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.long", Long.class).build().getValue(),
+ Long.valueOf(1234567890123456L));
+ }
+
+ @Test
+ public void testFloatConverter() {
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.float", Float.class).build().getValue(),
+ Float.valueOf(12.34f));
+ }
+
+ @Test
+ public void testDoubleonverter() {
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.double", Double.class).build().getValue(),
+ Double.valueOf(12.34567890123456));
+ }
+
+ @Test
+ public void testBooleanConverter() {
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true_uppercase", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.true_mixedcase", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.false", Boolean.class).build().getValue(),
+ Boolean.FALSE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.one", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.zero",Boolean.class).build().getValue(),
+ Boolean.FALSE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.seventeen", Boolean.class).build().getValue(),
+ Boolean.FALSE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes_uppercase", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.yes_mixedcase", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.y", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.y_uppercase", Boolean.class).build().getValue(),
+ Boolean.TRUE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.no", Boolean.class).build().getValue(),
+ Boolean.FALSE);
+
+ Assert.assertEquals(config.access("tck.config.test.javaconfig.configvalue.boolean.no_mixedcase", Boolean.class).build().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, String.class).cacheFor(30, ChronoUnit.MILLIS).build();
+ 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, String.class).build();
+ Assert.assertNull(val.getDefaultValue());
+
+ ConfigAccessor val2 = config.access(key, String.class).withDefault("abc").build();
+ Assert.assertEquals(val2.getDefaultValue(), "abc");
+ Assert.assertEquals(val2.getValue(), "abc");
+
+ ConfigAccessor vali = config.access(key, Integer.class).withDefault(123).build();
+ Assert.assertEquals(vali.getDefaultValue(), Integer.valueOf(123));
+ Assert.assertEquals(vali.getValue(), Integer.valueOf(123));
+
+ ConfigAccessor vali2 = config.access(key, Integer.class).withStringDefault("123").build();
+ 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/configsources/ConfigurableConfigSource.java b/tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java
new file mode 100644
index 00000000..27da79f6
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/config/tck/configsources/ConfigurableConfigSource.java
@@ -0,0 +1,81 @@
+/*
+ * 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 ConfigSource.ChangeSupport onAttributeChange(Consumer> reportAttributeChange) {
+ this.reportAttributeChange = reportAttributeChange;
+ return ChangeSupport.SUPPORTED;
+ }
+
+ 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 119cb368..3705142f 100644
--- a/tck/src/main/resources/internal/META-INF/microprofile-config.properties
+++ b/tck/src/main/resources/internal/META-INF/microprofile-config.properties
@@ -34,27 +34,23 @@ tck.config.test.javaconfig.converter.shortvalue.broken = xxx
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
-
tck.config.test.javaconfig.converter.charvalue = c
tck.config.test.javaconfig.converter.charvalue.broken = xxx
+# 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
@@ -78,6 +74,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
@@ -92,6 +89,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
@@ -125,10 +123,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
@@ -153,3 +155,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