SLING-4126 : Provide a mechanism to merge configurations

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1684183 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/provisioning/model/ModelConstants.java b/src/main/java/org/apache/sling/provisioning/model/ModelConstants.java
index e5e74dd..b65ae84 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ModelConstants.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ModelConstants.java
@@ -45,12 +45,30 @@
     /** Format of the unprocessed configuration values. */
     public static final String CFG_UNPROCESSED_FORMAT = ":rawconfig.format";
 
+    /**
+     * Format of the unprocessed configuration values.
+     * @since 1.1
+     */
+    public static final String CFG_UNPROCESSED_MODE = ":rawconfig.mode";
+
     /** Format of the Apache Felix Config Admin. */
     public static final String CFG_FORMAT_FELIX_CA = "felixca";
 
     /** Property file format. */
     public static final String CFG_FORMAT_PROPERTIES = "properties";
 
+    /**
+     * Mode for overwriting a configuration.
+     * @since 1.1
+     */
+    public static final String CFG_MODE_OVERWRITE = "overwrite";
+
+    /**
+     * Mode for merging a configuration
+     * @since 1.1
+     */
+    public static final String CFG_MODE_MERGE = "merge";
+
     /** Name of the webapp run mode. */
     public static final String RUN_MODE_WEBAPP = ":webapp";
 
diff --git a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
index 8c9087f..aa64ed9 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
@@ -24,8 +24,10 @@
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.felix.cm.file.ConfigurationHandler;
 
@@ -125,11 +127,8 @@
                 // configurations
                 for(final Configuration config : runMode.getConfigurations()) {
                     final Configuration found = baseRunMode.getOrCreateConfiguration(config.getPid(), config.getFactoryPid());
-                    final Enumeration<String> e = config.getProperties().keys();
-                    while ( e.hasMoreElements() ) {
-                        final String key = e.nextElement();
-                        found.getProperties().put(key, config.getProperties().get(key));
-                    }
+
+                    mergeConfiguration(found, config);
                 }
 
                 // settings
