From a526ca01103a9d7459ffcf582f40d4115d0a43a7 Mon Sep 17 00:00:00 2001 From: nscuro Date: Sat, 19 Oct 2024 22:19:13 +0200 Subject: [PATCH] Adopt SmallRye Config Closes #695 Signed-off-by: nscuro --- alpine-common/pom.xml | 4 + .../src/main/java/alpine/Config.java | 183 ++++++++---------- .../src/test/java/alpine/ConfigTest.java | 80 ++++++-- .../alpine/common/util/ThreadUtilTest.java | 32 ++- ...ig_testGetPassThroughProperties.properties | 3 +- ...rye.config.SmallRyeConfigBuilderCustomizer | 1 + alpine-test/pom.xml | 55 ++++++ .../test/config/ConfigPropertyRule.java | 71 +++++++ .../test/config/InMemoryConfigSource.java | 68 +++++++ .../config/TestConfigBuilderCustomizer.java | 34 ++++ .../test/config/WithConfigProperty.java | 37 ++++ ...rye.config.SmallRyeConfigBuilderCustomizer | 1 + .../test/config/ConfigPropertyRuleTest.java | 46 +++++ .../test/config/InMemoryConfigSourceTest.java | 44 +++++ alpine-test/src/test/resources/alpine.version | 4 + pom.xml | 8 + 16 files changed, 551 insertions(+), 120 deletions(-) create mode 100644 alpine-common/src/test/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer create mode 100644 alpine-test/pom.xml create mode 100644 alpine-test/src/main/java/alpine/test/config/ConfigPropertyRule.java create mode 100644 alpine-test/src/main/java/alpine/test/config/InMemoryConfigSource.java create mode 100644 alpine-test/src/main/java/alpine/test/config/TestConfigBuilderCustomizer.java create mode 100644 alpine-test/src/main/java/alpine/test/config/WithConfigProperty.java create mode 100644 alpine-test/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer create mode 100644 alpine-test/src/test/java/alpine/test/config/ConfigPropertyRuleTest.java create mode 100644 alpine-test/src/test/java/alpine/test/config/InMemoryConfigSourceTest.java create mode 100644 alpine-test/src/test/resources/alpine.version diff --git a/alpine-common/pom.xml b/alpine-common/pom.xml index 906eeda3..cf44e287 100644 --- a/alpine-common/pom.xml +++ b/alpine-common/pom.xml @@ -35,6 +35,10 @@ org.apache.commons commons-lang3 + + io.smallrye.config + smallrye-config-core + com.fasterxml.jackson.core jackson-annotations diff --git a/alpine-common/src/main/java/alpine/Config.java b/alpine-common/src/main/java/alpine/Config.java index ca67eb99..38bf5b94 100644 --- a/alpine-common/src/main/java/alpine/Config.java +++ b/alpine-common/src/main/java/alpine/Config.java @@ -22,10 +22,12 @@ import alpine.common.util.ByteFormat; import alpine.common.util.PathUtil; import alpine.common.util.SystemUtil; +import io.smallrye.config.ExpressionConfigSourceInterceptor; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; import org.apache.commons.lang3.StringUtils; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -38,6 +40,7 @@ import java.util.Properties; import java.util.UUID; +import static io.smallrye.config.PropertiesConfigSourceLoader.inFileSystem; import static java.util.function.Predicate.not; /** @@ -50,14 +53,13 @@ public class Config { private static final Logger LOGGER = Logger.getLogger(Config.class); private static final String ALPINE_APP_PROP = "alpine.application.properties"; - private static final String PROP_FILE = "application.properties"; private static final String ALPINE_VERSION_PROP_FILE = "alpine.version"; private static final String APPLICATION_VERSION_PROP_FILE = "application.version"; private static final Config INSTANCE; - private static Properties properties; private static Properties alpineVersionProperties; private static Properties applicationVersionProperties; private static String systemId; + private static org.eclipse.microprofile.config.Config delegateConfig; static { LOGGER.info(StringUtils.repeat("-", 80)); @@ -218,40 +220,43 @@ public static Config getInstance() { * Initialize the Config object. This method should only be called once. */ void init() { - if (properties != null) { + if (delegateConfig != null) { return; } LOGGER.info("Initializing Configuration"); - properties = new Properties(); - + final SmallRyeConfigBuilder configBuilder = new SmallRyeConfigBuilder() + .forClassLoader(Thread.currentThread().getContextClassLoader()) + // Enable default config sources: + // + // | Source | Priority | + // | :--------------------------------------------------- | :------- | + // | System properties | 400 | + // | Environment variables | 300 | + // | ${pwd}/.env file | 295 | + // | ${pwd}/config/application.properties | 260 | + // | ${classpath}/application.properties | 250 | + // | ${classpath}/META-INF/microprofile-config.properties | 100 | + // + // https://smallrye.io/smallrye-config/3.10.0/config/getting-started/#config-sources + .addDefaultSources() + // Support expressions. + // https://smallrye.io/smallrye-config/3.10.0/config/expressions/ + .withInterceptors(new ExpressionConfigSourceInterceptor()) + // Allow applications to customize the Config via SPI. + // https://smallrye.io/smallrye-config/3.10.0/config/customizer/ + .addDiscoveredCustomizers(); + + // If a custom properties file is specified via "alpine.application.properties" system property, + // register it as additional config source. The file has a higher priority than any of the default + // properties sources. final String alpineAppProp = PathUtil.resolve(System.getProperty(ALPINE_APP_PROP)); if (StringUtils.isNotBlank(alpineAppProp)) { - LOGGER.info("Loading application properties from " + alpineAppProp); - try (InputStream fileInputStream = Files.newInputStream((new File(alpineAppProp)).toPath())) { - properties.load(fileInputStream); - } catch (FileNotFoundException e) { - LOGGER.error("Could not find property file " + alpineAppProp); - } catch (IOException e) { - LOGGER.error("Unable to load " + alpineAppProp); - } - } else { - LOGGER.info("System property " + ALPINE_APP_PROP + " not specified"); - LOGGER.info("Loading " + PROP_FILE + " from classpath"); - try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROP_FILE)) { - if (in != null) { - properties.load(in); - } else { - LOGGER.error("Unable to load (resourceStream is null) " + PROP_FILE); - } - } catch (IOException e) { - LOGGER.error("Unable to load " + PROP_FILE); - } - } - if (properties.size() == 0) { - LOGGER.error("A fatal error occurred loading application properties. Please correct the issue and restart the application."); + configBuilder.withSources(inFileSystem(alpineAppProp, 275, Thread.currentThread().getContextClassLoader())); } + delegateConfig = configBuilder.build(); + alpineVersionProperties = new Properties(); try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(ALPINE_VERSION_PROP_FILE)) { alpineVersionProperties.load(in); @@ -313,6 +318,20 @@ void init() { } } + /** + * @since 3.2.0 + */ + public org.eclipse.microprofile.config.Config getDelegate() { + return delegateConfig; + } + + /** + * @since 3.2.0 + */ + public T getMapping(final Class mappingClass) { + return delegateConfig.unwrap(SmallRyeConfig.class).getConfigMapping(mappingClass); + } + /** * Retrieves the path where the system.id is stored * @return a File representing the path to the system.id @@ -432,15 +451,10 @@ public File getDataDirectorty() { * @since 1.0.0 */ public String getProperty(Key key) { - final String envVariable = getPropertyFromEnvironment(key); - if (envVariable != null) { - return envVariable; - } - if (key.getDefaultValue() == null) { - return properties.getProperty(key.getPropertyName()); - } else { - return properties.getProperty(key.getPropertyName(), String.valueOf(key.getDefaultValue())); - } + return delegateConfig.getOptionalValue(key.getPropertyName(), String.class) + .orElseGet(() -> key.getDefaultValue() != null + ? String.valueOf(key.getDefaultValue()) + : null); } /** @@ -455,21 +469,17 @@ public String getProperty(Key key) { * @since 1.7.0 */ public String getPropertyOrFile(AlpineKey key) { - final AlpineKey fileKey = AlpineKey.valueOf(key.toString()+"_FILE"); - final String filePath = getProperty(fileKey); - final String prop = getProperty(key); - if (StringUtils.isNotBlank(filePath)) { - if (prop != null && !prop.equals(String.valueOf(key.getDefaultValue()))) { - LOGGER.warn(fileKey.getPropertyName() + " overrides value from property " + key.getPropertyName()); - } - try { - return new String(Files.readAllBytes(new File(PathUtil.resolve(filePath)).toPath())).replaceAll("\\s+", ""); - } catch (IOException e) { - LOGGER.error(filePath + " file doesn't exist or not readable."); - return null; - } - } - return prop; + return delegateConfig.getOptionalValue(key.getPropertyName() + ".file", String.class) + .map(filePath -> { + try { + return new String(Files.readAllBytes(new File(PathUtil.resolve(filePath)).toPath())).replaceAll("\\s+", ""); + } catch (IOException e) { + LOGGER.error(filePath + " file doesn't exist or not readable.", e); + return null; + } + }) + .or(() -> delegateConfig.getOptionalValue(key.getPropertyName(), String.class)) + .orElse(null); } /** @@ -538,9 +548,6 @@ public List getPropertyAsList(Key key) { * Their main use-case is to allow users to configure certain aspects of libraries and frameworks used by Alpine, * without Alpine having to introduce {@link AlpineKey}s for every single option. *

