MEECROWAVE-203 adding configuration.complete support for meecrowave.properties
diff --git a/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java b/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
index 0028c60..748cd6f 100644
--- a/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
+++ b/meecrowave-core/src/main/java/org/apache/meecrowave/Meecrowave.java
@@ -2051,8 +2051,9 @@
public Builder loadFrom(final String resource) {
// load all of those files on the classpath, sorted by ordinal
- Properties config = PropertyLoader.getProperties(resource);
- if (config == null) {
+ Properties config = PropertyLoader.getProperties(resource,
+ sortedProperties -> mergeProperties(resource, sortedProperties));
+ if (config == null || config.isEmpty()) {
final File file = new File(resource);
if (file.exists()) {
config = new Properties();
@@ -2369,6 +2370,27 @@
public void setMeecrowaveProperties(final String meecrowaveProperties) {
this.meecrowaveProperties = meecrowaveProperties;
}
+
+ private Properties mergeProperties(final String resource, final List<Properties> sortedProperties) {
+ Properties mergedProperties = new Properties();
+ Properties master = null;
+ for (final Properties p : sortedProperties)
+ {
+ if (Boolean.parseBoolean(p.getProperty("configuration.complete", "false"))) {
+ if (master != null) {
+ throw new IllegalArgumentException("Ambiguous '" + resource + "', " +
+ "multiple " + resource + " with configuration.complete=true");
+ }
+ master = p;
+ }
+ mergedProperties.putAll(p);
+ }
+
+ if (master != null) {
+ return master;
+ }
+ return mergedProperties;
+ }
}
public static class ValueTransformers implements Function<String, String> {
diff --git a/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java b/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
index a652d76..94916dc 100644
--- a/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
+++ b/meecrowave-core/src/test/java/org/apache/meecrowave/MeecrowaveTest.java
@@ -28,6 +28,7 @@
import org.superbiz.app.RsApp;
import org.superbiz.app.TestJsonEndpoint;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
@@ -35,10 +36,17 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
import java.util.Properties;
import java.util.stream.Stream;
+import static java.util.Arrays.asList;
+import static java.util.Collections.enumeration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -46,6 +54,37 @@
public class MeecrowaveTest {
@Test
+ public void conflictingConfig() throws MalformedURLException {
+ withConfigClassLoader(() -> {
+ try {
+ new Meecrowave.Builder().loadFrom("test.config.properties");
+ fail("should have failed since it conflicts");
+ } catch (final IllegalArgumentException iae) {
+ // ok
+ }
+ }, configUrl("configuration.complete=true\nf=1"), configUrl("configuration.complete=true\nf=2"));
+ }
+
+ @Test
+ public void masterConfig() throws MalformedURLException {
+ withConfigClassLoader(() -> {
+ final Meecrowave.Builder builder = new Meecrowave.Builder();
+ builder.loadFrom("test.config.properties");
+ assertEquals(1, builder.getHttpPort());
+ }, configUrl("http=2"), configUrl("configuration.complete=true\nhttp=1"), configUrl("http=3"));
+ }
+
+ @Test
+ public void mergedConfig() throws MalformedURLException {
+ withConfigClassLoader(() -> {
+ final Meecrowave.Builder builder = new Meecrowave.Builder();
+ builder.loadFrom("test.config.properties");
+ assertEquals(2, builder.getHttpPort());
+ assertEquals(4, builder.getHttpsPort());
+ }, configUrl("http=2\nconfiguration.ordinal=2\nhttps=4"), configUrl("http=3\nconfiguration.ordinal=1"));
+ }
+
+ @Test
public void configBinding() {
final MyConfig config = new Meecrowave.Builder()
.property("my-prefix-port", "1234")
@@ -160,4 +199,51 @@
@CliOption(name = "my-prefix-bool", description = "")
private boolean bool;
}
+
+ private static URL configUrl(final String content) throws MalformedURLException {
+ return new URL("memory", null, -1, "test.config.properties", new MemoryHandler(content));
+ }
+
+ private static void withConfigClassLoader(final Runnable test, final URL... urls) {
+ final Thread thread = Thread.currentThread();
+ final ClassLoader loader = thread.getContextClassLoader();
+ thread.setContextClassLoader(new ClassLoader(loader) {
+ @Override
+ public Enumeration<URL> getResources(final String name) throws IOException {
+ return "test.config.properties".equals(name) ? enumeration(asList(urls)) : super.getResources(name);
+ }
+ });
+ try {
+ test.run();
+ } finally {
+ thread.setContextClassLoader(loader);
+ }
+ }
+
+ private static class MemoryHandler extends URLStreamHandler
+ {
+ private final String content;
+
+ private MemoryHandler(final String content)
+ {
+ this.content = content;
+ }
+
+ @Override
+ protected URLConnection openConnection(final URL u) {
+ return new URLConnection(u)
+ {
+ @Override
+ public void connect()
+ {
+ // no-op
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+ }
+ };
+ }
+ }
}
diff --git a/meecrowave-doc/src/main/jbake/content/meecrowave-core/configuration.adoc b/meecrowave-doc/src/main/jbake/content/meecrowave-core/configuration.adoc
index c49a74d..06e60e7 100755
--- a/meecrowave-doc/src/main/jbake/content/meecrowave-core/configuration.adoc
+++ b/meecrowave-doc/src/main/jbake/content/meecrowave-core/configuration.adoc
@@ -124,7 +124,11 @@
- `configurationCustomizer.x=y` will set `x` to `y` for the customizer
TIP: Out of the box, any `Builder` instance will read `meecrowave.properties`.
-`meecrowave.properties` uses CLI names (without the leading `--`).
+`meecrowave.properties` uses CLI names (without the leading `--`). It loads all available files from the classpath,
+they are merged using `configuration.ordinal` key (exactly like Apache OpenWebBeans does for its configuration).
+It also supports `configuration.complete=[true|false]` which enables a single file to host it with the `true` value
+and will consider this file as the merged result of all potential files found in the classpath. It is useful to
+avoid an implicit merging and can typically be used in `conf/meecrowave.properties` in bundle mode.
See link:{context_rootpath}/meecrowave-core/cli.html[CLI] page for the list.
=== Valve configuration
diff --git a/pom.xml b/pom.xml
index 446a8aa..77efdc8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,7 +51,7 @@
<junit.version>4.13-beta-3</junit.version>
<tomcat.version>9.0.22</tomcat.version>
- <openwebbeans.version>2.0.11</openwebbeans.version>
+ <openwebbeans.version>2.0.12-SNAPSHOT</openwebbeans.version>
<cxf.version>3.3.2</cxf.version>
<johnzon.version>1.1.13-SNAPSHOT</johnzon.version>
<log4j2.version>2.11.2</log4j2.version>