@@ -142,6 +141,79 @@
     }
 
     /**
+     * Merge two configurations
+     * @param baseConfig The base configuration.
+     * @param mergeConfig The merge configuration.
+     */
+    private static void mergeConfiguration(final Configuration baseConfig, final Configuration mergeConfig) {
+        // check for merge mode
+        final boolean baseIsRaw = baseConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED) != null;
+        final boolean mergeIsRaw = mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED) != null;
+        // simplest case, both are raw
+        if ( baseIsRaw && mergeIsRaw ) {
+            final String cfgMode = (String)mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE);
+            if ( cfgMode == null || ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode) ) {
+                copyConfigurationProperties(baseConfig, mergeConfig);
+            } else {
+                final Configuration newConfig = new Configuration(baseConfig.getPid(), baseConfig.getFactoryPid());
+                getProcessedConfiguration(newConfig, baseConfig);
+                clearConfiguration(baseConfig);
+                copyConfigurationProperties(baseConfig, newConfig);
+
+                clearConfiguration(newConfig);
+                getProcessedConfiguration(newConfig, mergeConfig);
+                copyConfigurationProperties(baseConfig, newConfig);
+            }
+
+        // another simple case, both are not raw
+        } else if ( !baseIsRaw && !mergeIsRaw ) {
+            // merge mode is always overwrite
+            clearConfiguration(baseConfig);
+            copyConfigurationProperties(baseConfig, mergeConfig);
+
+        // base is not raw but merge is
+        } else if ( !baseIsRaw && mergeIsRaw ) {
+            final String cfgMode = (String)mergeConfig.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE);
+            if ( cfgMode == null || ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode) ) {
+                clearConfiguration(baseConfig);
+                copyConfigurationProperties(baseConfig, mergeConfig);
+            } else {
+                final Configuration newMergeConfig = new Configuration(mergeConfig.getPid(), mergeConfig.getFactoryPid());
+                getProcessedConfiguration(newMergeConfig, mergeConfig);
+                copyConfigurationProperties(baseConfig, newMergeConfig);
+            }
+
+            // base is raw, but merge is not raw
+        } else {
+            // merge mode is always overwrite
+            clearConfiguration(baseConfig);
+            copyConfigurationProperties(baseConfig, mergeConfig);
+        }
+    }
+
+    private static void clearConfiguration(final Configuration cfg) {
+        final Set<String> keys = new HashSet<String>();
+        final Enumeration<String> e = cfg.getProperties().keys();
+        while ( e.hasMoreElements() ) {
+            keys.add(e.nextElement());
+        }
+
+        for(final String key : keys) {
+            cfg.getProperties().remove(key);
+        }
+    }
+
+    private static void copyConfigurationProperties(final Configuration baseConfig, final Configuration mergeConfig) {
+        final Enumeration<String> e = mergeConfig.getProperties().keys();
+        while ( e.hasMoreElements() ) {
+            final String key = e.nextElement();
+            if ( !key.equals(ModelConstants.CFG_UNPROCESSED_MODE) ) {
+                baseConfig.getProperties().put(key, mergeConfig.getProperties().get(key));
+            }
+        }
+    }
+
+    /**
      * Optional variable resolver
      */
     public interface VariableResolver {
@@ -204,80 +276,8 @@
                 newRunMode.getConfigurations().setLocation(runMode.getConfigurations().getLocation());
                 for(final Configuration config : runMode.getConfigurations()) {
                     final Configuration newConfig = newRunMode.getOrCreateConfiguration(config.getPid(), config.getFactoryPid());
-                    newConfig.setComment(config.getComment());
-                    newConfig.setLocation(config.getLocation());
 
-                    // check for raw configuration
-                    final String rawConfig = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED);
-                    if ( rawConfig != null ) {
-                        if ( config.isSpecial() ) {
-                            newConfig.getProperties().put(config.getPid(), rawConfig);
-                        } else {
-                            final String format = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED_FORMAT);
-
-                            if ( ModelConstants.CFG_FORMAT_PROPERTIES.equals(format) ) {
-                                // properties
-                                final Properties props = new Properties();
-                                try {
-                                    props.load(new StringReader(rawConfig));
-                                } catch ( final IOException ioe) {
-                                    throw new IllegalArgumentException("Unable to read configuration properties.", ioe);
-                                }
-                                final Enumeration<Object> i = props.keys();
-                                while ( i.hasMoreElements() ) {
-                                    final String key = (String)i.nextElement();
-                                    newConfig.getProperties().put(key, props.get(key));
-                                }
-                            } else {
-                                // Apache Felix CA format
-                                // the raw format might have comments, we have to remove them first
-                                final StringBuilder sb = new StringBuilder();
-                                try {
-                                    final LineNumberReader lnr = new LineNumberReader(new StringReader(rawConfig));
-                                    String line = null;
-                                    while ((line = lnr.readLine()) != null ) {
-                                        line = line.trim();
-                                        if ( line.isEmpty() || line.startsWith("#")) {
-                                            continue;
-                                        }
-                                        sb.append(line);
-                                        sb.append('\n');
-                                    }
-                                } catch ( final IOException ioe) {
-                                    throw new IllegalArgumentException("Unable to read configuration properties: " + config, ioe);
-                                }
-
-                                ByteArrayInputStream bais = null;
-                                try {
-                                    bais = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
-                                    @SuppressWarnings("unchecked")
-                                    final Dictionary<String, Object> props = ConfigurationHandler.read(bais);
-                                    final Enumeration<String> i = props.keys();
-                                    while ( i.hasMoreElements() ) {
-                                        final String key = i.nextElement();
-                                        newConfig.getProperties().put(key, props.get(key));
-                                    }
-                                } catch ( final IOException ioe) {
-                                    throw new IllegalArgumentException("Unable to read configuration properties: " + config, ioe);
-                                } finally {
-                                    if ( bais != null ) {
-                                        try {
-                                            bais.close();
-                                        } catch ( final IOException ignore ) {
-                                            // ignore
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    } else {
-                        // simply copy
-                        final Enumeration<String> i = config.getProperties().keys();
-                        while ( i.hasMoreElements() ) {
-                            final String key = i.nextElement();
-                            newConfig.getProperties().put(key, config.getProperties().get(key));
-                        }
-                    }
+                    getProcessedConfiguration(newConfig, config);
                 }
 
                 newRunMode.getSettings().setComment(runMode.getSettings().getComment());
@@ -421,4 +421,81 @@
         }
         return errors;
     }
+
+    private static void getProcessedConfiguration(final Configuration newConfig, final Configuration config) {
+        newConfig.setComment(config.getComment());
+        newConfig.setLocation(config.getLocation());
+
+        // check for raw configuration
+        final String rawConfig = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED);
+        if ( rawConfig != null ) {
+            if ( config.isSpecial() ) {
+                newConfig.getProperties().put(config.getPid(), rawConfig);
+            } else {
+                final String format = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED_FORMAT);
+
+                if ( ModelConstants.CFG_FORMAT_PROPERTIES.equals(format) ) {
+                    // properties
+                    final Properties props = new Properties();
+                    try {
+                        props.load(new StringReader(rawConfig));
+                    } catch ( final IOException ioe) {
+                        throw new IllegalArgumentException("Unable to read configuration properties.", ioe);
+                    }
+                    final Enumeration<Object> i = props.keys();
+                    while ( i.hasMoreElements() ) {
+                        final String key = (String)i.nextElement();
+                        newConfig.getProperties().put(key, props.get(key));
+                    }
+                } else {
+                    // Apache Felix CA format
+                    // the raw format might have comments, we have to remove them first
+                    final StringBuilder sb = new StringBuilder();
+                    try {
+                        final LineNumberReader lnr = new LineNumberReader(new StringReader(rawConfig));
+                        String line = null;
+                        while ((line = lnr.readLine()) != null ) {
+                            line = line.trim();
+                            if ( line.isEmpty() || line.startsWith("#")) {
+                                continue;
+                            }
+                            sb.append(line);
+                            sb.append('\n');
+                        }
+                    } catch ( final IOException ioe) {
+                        throw new IllegalArgumentException("Unable to read configuration properties: " + config, ioe);
+                    }
+
+                    ByteArrayInputStream bais = null;
+                    try {
+                        bais = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
+                        @SuppressWarnings("unchecked")
+                        final Dictionary<String, Object> props = ConfigurationHandler.read(bais);
+                        final Enumeration<String> i = props.keys();
+                        while ( i.hasMoreElements() ) {
+                            final String key = i.nextElement();
+                            newConfig.getProperties().put(key, props.get(key));
+                        }
+                    } catch ( final IOException ioe) {
+                        throw new IllegalArgumentException("Unable to read configuration properties: " + config, ioe);
+                    } finally {
+                        if ( bais != null ) {
+                            try {
+                                bais.close();
+                            } catch ( final IOException ignore ) {
+                                // ignore
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            // simply copy
+            final Enumeration<String> i = config.getProperties().keys();
+            while ( i.hasMoreElements() ) {
+                final String key = i.nextElement();
+                newConfig.getProperties().put(key, config.getProperties().get(key));
+            }
+        }
+    }
 }
diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
index 9419880..4d1f9b2 100644
--- a/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
+++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
@@ -243,7 +243,7 @@
                                               final int startPos = line.indexOf("[");
                                               if ( startPos != -1 ) {
                                                   configId = line.substring(0, startPos).trim();
-                                                  cfgPars = parseParameters(line.substring(startPos + 1, line.length() - 1).trim(), new String[] {"format"});
+                                                  cfgPars = parseParameters(line.substring(startPos + 1, line.length() - 1).trim(), new String[] {"format", "mode"});
                                               }
                                           }
                                           String format = cfgPars.get("format");
@@ -255,6 +255,15 @@
                                           } else {
                                               format = ModelConstants.CFG_FORMAT_FELIX_CA;
                                           }
+                                          String cfgMode= cfgPars.get("mode");
+                                          if ( cfgMode != null ) {
+                                              if ( !ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode)
+                                                      && !ModelConstants.CFG_MODE_MERGE.equals(cfgMode) ) {
+                                                      throw new IOException(exceptionPrefix + "Unknown mode configuration parameter in line " + this.lineNumberReader.getLineNumber() + ": " + line);
+                                                  }
+                                          } else {
+                                              cfgMode = ModelConstants.CFG_MODE_OVERWRITE;
+                                          }
                                           final String pid;
                                           final String factoryPid;
                                           final int factoryPos = configId.indexOf('-');
@@ -271,6 +280,7 @@
                                           config = runMode.getOrCreateConfiguration(pid, factoryPid);
                                           this.init(config);
                                           config.getProperties().put(ModelConstants.CFG_UNPROCESSED_FORMAT, format);
+                                          config.getProperties().put(ModelConstants.CFG_UNPROCESSED_MODE, cfgMode);
                                           configBuilder = new StringBuilder();
                                           mode = CATEGORY.CONFIG;
                                           break;
diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
index 470e5fa..8baf9a6 100644
--- a/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
+++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
@@ -175,15 +175,31 @@
                         if ( format == null ) {
                             format = ModelConstants.CFG_FORMAT_FELIX_CA;
                         }
+                        String cfgMode = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED_MODE);
+                        if ( cfgMode == null ) {
+                            cfgMode = ModelConstants.CFG_MODE_OVERWRITE;
+                        }
                         pw.print("  ");
                         if ( config.getFactoryPid() != null ) {
                             pw.print(config.getFactoryPid());
                             pw.print("-");
                         }
                         pw.print(config.getPid());
