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

#418 config profile support #554

Merged
merged 8 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public interface ConfigBuilder {
* @return this configuration builder instance
*/
<T> ConfigBuilder withConverter(Class<T> type, int priority, Converter<T> converter);

/**
OndroMih marked this conversation as resolved.
Show resolved Hide resolved
* Build a new {@link Config} instance based on this builder instance.
*
Expand Down
77 changes: 77 additions & 0 deletions spec/src/main/asciidoc/configprofile.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// Copyright (c) 2020 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:
// Emily Jiang


[[configprofile]]
== Config Profile

Config Profile indicates the project phase, such as dev, testing, live, etc.

=== Specify Config Profile
Copy link
Member

Choose a reason for hiding this comment

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

Why not use the term which is established in JavaEE: ProjectStage ?

This also has the benefit, that there is always a ProjectStage. Even if you don't specify any, you'll fallback to Production. This allows the following neat use case:

myproject.security.enabled.Production=true
myproject.security.enabled=false

Which means: enable security in production, but have it disabled anywhere else.

Copy link
Contributor

Choose a reason for hiding this comment

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

In my opinion, profile seems to be a more "popular" term to specify the configuration. It is used by Spring for instance.

Is there any particular reason why EE went with ProjectStage instead of Profile?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I will stick with profile for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will keep profile for now.


The config profile can be specified via the property `mp.config.profile`, which can be set in any of the configuration sources. The value of the property can contain only characters that are valid for config property names.
This is because the name of the profile is directly stored in the name of the config property. It can be set when starting your application. e.g.

[source, text]
----
java -jar myapp.jar -Dmp.config.profile=testing
----

The value of the property `mp.config.profile` shouldn't be updated after the application is started. It's only read once during the application start up. If the property value is modified afterwards, the behavior is undefined and any changes to its value made later can be ignored by the implementation.

The value of the property `mp.config.profile` specifies a single active profile. Only one profile can be active at a time. Commas in the value have no special meaning. For example, if the value of `mp.config.profile` is `testing,live`, a single profile named `testing,live` is active instead of two active profiles.
If the property `mp.config.profile` is specified in multiple config sources, the value of the property is determined following the same rules as other configuration properties, which means the value in the config source with the highest ordinal wins.
Copy link
Member

Choose a reason for hiding this comment

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

This information is redundant. Standard rules are, well ... standard ;)

Copy link
Member Author

@Emily-Jiang Emily-Jiang Jun 2, 2020

Choose a reason for hiding this comment

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

In the earlier conversation, it was asked for clarification. If we treat this mp.config.profile exactly same as other properties, we can just say the rules on config property applies. At the moment, @OndroMih is against the idea of providing the value in other places except environment variables, System Properties. It will simply the logic a great deal.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should be consistent and "eat our own dog food" :)


=== How Config Profile works
Copy link
Contributor

Choose a reason for hiding this comment

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

This section isn't as clear as I'd hope. The way I understand it profiles do not have an effect beyond their source. If a source has either the original property or the profile specific property and the highest ordinal it wins (as usual) whereby naturally the profile specific property (if present) does override the original property value within that winning source.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jbee your undestanding is correct. Within the same config source, the active profile specific property overrides the original property value. Overall, the property from the highest ordinal wins no matter whether it is the original property or the active profile property.


The configuration property that utilizes the Config Profile is called a "profile-specific" property. A "profile-specific" property name consists of the following sequence: `% <profile name>.<original property name>`.

Conforming implementations are required to search for a configuration source with the highest ordinal (priority) that provides either the property name or the "profile-specific" property name.
If the configuration source provides the "profile-specific" name, the value of the "profile-specific" property will be returned. If it doesn't contain the "profile-specific" name, the value of the plain property will be returned.


For an instance, a config source can be specified as follows.
OndroMih marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove an?

Copy link
Member Author

Choose a reason for hiding this comment

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

well spotted


[source, text]
----
%dev.vehicle.name=car
%live.vehicle.name=train
%testing.vehicle.name=bike
vehicle.name=lorry
----

A config property associated with the Config Profile can be looked up as shown below.

[source, text]
----
@Inject @ConfigProperty(name="vehicle.name") String vehicleName;
----

[source, text]
----
String vehicleName = ConfigProvider.getConfig().getValue("vehicle.name", String.class);
----

Copy link
Contributor

Choose a reason for hiding this comment

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

We need some normative text covering what conforming implementations are required to do.

Copy link
Contributor

Choose a reason for hiding this comment

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

Detailed description of resolution rules and interplay between ordinals and other config sources are needed here and these rules also tested within the TCK.

Copy link
Member Author

@Emily-Jiang Emily-Jiang Apr 24, 2020

Choose a reason for hiding this comment

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

+1 @smillidge , I will add more TCKs for the config source ordinal rules once we are happy with the rules. See here for more explaination.

If the property `mp.config.profile` is set to `dev`, the property `%dev.vehicle.name` is the Active Property. An active property overrides the properties in the same config source.
Copy link
Member

Choose a reason for hiding this comment

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

This is exactly the other way around as any other established solutions do it. Everyone else is using postfixing, e.g. vehicle.name.dev. The reason is the following:

  • you can easily group configuration that way
  • you can have multiple lookup paths e.g. a dev.oracle and dev.postgres postfix.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think prefix or postfix both works. SmallRye Config went with prefix. I think what you listed above can be done using prefix as well.
%dev.vehicle.name stands out more than vehicle.name.dev though.

Copy link
Contributor

Choose a reason for hiding this comment

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

The issue with postfixing is that since a profile name can be anything you can encounter clashes. So vehicle.name.something, how do I know if something refers to a profile and I should set up vehicle.name, or it is an actual property I should setup vehicle.name.something.something?

In more details, if `mp.config.profile` is set to `dev`, the property `%dev.vehicle.name` overrides the property `vehicle.name`. The `vehicleName` will be set to `car`.
The properties `%live.vehicle.name` and `%testing.vehicle.name` are inactive config properties and don't override the property `vehicle.name`.

If `mp.config.profile` is set to `live`, the property `%live.vehicle.name` is the active property. The `vehicleName` will be `train`. Similarly, `bike` will be the value of `vehicleName`, if the profile is `testing`.

2 changes: 1 addition & 1 deletion spec/src/main/asciidoc/configprovider.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ConfigBuilder builder = resolver.getBuilder();
** Add config sources and build:
+
```
Config config = builder.addDefaultSources().withSources(mySource).withConverters(myConverter).build;
Config config = builder.addDefaultSources().withSources(mySource).withConverters(myConverter).withProfile(profile).build;
```
** (optional) Manage the lifecycle of the config
+
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 @@ -46,4 +46,6 @@ include::configsources.asciidoc[]

include::converters.asciidoc[]

include::configprofile.asciidoc[]

include::release_notes.asciidoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.configsources;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

/**
* @author Emily Jiang
*/
public class CustomConfigProfileConfigSource implements ConfigSource {

private Map<String, String> configValues = new HashMap<>();

public CustomConfigProfileConfigSource() {
configValues.put("mp.config.profile", "test");
configValues.put("%dev.vehicle.name", "bike");
configValues.put("%prod.vehicle.name", "bus");
configValues.put("%test.vehicle.name", "van");
configValues.put("vehicle.name", "car");
}

@Override
public int getOrdinal() {
return 500;
}
@Override
Copy link
Contributor

Choose a reason for hiding this comment

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

Add blank line.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

public String getValue(String key) {
return configValues.get(key);
}

@Override
public String getName() {
return "customConfigProfileSource";
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional line.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@Override
public Set<String> getPropertyNames() {

return configValues.keySet();
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove blank lines.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2020 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.profile;


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.enterprise.inject.spi.CDI;
import javax.inject.Inject;

import org.eclipse.microprofile.config.ConfigProvider;
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.testng.annotations.Test;

/**
* Test cases for Config profile
*
* @author Emily Jiang
*/
public class DevConfigProfileTest extends Arquillian {


@Deployment
public static Archive deployment() {
JavaArchive testJar = ShrinkWrap
.create(JavaArchive.class, "DevConfigProfileTest.jar")
.addClasses(DevConfigProfileTest.class, ProfilePropertyBean.class)
.addAsManifestResource(
new StringAsset(
"mp.config.profile=dev\n" +
"%dev.vehicle.name=bike\n" +
"%prod.vehicle.name=bus\n" +
"%test.vehicle.name=van\n" +
"vehicle.name=car"),
"microprofile-config.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.as(JavaArchive.class);



Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank lines.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

WebArchive war = ShrinkWrap
.create(WebArchive.class, "DevConfigProfileTest.war")
.addAsLibrary(testJar);
return war;

}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank line.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@Test
public void testConfigProfileWithDev() {
ProfilePropertyBean bean = CDI.current().select(ProfilePropertyBean.class).get();

assertThat(bean.getConfigProperty(), is(equalTo("bike")));
assertThat(ConfigProvider.getConfig().getValue("vehicle.name", String.class), is(equalTo("bike")));
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank line.

@Dependent
public static class ProfilePropertyBean {
@Inject
@ConfigProperty(name="vehicle.name")
private String vehicleName;

public String getConfigProperty() {
return vehicleName;
}
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank lines.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2020 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.profile;


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.enterprise.inject.spi.CDI;
import javax.inject.Inject;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.tck.base.AbstractTest;
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.testng.annotations.Test;

/**
* Test cases for Config profile
*
* @author Emily Jiang
*/
public class InvalidConfigProfileTest extends Arquillian {


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank line.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@Deployment
public static Archive deployment() {
JavaArchive testJar = ShrinkWrap
.create(JavaArchive.class, "InvalidConfigProfileTest.jar")
.addClasses(InvalidConfigProfileTest.class, ProfilePropertyBean.class)
.addAsManifestResource(
new StringAsset(
"mp.config.profile=invalid\n" +
"%dev.vehicle.name=bike\n" +
"%prod.vehicle.name=bus\n" +
"%test.vehicle.name=van\n" +
"vehicle.name=car"),
"microprofile-config.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.as(JavaArchive.class);

AbstractTest.addFile(testJar, "META-INF/microprofile-config.properties");

WebArchive war = ShrinkWrap
.create(WebArchive.class, "InvalidConfigProfileTest.war")
.addAsLibrary(testJar);
return war;

}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank line.

@Test
public void testConfigProfileWithDev() {
ProfilePropertyBean bean = CDI.current().select(ProfilePropertyBean.class).get();

assertThat(bean.getConfigProperty(), is(equalTo("car")));
assertThat(ConfigProvider.getConfig().getValue("vehicle.name", String.class), is(equalTo("car")));
}



Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank lines.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@Dependent
public static class ProfilePropertyBean {
@Inject
@ConfigProperty(name="vehicle.name")
private String vehicleName;

public String getConfigProperty() {
return vehicleName;
}
}


Copy link
Contributor

Choose a reason for hiding this comment

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

Remove additional blank lines.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

}
Loading