Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support variable expansion #405

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions api/src/main/java/org/eclipse/microprofile/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ public interface Config {
*
* If this method gets used very often then consider to locally store the configured value.
*
* <p>Note that no variable replacement like in {@link ConfigAccessorBuilder#evaluateVariables(boolean)} will be performed!
*
* @param <T> the property type
* @param propertyName
* The configuration propertyName.
Expand All @@ -121,8 +119,6 @@ public interface Config {
*
* If this method is used very often then consider to locally store the configured value.
*
* <p>Note that no variable replacement like in {@link ConfigAccessorBuilder#evaluateVariables(boolean)} will be performed!
*
* @param <T> the property type
* @param propertyName
* The configuration propertyName.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public interface ConfigAccessorBuilder<T> {
* {@code "mycompany.some.url"} would be:
* {@code "http://localhost:8081/some/path"}
*
* <p><b>ATTENTION:</b> This defaults to {@code true}! That means variable replacement is enabled by default!</p>
* <strong>ATTENTION: This defaults to {@code true}! That means variable evaluation is enabled by default!</strong>
*
* @param evaluateVariables whether to evaluate variables in values or not
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@

/**
* @see org.eclipse.microprofile.config.ConfigAccessorBuilder#evaluateVariables(boolean)
* @return whether variable replacement is enabled. Defaults to {@code true}.
* @return whether variable evaluation is enabled. Defaults to {@code true}.
*/
@Nonbinding
boolean evaluateVariables() default true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ public interface ConfigBuilder {
*/
ConfigBuilder withConverters(Converter<?>... converters);

/**
* Determines whether variable evaluation is enabled.
*
* This enables variable evaluation globally for any configuration values retrieved using the
* {@link Config#getValue(String, Class)} and {@link Config#getOptionalValue(String, Class)} methods
* on `Config` built by this builder.
*
* This setting has no impact on {@link org.eclipse.microprofile.config.ConfigAccessorBuilder#evaluateVariables(boolean)}.
*
* @param evaluateVariables {@code true} to evaluate variables
* @return the ConfigBuilder
*/
ConfigBuilder evaluateVariables(boolean evaluateVariables);

/**
* Add the specified {@link Converter} for the given type.
Expand Down
2 changes: 2 additions & 0 deletions spec/src/main/asciidoc/microprofile-config-spec.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ include::configprovider.asciidoc[]

include::configsources.asciidoc[]

include::variable-evaluation.asciidoc[]

include::converters.asciidoc[]

include::configaccessor.asciidoc[]
Expand Down
11 changes: 7 additions & 4 deletions spec/src/main/asciidoc/release_notes.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,19 @@ Java2 security related change (link:https://github.com/eclipse/microprofile-conf
[[release_notes_14]]
== Release Notes for MicroProfile Config 1.4

The following changes occurred in the 1.4 release, compared to 1.2
The following changes occurred in the 1.4 release, compared to 1.3

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]
A full list of changes may be found on the link:https://github.com/eclipse/microprofile-config/milestone/7[MicroProfile Config 1.4 Milestone]

=== API/SPI Changes

MicroProfile 1.4 introduced a way to deal with dynamic values.
MicroProfile Config 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.

MicroProfile Config now supports variable evaluation so that a Config property value can refer to other config properties.

=== Functional Changes

OSGi compatibility got improved.
OSGi compatibility got improved.
123 changes: 123 additions & 0 deletions spec/src/main/asciidoc/variable-evaluation.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// Copyright (c) 2016-2019 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.

[[variable-evaluation]]
== Variable Evaluation

The value of a configuration property can contains a variable corresponding to another configuration property.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The value of a configuration property can contains a variable corresponding to another configuration property.
The value of a configuration property can contain a variable corresponding to another configuration property.

This variable is _evaluated_ to be replaced by its own value when the original property is returned.

For example, let's define a `server.url` property:

[source]
----
server.url=http://${server.host}:{server.port}/endpoint
----

When the Config API returns the value of `server.url`, it evaluates any variable (such as `server.host` and `server.port`) to replace
them with their value.

If these properties are also defined:

[source]
----
server.host=example.org
server.port=8080
----

The `server.url` value returned by the Config API is `http://example.org:8080/endpoint`.

[NOTE]
====
Backwards Compatibility

Variable evaluation is not backwards compatible.
Previous version of MicroProfile Config would not evalute the variables and would return the value `http://${server.host}:{server.port}/endpoint`.

Implementation of MicroProfile Config MUST provide a way to disable variable evaluation to provide backwards compatibility.

The property `mp.config.evaluateVariables` can be configured as a boolean in one of the 3 default `ConfigSource` (environment variables, system properties,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 on specifying the location of the property. It should be allowed to be specified anywhere as a config, as long as it is available on application starting

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, the property is supposed to apply on a per-source basis only, so that some sources can have expressions and some not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmlloyd no, that's not the intent.

The intent is to support the mp.config.evaluateVariables property to configure globally variable evaluation on a Config level.
The wording is poor. I just wanted to restrict the usage of this property to the 3 default config sources so that a ConfigBuilder will only look in these 3 config sources to determine the behaviour of the built Config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emilyjiang we can not specify this property that is using to configure the Config itself on any ConfigSource or we end up with a chicken-and-egg situation.

Let's say we have a 3rd-party Zookeeper ConfigSource that calls ConfigProvider.getConfig() to get its own configuration (to be able to connect to ZK).

If we look for any ConfigSource to get this mp.config.evaluateVariables, we will call ZooKeep ConfigSource that will call ConfigProvider.getConfig() to get its configuration (to be able to fetch its properties). In turn this will call the method responsible to get the mp.config.evaluateVariables property, etc.

Restricting to the 3 default ConfigSource avoid this situation. This property is a special case: it it used to configure the ConfigBuilder itself, not the application. It seems ok to me to limit its use.

`META-INF/microprofile-config.properties`). Its value will be used by the MicroProfile Config implementation to _globally_ enable or disable variable evaluation.
In the absence of this property, variable evaluation is enabled.
====

---


=== Variable Syntax

The syntax to use a variable is `${key}` for a _required_ config property and `${<key>:<default_value>}` for
an _optional_ config property.

If the variable is required , by using `${key}`, a config property with the name `key` MUST be present in the Config to be evaluated, otherwise, the
Config API will throw a `NoSuchElementException` (or an `Optional.empty()` value).

For example, let's define the configuration properties:

[source]
----
server.url=http://${server.host}:{server.port}/endpoint
server.host=example.org
----

A call to `Config.getValue("server.url", String.class)` will throw a `NoSuchElementException` since the
`server.port` property is not configured.
Likewise, a call to `Config.getOptionalValue("server.url", String.class)` will return an `Optional.empty()`.

==== Default value

A variable can define a _default_ value to be evaluated if the corresponding config property is not present.

For example, let's define the configuration properties:

[source]
----
server.url=http://${server.host:example.org}:{server.port:8080}/endpoint
----

If neither `server.host` nor `server.port` config properties are present, the Config API will use the variable's default values and will return
`http://example.org:8080/endpoint` for the `server.url`.

It is possible to define an empty default value for a variable.
If the key is not present in the Config, it will not be evaluated.

For example, let's define the configuration properties:

[source]
----
server.url=http://example.org:8080/endpoint#${server.anchor:}
----

When the `server.anchor` configuration property is not present, the Config API will return
`http://example.org:8080/endpoint#` for the `server.url`.

If the `server.anchor` is configured with the value `id123`, the Config API will return
`http://example.org:8080/endpoint#id123` for the `server.url`.

A default value does not support variable evaluation (a default value is _raw_).

For example, let's define the configuration properties:

[source]
----
foo.one=Hello
foo.two=${foo.three:${foo.one}}
----

`foo.two` defines a variable which key is `foo.three` and which default value is `${foo.one}`.
If the config property `foo.three` is not present, the Config API will return `${foo.one}` for the `foo.two` config property.
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,11 @@ public void testVariableReplacement() {
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");
"http://some.host.name/endpointOne");

Assert.assertEquals(config.getOptionalValue("tck.config.variable.firstEndpoint", String.class).get(),
"http://${tck.config.variable.baseHost}/endpointOne");
"http://some.host.name/endpointOne");
}

@Test
Expand Down
Loading