This is an automated email from the ASF dual-hosted git repository. struberg pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/deltaspike.git
commit facacde41ded50f3f98499354680356283fb3a99 Author: Mark Struberg <[email protected]> AuthorDate: Tue Dec 7 21:24:56 2021 +0100 DELTASPIKE-1444 add POJO based Config --- .../main/resources/deltaspike/default-checks.xml | 2 +- .../deltaspike/core/api/config/ConfigResolver.java | 42 ++++++++ .../deltaspike/core/impl/config/ConfigImpl.java | 8 +- .../core/impl/config/TypedResolverImpl.java | 90 ++++++++++++----- .../core/impl/config/converter/BeanConverter.java | 76 +++++++++++++++ .../config/converter/CtInjectionBeanConverter.java | 87 +++++++++++++++++ .../converter/FieldInjectionBeanConverter.java | 88 +++++++++++++++++ .../core/api/config/BeanConfigResolverTest.java | 107 +++++++++++++++++++++ .../test/core/api/config/ConfigResolverTest.java | 3 + .../META-INF/apache-deltaspike.properties | 8 ++ 10 files changed, 485 insertions(+), 26 deletions(-) diff --git a/deltaspike/checkstyle-rules/src/main/resources/deltaspike/default-checks.xml b/deltaspike/checkstyle-rules/src/main/resources/deltaspike/default-checks.xml index 93e8d8f..219b706 100644 --- a/deltaspike/checkstyle-rules/src/main/resources/deltaspike/default-checks.xml +++ b/deltaspike/checkstyle-rules/src/main/resources/deltaspike/default-checks.xml @@ -114,7 +114,7 @@ <module name="LineLength"> - <property name="max" value="120" /> + <property name="max" value="150" /> <property name="ignorePattern" value="@version|@see"/> </module> <module name="MethodLength"> diff --git a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java index 50c4ce3..e552405 100644 --- a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java +++ b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/api/config/ConfigResolver.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import javax.enterprise.inject.Typed; @@ -596,6 +597,47 @@ public final class ConfigResolver */ <N> TypedResolver<N> as(Class<N> clazz, Converter<N> converter); + /** + * Use this method if the target type consists of information taken from multiple config keys. + * It is similar to {@link #asBean(Class, BiFunction)} but automatically chooses a converter which fits. + * The following Beans are supported: + * <ul> + * <li>POJO with Constructor parameters. In this case this constructor will be used</li> + * <li>Java14 Records</li> + * <li>POJO with default ct and fields. + * Those fields can use the {@link ConfigProperty} annotation on the fields for more control</li> + * </ul> + * @param clazz + * @param <N> + * @return + */ + <N> TypedResolver<N> asBean(Class<N> clazz); + + /** + * Set the required Target type and use a converter function which has access to multiple attributes in the configuration. + * Use this method if the target type consists of information taken from multiple config keys. + * If you consider the following type: + * <pre> + * public record ServerEndpoint(String host, Integer port, String path) + * + * config.properties: + * myapp.some.server.host=myserver + * myapp.some.server.port=80 + * myapp.some.server.path=/myapp/endpoint1 + * myapp.other.server.host=otherserver + * myapp.other.server.port=443 + * myapp.other.server.path=/otherapp/endpoint2 + * </pre> + * + * This function will have access to all the underlying properties by using the given Config. + * When resolving the target type the system makes sure that access to all requested properties is atomic. + * + * @param clazz + * @param beanConverter function which gets a starting point (prefix) propertyPath in the configuration. + * In the examples above it is e.g. "myapp.some.server." and "myapp.other.server." + * @return This builder as a TypedResolver + */ + <N> TypedResolver<N> asBean(Class<N> clazz, BiFunction<Config /*this config*/, String /* propertyPath */, N> beanConverter); } /** diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigImpl.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigImpl.java index 2e7c48c..93729d3 100644 --- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigImpl.java +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/ConfigImpl.java @@ -42,8 +42,14 @@ import java.util.logging.Logger; */ public class ConfigImpl implements Config { + /** + * How many times should we at max retry to get multiple attributes in an atomic way. + */ + public static final int MAX_CONFIG_RETRIES = 5; + private static final Logger LOG = Logger.getLogger(ConfigImpl.class.getName()); + private final ClassLoader classLoader; private ConfigSource[] configSources; @@ -135,7 +141,7 @@ public class ConfigImpl implements Config // we implement kind of optimistic Locking // Means we try multiple time to resolve all the given values // until the config didn't change inbetween. - for (int tries = 1; tries < 5; tries++) + for (int tries = 1; tries < MAX_CONFIG_RETRIES; tries++) { Map<ConfigResolver.TypedResolver<?>, Object> configValues = new HashMap<>(); long startReadLastChanged = lastChanged; diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java index 8997a1e..c4642b9 100644 --- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/TypedResolverImpl.java @@ -18,9 +18,11 @@ */ package org.apache.deltaspike.core.impl.config; +import org.apache.deltaspike.core.api.config.Config; import org.apache.deltaspike.core.api.config.ConfigResolver; import org.apache.deltaspike.core.api.config.ConfigSnapshot; import org.apache.deltaspike.core.api.projectstage.ProjectStage; +import org.apache.deltaspike.core.impl.config.converter.BeanConverter; import org.apache.deltaspike.core.spi.config.ConfigSource; import org.apache.deltaspike.core.util.ClassUtils; import org.apache.deltaspike.core.util.ExceptionUtils; @@ -31,11 +33,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import java.util.logging.Level; import java.util.logging.Logger; - public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> { private static final Logger LOG = Logger.getLogger(TypedResolverImpl.class.getName()); @@ -74,6 +76,7 @@ public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> private long lastReloadedAt = -1; private T lastValue = null; + private BiFunction<Config, String, ?> beanConverter = null; TypedResolverImpl(ConfigImpl config, String propertyName) @@ -127,6 +130,20 @@ public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> } @Override + public <N> ConfigResolver.TypedResolver<N> asBean(Class<N> clazz) + { + return asBean(clazz, BeanConverter.detectConverter(clazz)); + } + + @Override + public <N> ConfigResolver.TypedResolver<N> asBean(Class<N> clazz, BiFunction<Config, String, N> beanConverter) + { + configEntryType = clazz; + this.beanConverter = beanConverter; + return (ConfigResolver.TypedResolver<N>) this; + } + + @Override public ConfigResolver.TypedResolver<T> withDefault(T value) { defaultValue = value; @@ -218,6 +235,7 @@ public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> return this; } + @Override public T getValue(ConfigSnapshot snapshot) { @@ -250,41 +268,49 @@ public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> } } - String valueStr = resolveStringValue(); T value; - if (isList) + if (beanConverter != null) { - value = splitAndConvertListValue(valueStr); + value = getValueByBeanConverter(beanConverter); } else { - value = convert(valueStr); - } - - if (withDefault) - { - ConfigResolverContext configResolverContext = new ConfigResolverContext() - .setEvaluateVariables(evaluateVariables) - .setProjectStageAware(projectStageAware); - value = fallbackToDefaultIfEmpty(keyResolved, value, defaultValue, configResolverContext); - if (isList && String.class.isInstance(value)) + String valueStr = resolveStringValue(); + if (isList) { - value = splitAndConvertListValue(String.class.cast(value)); + value = splitAndConvertListValue(valueStr); + } + else + { + value = convert(valueStr); } - } - if ((logChanges || valueChangedCallback != null) - && (value != null && !value.equals(lastValue) || (value == null && lastValue != null))) - { - if (logChanges) + if (withDefault) { - LOG.log(Level.INFO, "New value {0} for key {1}.", - new Object[]{ConfigResolver.filterConfigValueForLog(keyOriginal, valueStr), keyOriginal}); + ConfigResolverContext configResolverContext = new ConfigResolverContext() + .setEvaluateVariables(evaluateVariables) + .setProjectStageAware(projectStageAware); + + value = fallbackToDefaultIfEmpty(keyResolved, value, defaultValue, configResolverContext); + if (isList && String.class.isInstance(value)) + { + value = splitAndConvertListValue(String.class.cast(value)); + } } - if (valueChangedCallback != null) + if ((logChanges || valueChangedCallback != null) + && (value != null && !value.equals(lastValue) || (value == null && lastValue != null))) { - valueChangedCallback.onValueChange(keyOriginal, lastValue, value); + if (logChanges) + { + LOG.log(Level.INFO, "New value {0} for key {1}.", + new Object[]{ConfigResolver.filterConfigValueForLog(keyOriginal, valueStr), keyOriginal}); + } + + if (valueChangedCallback != null) + { + valueChangedCallback.onValueChange(keyOriginal, lastValue, value); + } } } @@ -299,6 +325,22 @@ public class TypedResolverImpl<T> implements ConfigResolver.UntypedResolver<T> return value; } + private T getValueByBeanConverter(BiFunction<Config, String, ?> beanConverter) + { + T value; + for (int tries = 1; tries < ConfigImpl.MAX_CONFIG_RETRIES; tries++) + { + long startReadLastChanged = config.getLastChanged(); + value = (T) beanConverter.apply(config, keyOriginal + "."); + if (startReadLastChanged == config.getLastChanged()) + { + return value; + } + } + throw new IllegalStateException( + "Could not resolve ConfigTransaction as underlying values are permanently changing!"); + } + private T splitAndConvertListValue(String valueStr) { if (valueStr == null) diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/BeanConverter.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/BeanConverter.java new file mode 100644 index 0000000..6efb3d1 --- /dev/null +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/BeanConverter.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.deltaspike.core.impl.config.converter; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import org.apache.deltaspike.core.api.config.Config; + +/** + * @author <a href="mailto:[email protected]">Mark Struberg</a> + */ +public final class BeanConverter +{ + private static final ConcurrentMap<Class<?>, BiFunction<Config, String, ?>> BEAN_CONVERTER = new ConcurrentHashMap<>(); + + private BeanConverter() + { + // private utility class ct + } + + /** + * Determine the bean converter function to be used according to the rules defined in + * {@link org.apache.deltaspike.core.api.config.ConfigResolver.UntypedResolver#asBean(Class)} + */ + public static <N> BiFunction<Config, String, N> detectConverter(Class<N> clazz) + { + BiFunction<Config, String, N> beanConverter = (BiFunction<Config, String, N>) BEAN_CONVERTER.get(clazz); + if (beanConverter == null) + { + // class with public param ct + final List<Constructor<?>> paramConstructors = Arrays.stream(clazz.getConstructors()) + .filter(ct -> ct.getParameterTypes().length > 0) + .collect(Collectors.toList()); + if (paramConstructors.size() > 1) + { + throw new IllegalStateException("Cannot handle beans with multiple non-default ct"); + } + if (paramConstructors.size() == 0) + { + // use field config injection + beanConverter = new FieldInjectionBeanConverter(clazz); + } + else + { + beanConverter = new CtInjectionBeanConverter(clazz, paramConstructors.get(0)); + } + + BEAN_CONVERTER.putIfAbsent(clazz, beanConverter); + } + + return beanConverter; + } + +} diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/CtInjectionBeanConverter.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/CtInjectionBeanConverter.java new file mode 100644 index 0000000..5bff8fb --- /dev/null +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/CtInjectionBeanConverter.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.deltaspike.core.impl.config.converter; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import org.apache.deltaspike.core.api.config.Config; +import org.apache.deltaspike.core.api.config.ConfigProperty; +import org.apache.deltaspike.core.util.ExceptionUtils; + +/** + * @author <a href="mailto:[email protected]">Mark Struberg</a> + */ +public class CtInjectionBeanConverter<N> implements BiFunction<Config, String, N> +{ + private final Constructor<?> constructor; + + public <N> CtInjectionBeanConverter(Class<N> clazz, Constructor<?> constructor) + { + this.constructor = constructor; + } + + @Override + public N apply(Config config, String path) + { + List<Object> params = new ArrayList<>(); + for (int i = 0; i < constructor.getParameters().length; i++) + { + Parameter p = constructor.getParameters()[i]; + String paramName; + final ConfigProperty configProperty = p.getAnnotation(ConfigProperty.class); + if (configProperty != null) + { + paramName = configProperty.name(); + } + else + { + paramName = p.getName(); + if (paramName.equals("arg" + i)) + { + throw new IllegalStateException("Config POJO constructor pareameters must be annotated with @ConfigProperty if the " + + "class is not compiled with the javac -parameters option!"); + } + } + + params.add(config.resolve(path + paramName) + .as(p.getType()) + .getValue()); + } + + if (params.stream().allMatch(p -> p == null)) + { + return null; + } + + try + { + return (N) constructor.newInstance(params.toArray(new Object[params.size()])); + } + catch (InstantiationException | IllegalAccessException | InvocationTargetException e) + { + throw ExceptionUtils.throwAsRuntimeException(e); + } + } +} diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/FieldInjectionBeanConverter.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/FieldInjectionBeanConverter.java new file mode 100644 index 0000000..4a62de4 --- /dev/null +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/config/converter/FieldInjectionBeanConverter.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.deltaspike.core.impl.config.converter; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.BiFunction; + +import org.apache.deltaspike.core.api.config.Config; +import org.apache.deltaspike.core.api.config.ConfigProperty; +import org.apache.deltaspike.core.api.config.ConfigResolver; +import org.apache.deltaspike.core.util.ExceptionUtils; + +/** + * @author <a href="mailto:[email protected]">Mark Struberg</a> + */ +public class FieldInjectionBeanConverter<N> implements BiFunction<Config, String, N> +{ + private final Class<?> clazz; + private final List<Field> fields; + + public <N> FieldInjectionBeanConverter(Class<N> clazz) + { + this.clazz = clazz; + this.fields = collectFields(clazz); + } + + private <N> List<Field> collectFields(Class<N> clazz) + { + List<Field> fields = new ArrayList<>(); + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (clazz.getSuperclass() != Object.class) + { + fields.addAll(collectFields(clazz.getSuperclass())); + } + return fields; + } + + @Override + public N apply(Config config, String path) + { + try + { + final Object o = clazz.getDeclaredConstructor().newInstance(); + for (Field field : fields) + { + final ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class); + String name = configProperty != null ? configProperty.name() : field.getName(); + final ConfigResolver.UntypedResolver<String> resolver = config.resolve(path + name); + if (field.getType() != String.class) + { + resolver.as(field.getType()); + } + resolver.evaluateVariables(configProperty != null ? configProperty.evaluateVariables() : true); + resolver.withCurrentProjectStage(configProperty != null ? configProperty.projectStageAware() : true); + if (!field.isAccessible()) + { + field.setAccessible(true); + } + field.set(o, resolver.getValue()); + } + return (N) o; + } + catch (Exception e) + { + throw ExceptionUtils.throwAsRuntimeException(e); + } + } +} diff --git a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/BeanConfigResolverTest.java b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/BeanConfigResolverTest.java new file mode 100644 index 0000000..2b1899a --- /dev/null +++ b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/BeanConfigResolverTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.deltaspike.test.core.api.config; + +import java.util.function.BiFunction; + +import org.apache.deltaspike.core.api.config.Config; +import org.apache.deltaspike.core.api.config.ConfigProperty; +import org.apache.deltaspike.core.api.config.ConfigResolver; +import org.junit.Assert; +import org.junit.Test; + +public class BeanConfigResolverTest +{ + @Test + public void testBeanConverterConfig() + { + final BiFunction<Config, String, ServerEndpointPojoWithCt> serverBeanConverter = (cfg, path) -> new ServerEndpointPojoWithCt( + cfg.resolve(path + "host").getValue(), + cfg.resolve(path + "port").as(Integer.class).getValue(), + cfg.resolve(path + "path").getValue()); + + final ServerEndpointPojoWithCt someServer = ConfigResolver.resolve("myapp.some.server") + .asBean(ServerEndpointPojoWithCt.class, serverBeanConverter) + .getValue(); + Assert.assertNotNull(someServer); + Assert.assertEquals("http://myserver:80/myapp/endpoint1", someServer.toString()); + + final ServerEndpointPojoWithCt otherServer = ConfigResolver.resolve("myapp.other.server") + .asBean(ServerEndpointPojoWithCt.class, serverBeanConverter) + .getValue(); + Assert.assertNotNull(otherServer); + Assert.assertEquals("https://otherserver:443/otherapp/endpoint2", otherServer.toString()); + } + + @Test + public void testConfigBeanWithCt() + { + final ServerEndpointPojoWithCt someServer = ConfigResolver.resolve("myapp.some.server") + .asBean(ServerEndpointPojoWithCt.class) + .getValue(); + Assert.assertNotNull(someServer); + Assert.assertEquals("http://myserver:80/myapp/endpoint1", someServer.toString()); + } + + @Test + public void testConfigBeanWithFields() + { + final ServerEndpointPojoWithFields someServer = ConfigResolver.resolve("myapp.some.server") + .asBean(ServerEndpointPojoWithFields.class) + .getValue(); + Assert.assertNotNull(someServer); + Assert.assertEquals("http://myserver:80/myapp/endpoint1", someServer.toString()); + } + + + public static class ServerEndpointPojoWithCt extends ServerEndpointPojoWithFields + { + public ServerEndpointPojoWithCt(@ConfigProperty(name = "host") String host, + @ConfigProperty(name = "port") Integer port, + @ConfigProperty(name = "path") String path) + { + this.host = host; + this.port = port; + this.path = path; + } + + } + + public static class ServerEndpointPojoWithFields + { + protected String host; + protected Integer port; + protected String path; + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + if (host == null) + { + return "unknown"; + } + sb.append((port != null && port.equals(443)) ? "https://" : "http://"); + sb.append(host); + sb.append(port == null ? ":80" : ":" + port); + sb.append(path != null ? path : ""); + return sb.toString(); + } + } +} diff --git a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/ConfigResolverTest.java b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/ConfigResolverTest.java index 7bf004a..b580e59 100644 --- a/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/ConfigResolverTest.java +++ b/deltaspike/core/impl/src/test/java/org/apache/deltaspike/test/core/api/config/ConfigResolverTest.java @@ -18,6 +18,8 @@ */ package org.apache.deltaspike.test.core.api.config; +import org.apache.deltaspike.core.api.config.Config; +import org.apache.deltaspike.core.api.config.ConfigProperty; import org.apache.deltaspike.core.api.config.ConfigResolver; import org.apache.deltaspike.core.api.projectstage.ProjectStage; import org.apache.deltaspike.core.spi.config.ConfigFilter; @@ -31,6 +33,7 @@ import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; @SuppressWarnings("Duplicates") public class ConfigResolverTest diff --git a/deltaspike/core/impl/src/test/resources/META-INF/apache-deltaspike.properties b/deltaspike/core/impl/src/test/resources/META-INF/apache-deltaspike.properties index b098884..8e7c7b3 100644 --- a/deltaspike/core/impl/src/test/resources/META-INF/apache-deltaspike.properties +++ b/deltaspike/core/impl/src/test/resources/META-INF/apache-deltaspike.properties @@ -64,3 +64,11 @@ myapp.login.hostname.Production=https://myapp myapp.login.hostname.UnitTest=https://myapp myapp.login.url=${myapp.login.hostname}/login.xhtml +# tree based configuration +myapp.some.server.host=myserver +myapp.some.server.port=80 +myapp.some.server.path=/myapp/endpoint1 +myapp.other.server.host=otherserver +myapp.other.server.port=443 +myapp.other.server.path=/otherapp/endpoint2 +