-                        if ( !ModelConstants.CFG_FORMAT_FELIX_CA.equals(format) ) {
-                            pw.print(" [format=");
-                            pw.print(format);
+                        final boolean isDefaultFormat = ModelConstants.CFG_FORMAT_FELIX_CA.equals(format);
+                        final boolean isDefaultMode = ModelConstants.CFG_MODE_OVERWRITE.equals(cfgMode);
+                        if ( !isDefaultFormat || !isDefaultMode ) {
+                            pw.print(" [");
+                            if ( !isDefaultFormat ) {
+                                pw.print("format=");
+                                pw.print(format);
+                                if ( !isDefaultMode ) {
+                                    pw.print(",");
+                                }
+                            }
+                            if ( !isDefaultMode) {
+                                pw.print("mode=");
+                                pw.print(cfgMode);
+                            }
                             pw.print("]");
                         }
                         pw.println();
diff --git a/src/main/java/org/apache/sling/provisioning/model/io/package-info.java b/src/main/java/org/apache/sling/provisioning/model/io/package-info.java
index 31f116d..7db48c0 100644
--- a/src/main/java/org/apache/sling/provisioning/model/io/package-info.java
+++ b/src/main/java/org/apache/sling/provisioning/model/io/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0")
+@Version("1.1")
 package org.apache.sling.provisioning.model.io;
 
 import aQute.bnd.annotation.Version;
diff --git a/src/test/java/org/apache/sling/provisioning/model/ModelUtilityTest.java b/src/test/java/org/apache/sling/provisioning/model/ModelUtilityTest.java
index e066712..bfa682f 100644
--- a/src/test/java/org/apache/sling/provisioning/model/ModelUtilityTest.java
+++ b/src/test/java/org/apache/sling/provisioning/model/ModelUtilityTest.java
@@ -16,8 +16,10 @@
  */
 package org.apache.sling.provisioning.model;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 import java.util.List;
 
@@ -68,4 +70,127 @@
         assertEquals("org.sling.service.runmode.A", cfgs2.get(0).getPid());
         assertEquals("org.sling.service.runmode.C", cfgs2.get(1).getPid());
     }
