Support for mutable config sources to remove regression (even though … (#1667)

* Support for mutable MP config sources to remove regression (even though the original behavior was not intended). 
* Fixed unit test to be independent on ordering.
This commit is contained in:
Tomas Langer
2020-04-24 14:18:29 +02:00
committed by GitHub
parent 1e37487bef
commit 844c17cffe
5 changed files with 218 additions and 37 deletions

View File

@@ -16,13 +16,18 @@
package io.helidon.config;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.logging.Level;
import io.helidon.config.spi.ConfigNode;
import org.eclipse.microprofile.config.spi.ConfigSource;
import static io.helidon.config.AbstractConfigImpl.LOGGER;
class ConfigSourceMpRuntimeImpl extends ConfigSourceRuntimeBase {
private final ConfigSource source;
@@ -38,7 +43,28 @@ class ConfigSourceMpRuntimeImpl extends ConfigSourceRuntimeBase {
@Override
public void onChange(BiConsumer<String, ConfigNode> change) {
// this is a no-op - MP config sources do not support changes
try {
// this is not a documented feature
// it is to enable MP config sources to be "mutable" in Helidon
// this requires some design decisions (and clarification of the MP Config Specification), as this
// is open to different interpretations for now
Method method = source.getClass().getMethod("registerChangeListener", BiConsumer.class);
BiConsumer<String, String> mpListener = (key, value) -> change.accept(key, ConfigNode.ValueNode.create(value));
method.invoke(source, mpListener);
} catch (NoSuchMethodException e) {
LOGGER.finest("No registerChangeListener(BiConsumer) method found on " + source.getClass() + ", change"
+ " support not enabled for this config source (" + source.getName() + ")");
} catch (IllegalAccessException e) {
LOGGER.log(Level.WARNING, "Cannot invoke registerChangeListener(BiConsumer) method on " + source.getClass() + ", "
+ "change support not enabled for this config source ("
+ source.getName() + ")", e);
} catch (InvocationTargetException e) {
LOGGER.log(Level.WARNING, "Invocation of registerChangeListener(BiConsumer) method on " + source.getClass()
+ " failed with an exception, "
+ "change support not enabled for this config source ("
+ source.getName() + ")", e);
}
}
@Override
@@ -66,4 +92,10 @@ class ConfigSourceMpRuntimeImpl extends ConfigSourceRuntimeBase {
public String description() {
return source.getName();
}
@Override
boolean changesSupported() {
// supported through a known method signature
return true;
}
}

View File

@@ -18,21 +18,25 @@ package io.helidon.microprofile.config;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Map;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
import javax.inject.Qualifier;
import io.helidon.config.test.infra.RestoreSystemPropertiesExt;
import io.helidon.microprofile.cdi.HelidonContainer;
import io.helidon.microprofile.config.Converters.Ctor;
import io.helidon.microprofile.config.Converters.Of;
import io.helidon.microprofile.config.Converters.Parse;
import io.helidon.microprofile.config.Converters.ValueOf;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -49,27 +53,31 @@ import static org.junit.jupiter.api.Assertions.assertAll;
*/
@ExtendWith(RestoreSystemPropertiesExt.class)
class MpConfigInjectionTest {
private static HelidonContainer container;
private static SeContainer container;
@BeforeAll
static void initClass() {
// System properties for injection
// Removed use of system properties, as those stay around after test is finished
ConfigProviderResolver configProvider = ConfigProviderResolver.instance();
configProvider.registerConfig(configProvider.getBuilder()
.addDefaultSources()
.withSources(new TestSource())
.build(),
Thread.currentThread().getContextClassLoader());
System.setProperty("inject.of", "of");
System.setProperty("inject.valueOf", "valueOf");
System.setProperty("inject.parse", "parse");
System.setProperty("inject.ctor", "ctor");
// CDI container
container = HelidonContainer.instance();
container.start();
container = SeContainerInitializer.newInstance()
.addBeanClasses(Bean.class, SubBean.class)
.initialize();
}
@AfterAll
static void destroyClass() {
if (null != container) {
container.shutdown();
container.close();
}
}
@@ -131,4 +139,28 @@ class MpConfigInjectionTest {
@Specific
public static class SubBean extends Bean {
}
private static class TestSource implements ConfigSource {
private final Map<String, String> properties = Map.of(
"inject.of", "of",
"inject.valueOf", "valueOf",
"inject.parse", "parse",
"inject.ctor", "ctor"
);
@Override
public Map<String, String> getProperties() {
return properties;
}
@Override
public String getValue(String propertyName) {
return properties.get(propertyName);
}
@Override
public String getName() {
return getClass().getName();
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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 io.helidon.microprofile.config;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.CDI;
import javax.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
public class MutableMpTest {
private static SeContainer container;
private static MutableSource source;
@BeforeAll
static void initClass() {
source = new MutableSource("initial");
ConfigProviderResolver configProvider = ConfigProviderResolver.instance();
configProvider.registerConfig(configProvider.getBuilder()
.addDefaultSources()
.withSources(source)
.build(),
Thread.currentThread().getContextClassLoader());
// CDI container
container = SeContainerInitializer.newInstance()
.addBeanClasses(Bean.class)
.initialize();
}
@AfterAll
static void destroyClass() {
if (null != container) {
container.close();
}
source = null;
}
@Test
public void testMutable() {
Bean bean = CDI.current().select(Bean.class).get();
assertThat(bean.value, is("initial"));
source.setValue("updated");
bean = CDI.current().select(Bean.class).get();
assertThat(bean.value, is("updated"));
}
@Dependent
public static class Bean {
@Inject
@ConfigProperty(name = "value")
public String value;
}
// class must be public so helidon can see it and invoke methods through reflection
public static class MutableSource implements ConfigSource {
private AtomicReference<String> value = new AtomicReference<>();
private BiConsumer<String, String> listener;
public MutableSource(String initial) {
value.set(initial);
}
public void registerChangeListener(BiConsumer<String, String> listener) {
this.listener = listener;
}
@Override
public Map<String, String> getProperties() {
return Map.of("value", value.get());
}
@Override
public String getValue(String propertyName) {
if ("value".equals(propertyName)) {
return value.get();
}
return null;
}
@Override
public String getName() {
return "mutable-unit-test";
}
public void setValue(String value) {
this.value.set(value);
listener.accept("value", value);
}
}
}

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
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.
-->
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates.
#
# 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.
#
# needed to run unit tests independently (without beans.xml)
mp.initializer.allow=true
mp.initializer.warn=false