diff --git a/config/config/src/main/java/io/helidon/config/ConfigSourceMpRuntimeImpl.java b/config/config/src/main/java/io/helidon/config/ConfigSourceMpRuntimeImpl.java index a62dd2b55..edc1b88a6 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigSourceMpRuntimeImpl.java +++ b/config/config/src/main/java/io/helidon/config/ConfigSourceMpRuntimeImpl.java @@ -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 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 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; + } } diff --git a/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java index 6bf88454c..774670959 100644 --- a/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java +++ b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java @@ -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 properties = Map.of( + "inject.of", "of", + "inject.valueOf", "valueOf", + "inject.parse", "parse", + "inject.ctor", "ctor" + ); + + @Override + public Map getProperties() { + return properties; + } + + @Override + public String getValue(String propertyName) { + return properties.get(propertyName); + } + + @Override + public String getName() { + return getClass().getName(); + } + } } diff --git a/microprofile/config/src/test/java/io/helidon/microprofile/config/MutableMpTest.java b/microprofile/config/src/test/java/io/helidon/microprofile/config/MutableMpTest.java new file mode 100644 index 000000000..fca589467 --- /dev/null +++ b/microprofile/config/src/test/java/io/helidon/microprofile/config/MutableMpTest.java @@ -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 value = new AtomicReference<>(); + private BiConsumer listener; + + public MutableSource(String initial) { + value.set(initial); + } + + public void registerChangeListener(BiConsumer listener) { + this.listener = listener; + } + + @Override + public Map 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); + } + } +} diff --git a/microprofile/config/src/test/resources/META-INF/beans.xml b/microprofile/config/src/test/resources/META-INF/beans.xml deleted file mode 100644 index 191676fc9..000000000 --- a/microprofile/config/src/test/resources/META-INF/beans.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - diff --git a/microprofile/config/src/test/resources/META-INF/microprofile-config.properties b/microprofile/config/src/test/resources/META-INF/microprofile-config.properties new file mode 100644 index 000000000..0dcc20a6d --- /dev/null +++ b/microprofile/config/src/test/resources/META-INF/microprofile-config.properties @@ -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