- * Properties are read from both environment variables, and {@link #PROP_FILE}. - * When a property is defined in both environment and {@code application.properties}, environment takes precedence. - *

* Properties must be prefixed with {@code ALPINE_} (for environment variables) or {@code alpine.} * (for {@code application.properties}) respectively. The Alpine prefix will be removed in keys of the returned * {@link Map}, but the given {@code prefix} will be retained. @@ -551,33 +558,25 @@ public List getPropertyAsList(Key key) { */ public Map getPassThroughProperties(final String prefix) { final var passThroughProperties = new HashMap(); - try { - for (final Map.Entry envVar : System.getenv().entrySet()) { - if (envVar.getKey().startsWith("ALPINE_%s_".formatted(prefix.toUpperCase().replace(".", "_")))) { - final String key = envVar.getKey().replaceFirst("^ALPINE_", "").toLowerCase().replace("_", "."); - passThroughProperties.put(key, envVar.getValue()); - } - } - } catch (SecurityException e) { - LOGGER.warn(""" - Unable to retrieve pass-through properties for prefix "%s" \ - from environment variables. Using defaults.""".formatted(prefix), e); - } - for (final Map.Entry property : properties.entrySet()) { - if (property.getKey() instanceof String key - && key.startsWith("alpine.%s.".formatted(prefix)) - && property.getValue() instanceof final String value) { - key = key.replaceFirst("^alpine\\.", ""); - if (!passThroughProperties.containsKey(key)) { // Environment variables take precedence - passThroughProperties.put(key, value); - } + for (final String propertyName : delegateConfig.getPropertyNames()) { + if (!propertyName.startsWith("alpine.%s.".formatted(prefix))) { + continue; } + + final String key = propertyName.replaceFirst("^alpine\\.", ""); + passThroughProperties.put(key, delegateConfig.getValue(propertyName, String.class)); } return passThroughProperties; } - static void reset() { - properties = null; + /** + * Reload the configuration. + * + * @since 3.2.0 + */ + static void reload() { + delegateConfig = null; + INSTANCE.init(); } /** @@ -589,7 +588,7 @@ static void reset() { */ @Deprecated public String getProperty(String key) { - return properties.getProperty(key); + return delegateConfig.getOptionalValue(key, String.class).orElse(null); } /** @@ -602,31 +601,7 @@ public String getProperty(String key) { */ @Deprecated public String getProperty(String key, String defaultValue) { - return properties.getProperty(key, defaultValue); - } - - /** - * Attempts to retrieve the key via environment variable. Property names are - * always upper case with periods replaced with underscores. - * - * alpine.worker.threads - * becomes - * ALPINE_WORKER_THREADS - * - * @param key the key to retrieve from environment - * @return the value of the key (if set), null otherwise. - * @since 1.4.3 - */ - private String getPropertyFromEnvironment(Key key) { - final String envVariable = key.getPropertyName().toUpperCase().replace(".", "_"); - try { - return StringUtils.trimToNull(System.getenv(envVariable)); - } catch (SecurityException e) { - LOGGER.warn("A security exception prevented access to the environment variable. Using defaults."); - } catch (NullPointerException e) { - // Do nothing. The key was not specified in an environment variable. Continue along. - } - return null; + return delegateConfig.getOptionalValue(key, String.class).orElse(defaultValue); } /** diff --git a/alpine-common/src/test/java/alpine/ConfigTest.java b/alpine-common/src/test/java/alpine/ConfigTest.java index 5ea32280..928bb3d1 100644 --- a/alpine-common/src/test/java/alpine/ConfigTest.java +++ b/alpine-common/src/test/java/alpine/ConfigTest.java @@ -1,32 +1,34 @@ package alpine; +import io.smallrye.config.ConfigMapping; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilderCustomizer; +import io.smallrye.config.WithDefault; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RestoreEnvironmentVariables; import org.junitpioneer.jupiter.RestoreSystemProperties; import org.junitpioneer.jupiter.SetEnvironmentVariable; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Map; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; public class ConfigTest { - @AfterEach - public void tearDown() { - Config.reset(); - } - @AfterAll - public static void tearDownClass() { - Config.getInstance().init(); // Ensure we're not affecting other tests + public static void tearDown() { + Config.reload(); // Ensure we're not affecting other tests } @Test public void testGetPassThroughPropertiesEmpty() { - Config.getInstance().init(); + Config.reload(); assertThat(Config.getInstance().getPassThroughProperties("datanucleus")).isEmpty(); } @@ -43,21 +45,73 @@ public void testGetPassThroughPropertiesEmpty() { @SetEnvironmentVariable(key = "ALPINE_DATANUCLEUS_FROM_ENV", value = "fromEnv7") @SetEnvironmentVariable(key = "alpine_datanucleus_from_env_lowercase", value = "fromEnv8") @SetEnvironmentVariable(key = "Alpine_DataNucleus_From_Env_MixedCase", value = "fromEnv9") - public void testGetPassThroughProperties() { + @SetEnvironmentVariable(key = "ALPINE_DATANUCLEUS_EXPRESSION_FROM_ENV", value = "${alpine.datanucleus.from.env}") + public void testGetPassThroughProperties() throws Exception { final URL propertiesUrl = ConfigTest.class.getResource("/Config_testGetPassThroughProperties.properties"); assertThat(propertiesUrl).isNotNull(); - System.setProperty("alpine.application.properties", propertiesUrl.getPath()); + final Path tmpPropertiesFile = Files.createTempFile(null, ".properties"); + Files.copy(propertiesUrl.openStream(), tmpPropertiesFile, StandardCopyOption.REPLACE_EXISTING); + + System.setProperty("alpine.application.properties", tmpPropertiesFile.toUri().toString()); - Config.getInstance().init(); + Config.reload(); assertThat(Config.getInstance().getPassThroughProperties("datanucleus")) .containsExactlyInAnyOrderEntriesOf(Map.of( "datanucleus.foo", "fromEnv3", // ENV takes precedence over properties "datanucleus.foo.bar", "fromEnv4", // ENV takes precedence over properties "datanucleus.from.env", "fromEnv7", - "datanucleus.from.props", "fromProps7" + "datanucleus.from.props", "fromProps7", + "datanucleus.from.env.lowercase", "fromEnv8", + "datanucleus.from.env.mixedcase", "fromEnv9", + "datanucleus.expression.from.props", "fromEnv3", + "datanucleus.expression.from.env", "fromEnv7" )); } + @ConfigMapping(prefix = "alpine") + public interface TestConfig { + + DatabaseConfig database(); + + interface DatabaseConfig { + + Optional url(); + + @WithDefault("testUser") + String username(); + + Map pool(); + + } + + } + + public static class ConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer { + + @Override + public void configBuilder(final SmallRyeConfigBuilder configBuilder) { + configBuilder + .withMapping(TestConfig.class) + .withValidateUnknown(false); + } + + } + + @Test + @RestoreEnvironmentVariables + @SetEnvironmentVariable(key = "ALPINE_DATABASE_URL", value = "jdbc:h2:mem:alpine") + @SetEnvironmentVariable(key = "ALPINE_DATABASE_POOL_MAX_SIZE", value = "666") + void testGetMapping() { + Config.reload(); + + final var testConfig = Config.getInstance().getMapping(TestConfig.class); + assertThat(testConfig).isNotNull(); + assertThat(testConfig.database().url()).contains("jdbc:h2:mem:alpine"); + assertThat(testConfig.database().username()).isEqualTo("testUser"); + assertThat(testConfig.database().pool()) + .containsExactlyInAnyOrderEntriesOf(Map.of("max.size", "666")); + } + } \ No newline at end of file diff --git a/alpine-common/src/test/java/alpine/common/util/ThreadUtilTest.java b/alpine-common/src/test/java/alpine/common/util/ThreadUtilTest.java index 29f56a9a..e38496cf 100644 --- a/alpine-common/src/test/java/alpine/common/util/ThreadUtilTest.java +++ b/alpine-common/src/test/java/alpine/common/util/ThreadUtilTest.java @@ -18,24 +18,52 @@ */ package alpine.common.util; +import alpine.Config; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RestoreEnvironmentVariables; import org.junitpioneer.jupiter.SetEnvironmentVariable; +import java.lang.reflect.Method; + class ThreadUtilTest { + private static Method configReloadMethod; + + @BeforeAll + public static void setUp() throws Exception { + configReloadMethod = Config.class.getDeclaredMethod("reload"); + configReloadMethod.setAccessible(true); + } + + @AfterEach + public void tearDown() throws Exception { + configReloadMethod.invoke(Config.getInstance()); + } + + @AfterAll + public static void tearDownClass() throws Exception { + configReloadMethod.invoke(Config.getInstance()); // Ensure we're not affecting other tests. + } + @Test @RestoreEnvironmentVariables @SetEnvironmentVariable(key = "ALPINE_WORKER_THREADS", value = "10") - void determineNumberOfWorkerThreadsStaticTest() { + void determineNumberOfWorkerThreadsStaticTest() throws Exception { + configReloadMethod.invoke(Config.getInstance()); + Assertions.assertEquals(10, ThreadUtil.determineNumberOfWorkerThreads()); } @Test @RestoreEnvironmentVariables @SetEnvironmentVariable(key = "ALPINE_WORKER_THREADS", value = "0") - void determineNumberOfWorkerThreadsDynamicTest() { + void determineNumberOfWorkerThreadsDynamicTest() throws Exception { + configReloadMethod.invoke(Config.getInstance()); + Assertions.assertTrue(ThreadUtil.determineNumberOfWorkerThreads() > 0); } diff --git a/alpine-common/src/test/resources/Config_testGetPassThroughProperties.properties b/alpine-common/src/test/resources/Config_testGetPassThroughProperties.properties index 17591ad7..432931dd 100644 --- a/alpine-common/src/test/resources/Config_testGetPassThroughProperties.properties +++ b/alpine-common/src/test/resources/Config_testGetPassThroughProperties.properties @@ -6,4 +6,5 @@ alpine.data.nucleus.foo=fromProps5 datanucleus.foo=fromProps6 alpine.datanucleus.from.props=fromProps7 ALPINE.DATANUCLEUS.FROM.PROPS.UPPERCASE=fromProps8 -Alpine.DataNucleus.From.Props.MixedCase=fromProps9 \ No newline at end of file +Alpine.DataNucleus.From.Props.MixedCase=fromProps9 +alpine.datanucleus.expression.from.props=${alpine.datanucleus.foo} \ No newline at end of file diff --git a/alpine-common/src/test/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer b/alpine-common/src/test/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer new file mode 100644 index 00000000..804cf05c --- /dev/null +++ b/alpine-common/src/test/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer @@ -0,0 +1 @@ +alpine.ConfigTest$ConfigBuilderCustomizer \ No newline at end of file diff --git a/alpine-test/pom.xml b/alpine-test/pom.xml new file mode 100644 index 00000000..b14bdb8a --- /dev/null +++ b/alpine-test/pom.xml @@ -0,0 +1,55 @@ + + + + us.springett + alpine-parent + 3.1.3-SNAPSHOT + + 4.0.0 + + alpine-test + jar + 3.1.3-SNAPSHOT + + alpine-test + + An opinionated scaffolding library that jumpstarts Java projects with an API-first design, + secure defaults, and minimal dependencies. + + https://github.com/stevespringett/Alpine + 2016 + + + 4.13.2 + + + + + us.springett + alpine-common + ${project.parent.version} + provided + + + + junit + junit + ${lib.junit4.version} + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.vintage + junit-vintage-engine + ${lib.junit-jupiter.version} + test + + + + \ No newline at end of file diff --git a/alpine-test/src/main/java/alpine/test/config/ConfigPropertyRule.java b/alpine-test/src/main/java/alpine/test/config/ConfigPropertyRule.java new file mode 100644 index 00000000..1e9783db --- /dev/null +++ b/alpine-test/src/main/java/alpine/test/config/ConfigPropertyRule.java @@ -0,0 +1,71 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * A JUnit 4 {@link TestRule} to set config properties. + * + * @since 3.2.0 + */ +public class ConfigPropertyRule implements TestRule { + + private final Map properties = new HashMap<>(); + + @Override + public Statement apply(final Statement statement, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + InMemoryConfigSource.setProperties(properties); + + final var annotation = description.getAnnotation(WithConfigProperty.class); + if (annotation != null) { + Arrays.stream(annotation.value()) + .map(value -> value.split("=", 2)) + .forEach(valueParts -> InMemoryConfigSource.setProperty(valueParts[0], valueParts[1])); + } + + try { + statement.evaluate(); + } finally { + InMemoryConfigSource.clear(); + } + } + }; + } + + public ConfigPropertyRule withProperty(final String key, final String value) { + properties.put(key, value); + return this; + } + + public ConfigPropertyRule withProperties(final Map properties) { + this.properties.putAll(properties); + return this; + } + +} diff --git a/alpine-test/src/main/java/alpine/test/config/InMemoryConfigSource.java b/alpine-test/src/main/java/alpine/test/config/InMemoryConfigSource.java new file mode 100644 index 00000000..0807f012 --- /dev/null +++ b/alpine-test/src/main/java/alpine/test/config/InMemoryConfigSource.java @@ -0,0 +1,68 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * An in-memory {@link ConfigSource} intended for usage in tests. + * + * @since 3.2.0 + */ +class InMemoryConfigSource implements ConfigSource { + + private static final Map PROPERTIES = new ConcurrentHashMap<>(); + + static void setProperties(final Map properties) { + PROPERTIES.putAll(properties); + } + + static void setProperty(final String key, final String value) { + PROPERTIES.put(key, value); + } + + static void clear() { + PROPERTIES.clear(); + } + + @Override + public int getOrdinal() { + return Integer.MAX_VALUE; + } + + @Override + public Set getPropertyNames() { + return PROPERTIES.keySet(); + } + + @Override + public String getValue(final String propertyName) { + return PROPERTIES.get(propertyName); + } + + @Override + public String getName() { + return InMemoryConfigSource.class.getSimpleName(); + } + +} diff --git a/alpine-test/src/main/java/alpine/test/config/TestConfigBuilderCustomizer.java b/alpine-test/src/main/java/alpine/test/config/TestConfigBuilderCustomizer.java new file mode 100644 index 00000000..dbc0ad4f --- /dev/null +++ b/alpine-test/src/main/java/alpine/test/config/TestConfigBuilderCustomizer.java @@ -0,0 +1,34 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilderCustomizer; + +/** + * @since 3.2.0 + */ +public class TestConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer { + + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + builder.withSources(new InMemoryConfigSource()); + } + +} diff --git a/alpine-test/src/main/java/alpine/test/config/WithConfigProperty.java b/alpine-test/src/main/java/alpine/test/config/WithConfigProperty.java new file mode 100644 index 00000000..7a0f0c27 --- /dev/null +++ b/alpine-test/src/main/java/alpine/test/config/WithConfigProperty.java @@ -0,0 +1,37 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @since 3.2.0 + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface WithConfigProperty { + + String[] value() default {}; + +} diff --git a/alpine-test/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer b/alpine-test/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer new file mode 100644 index 00000000..8b57c1f2 --- /dev/null +++ b/alpine-test/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer @@ -0,0 +1 @@ +alpine.test.config.TestConfigBuilderCustomizer \ No newline at end of file diff --git a/alpine-test/src/test/java/alpine/test/config/ConfigPropertyRuleTest.java b/alpine-test/src/test/java/alpine/test/config/ConfigPropertyRuleTest.java new file mode 100644 index 00000000..ad68336c --- /dev/null +++ b/alpine-test/src/test/java/alpine/test/config/ConfigPropertyRuleTest.java @@ -0,0 +1,46 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import alpine.Config; +import org.junit.Rule; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ConfigPropertyRuleTest { + + @Rule + public ConfigPropertyRule rule = new ConfigPropertyRule() + .withProperty("foo.bar", "baz") + .withProperty("oof.rab", "${foo.bar}"); + + @Test + @WithConfigProperty(value = { + "local.foo.bar=local-baz", + "lacol.oof.rab=${local.foo.bar}"}) + @SuppressWarnings("deprecation") + public void test() { + assertEquals("baz", Config.getInstance().getProperty("foo.bar")); + assertEquals("baz", Config.getInstance().getProperty("oof.rab")); + assertEquals("local-baz", Config.getInstance().getProperty("local.foo.bar")); + assertEquals("local-baz", Config.getInstance().getProperty("lacol.oof.rab")); + } + +} \ No newline at end of file diff --git a/alpine-test/src/test/java/alpine/test/config/InMemoryConfigSourceTest.java b/alpine-test/src/test/java/alpine/test/config/InMemoryConfigSourceTest.java new file mode 100644 index 00000000..23503a09 --- /dev/null +++ b/alpine-test/src/test/java/alpine/test/config/InMemoryConfigSourceTest.java @@ -0,0 +1,44 @@ +/* + * This file is part of Alpine. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.test.config; + +import alpine.Config; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class InMemoryConfigSourceTest { + + @Test + @SuppressWarnings("deprecation") + void test() { + assertNull(Config.getInstance().getProperty("foo.bar")); + + InMemoryConfigSource.setProperty("foo.bar", "baz"); + InMemoryConfigSource.setProperty("oof.rab", "${foo.bar}"); + assertEquals("baz", Config.getInstance().getProperty("foo.bar")); + assertEquals("baz", Config.getInstance().getProperty("oof.rab")); + + InMemoryConfigSource.clear(); + assertNull(Config.getInstance().getProperty("foo.bar")); + assertNull(Config.getInstance().getProperty("oof.rab")); + } + +} \ No newline at end of file diff --git a/alpine-test/src/test/resources/alpine.version b/alpine-test/src/test/resources/alpine.version new file mode 100644 index 00000000..b5846a3d --- /dev/null +++ b/alpine-test/src/test/resources/alpine.version @@ -0,0 +1,4 @@ +name=${project.name} +version=${project.version} +timestamp=${timestamp} +uuid=${project.build.uuid} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 69100f83..923af115 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ alpine-infra alpine-server alpine-executable-war + alpine-test alpine-parent @@ -191,6 +192,7 @@ 1.1.7 1.1.7 2.0.12 + 3.10.2 2.2.27 5.11.4 @@ -228,6 +230,12 @@ jersey-client ${lib.jersey.version} + + + io.smallrye.config + smallrye-config-core + ${lib.smallrye-config.version} + jakarta.servlet