+
+    @Test public void mergeRawTest() throws Exception {
+        final Model baseRaw = U.readCompleteTestModel(new String[] {"merge/config-base.txt"});
+        final Model mergeRaw = U.readCompleteTestModel(new String[] {"merge/config-merge.txt"});
+
+        ModelUtility.merge(baseRaw, mergeRaw);
+
+        final List<Configuration> cfgs = U.assertConfigurationsInRunMode(baseRaw.getFeature("configadmin").getRunMode(), 3);
+
+        final Configuration cfgA = cfgs.get(0);
+        assertEquals("org.apache.test.A", cfgA.getPid());
+        assertNull(cfgA.getFactoryPid());
+        assertEquals(1, cfgA.getProperties().size());
+        assertEquals("AA", cfgA.getProperties().get("name"));
+
+        final Configuration cfgB = cfgs.get(1);
+        assertEquals("org.apache.test.B", cfgB.getPid());
+        assertNull(cfgB.getFactoryPid());
+        assertEquals(3, cfgB.getProperties().size());
+        assertEquals("BB", cfgB.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+        assertArrayEquals(new String[] {"one", "two", "three"}, (String[])cfgB.getProperties().get("array"));
+
+        final Configuration cfgC = cfgs.get(2);
+        assertEquals("org.apache.test.C", cfgC.getPid());
+        assertNull(cfgC.getFactoryPid());
+        assertEquals(3, cfgC.getProperties().size());
+        assertEquals("C", cfgC.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+        assertArrayEquals(new Integer[] {1,2,3}, (Integer[])cfgC.getProperties().get("array"));
+    }
+
+    @Test public void mergeEffectiveTest() throws Exception {
+        final Model baseRaw = U.readCompleteTestModel(new String[] {"merge/config-base.txt"});
+        final Model mergeRaw = U.readCompleteTestModel(new String[] {"merge/config-merge.txt"});
+
+        final Model baseEffective = ModelUtility.getEffectiveModel(baseRaw, null);
+        final Model mergeEffective = ModelUtility.getEffectiveModel(mergeRaw, null);
+
+        ModelUtility.merge(baseEffective, mergeEffective);
+
+        final List<Configuration> cfgs = U.assertConfigurationsInRunMode(baseEffective.getFeature("configadmin").getRunMode(), 3);
+
+        final Configuration cfgA = cfgs.get(0);
+        assertEquals("org.apache.test.A", cfgA.getPid());
+        assertNull(cfgA.getFactoryPid());
+        assertEquals(1, cfgA.getProperties().size());
+        assertEquals("AA", cfgA.getProperties().get("name"));
+
+        final Configuration cfgB = cfgs.get(1);
+        assertEquals("org.apache.test.B", cfgB.getPid());
+        assertNull(cfgB.getFactoryPid());
+        assertEquals(2, cfgB.getProperties().size());
+        assertEquals("BB", cfgB.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+
+        final Configuration cfgC = cfgs.get(2);
+        assertEquals("org.apache.test.C", cfgC.getPid());
+        assertNull(cfgC.getFactoryPid());
+        assertEquals(1, cfgC.getProperties().size());
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+    }
+
+    @Test public void mergeBaseRawTest() throws Exception {
+        final Model baseRaw = U.readCompleteTestModel(new String[] {"merge/config-base.txt"});
+        final Model mergeRaw = U.readCompleteTestModel(new String[] {"merge/config-merge.txt"});
+        final Model mergeEffective = ModelUtility.getEffectiveModel(mergeRaw, null);
+
+        ModelUtility.merge(baseRaw, mergeEffective);
+
+        final List<Configuration> cfgs = U.assertConfigurationsInRunMode(baseRaw.getFeature("configadmin").getRunMode(), 3);
+
+        final Configuration cfgA = cfgs.get(0);
+        assertEquals("org.apache.test.A", cfgA.getPid());
+        assertNull(cfgA.getFactoryPid());
+        assertEquals(1, cfgA.getProperties().size());
+        assertEquals("AA", cfgA.getProperties().get("name"));
+
+        final Configuration cfgB = cfgs.get(1);
+        assertEquals("org.apache.test.B", cfgB.getPid());
+        assertNull(cfgB.getFactoryPid());
+        assertEquals(2, cfgB.getProperties().size());
+        assertEquals("BB", cfgB.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+
+        final Configuration cfgC = cfgs.get(2);
+        assertEquals("org.apache.test.C", cfgC.getPid());
+        assertNull(cfgC.getFactoryPid());
+        assertEquals(1, cfgC.getProperties().size());
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+    }
+
+    @Test public void mergeBaseEffectiveTest() throws Exception {
+        final Model baseRaw = U.readCompleteTestModel(new String[] {"merge/config-base.txt"});
+        final Model mergeRaw = U.readCompleteTestModel(new String[] {"merge/config-merge.txt"});
+        final Model baseEffective = ModelUtility.getEffectiveModel(baseRaw, null);
+
+        ModelUtility.merge(baseEffective, mergeRaw);
+
+        final List<Configuration> cfgs = U.assertConfigurationsInRunMode(baseEffective.getFeature("configadmin").getRunMode(), 3);
+
+        final Configuration cfgA = cfgs.get(0);
+        assertEquals("org.apache.test.A", cfgA.getPid());
+        assertNull(cfgA.getFactoryPid());
+        assertEquals(1, cfgA.getProperties().size());
+        assertEquals("AA", cfgA.getProperties().get("name"));
+
+        final Configuration cfgB = cfgs.get(1);
+        assertEquals("org.apache.test.B", cfgB.getPid());
+        assertNull(cfgB.getFactoryPid());
+        assertEquals(3, cfgB.getProperties().size());
+        assertEquals("BB", cfgB.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+        assertArrayEquals(new String[] {"one", "two", "three"}, (String[])cfgB.getProperties().get("array"));
+
+        final Configuration cfgC = cfgs.get(2);
+        assertEquals("org.apache.test.C", cfgC.getPid());
+        assertNull(cfgC.getFactoryPid());
+        assertEquals(3, cfgC.getProperties().size());
+        assertEquals("C", cfgC.getProperties().get("name"));
+        assertEquals("bar", cfgB.getProperties().get("foo"));
+        assertArrayEquals(new Integer[] {1,2,3}, (Integer[])cfgC.getProperties().get("array"));
+    }
 }
diff --git a/src/test/java/org/apache/sling/provisioning/model/U.java b/src/test/java/org/apache/sling/provisioning/model/U.java
index 65576de..a7924ad 100644
--- a/src/test/java/org/apache/sling/provisioning/model/U.java
+++ b/src/test/java/org/apache/sling/provisioning/model/U.java
@@ -52,7 +52,7 @@
 
     /** Read the complete model from that names */
     public static Model readCompleteTestModel(final String[] names) throws Exception {
-        final Model result = new Model();
+        Model result = null;
 
         for(final String name : names) {
             final Reader reader = new InputStreamReader(U.class.getResourceAsStream("/" + name), "UTF-8");
@@ -62,7 +62,11 @@
                 if (errors != null ) {
                     throw new Exception("Invalid model at " + name + " : " + errors);
                 }
-                ModelUtility.merge(result, current);
+                if ( result == null ) {
+                    result = current;
+                } else {
+                    ModelUtility.merge(result, current);
+                }
             } finally {
                 reader.close();
             }
diff --git a/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java b/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
index 9abf82d..e271a00 100644
--- a/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
+++ b/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
@@ -43,7 +43,7 @@
 
         U.verifyTestModel(result, false);
 
-        // Write the merged model
+        // Write the merged raw model
         StringWriter writer = new StringWriter();
         try {
             ModelWriter.write(writer, result);
@@ -66,6 +66,21 @@
         // Resolve variables and verify the result
         final Model effective = ModelUtility.getEffectiveModel(readModel, null);
         U.verifyTestModel(effective, true);
+
+        // write effective model
+        writer = new StringWriter();
+        ModelWriter.write(writer, effective);
+        writer.close();
+
+        reader = new StringReader(writer.toString());
+        final Model readModel2 = ModelReader.read(reader, "memory");
+        reader.close();
+        final Map<Traceable, String> readErrors2 = ModelUtility.validate(readModel2);
+        if (readErrors2 != null ) {
+            throw new Exception("Invalid read model : " + readErrors2);
+        }
+        // and verify the result
+        U.verifyTestModel(readModel2, true);
     }
 
     @Test public void testMultilineConfiguration() throws Exception {
diff --git a/src/test/resources/merge/config-base.txt b/src/test/resources/merge/config-base.txt
new file mode 100644
index 0000000..e5c0451
--- /dev/null
+++ b/src/test/resources/merge/config-base.txt
@@ -0,0 +1,44 @@
+#
+#  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.
+#
+# This is a feature description
+#
+# A feature consists of variables and run mode dependent artifacts.
+#
+[feature name=configadmin]
+
+[configurations]
+org.apache.test.A
+    name="A"    
+
+org.apache.test.B
+    array=[
+      "one",
+      "two",
+      "three"
+    ]
+    name="B"    
+
+org.apache.test.C
+    array=I[
+      "1",
+      "2",
+      "3"
+    ]
+    name="C"    
+    
\ No newline at end of file
diff --git a/src/test/resources/merge/config-merge.txt b/src/test/resources/merge/config-merge.txt
new file mode 100644
index 0000000..b640577
--- /dev/null
+++ b/src/test/resources/merge/config-merge.txt
@@ -0,0 +1,35 @@
+#
+#  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.
+#
+# This is a feature description
+#
+# A feature consists of variables and run mode dependent artifacts.
+#
+[feature name=configadmin]
+
+[configurations]
+org.apache.test.A [mode=merge]
+    name="AA"    
+
+org.apache.test.B [mode=merge]
+    name="BB"
+    foo="bar"    
+
+org.apache.test.C [mode=merge]
+    foo="bar"    
+    
\ No newline at end of file