SLING-11839 Support writing nested configurations and configuration collections
diff --git a/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationDataParts.java b/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationDataParts.java
new file mode 100644
index 0000000..080492d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationDataParts.java
@@ -0,0 +1,63 @@
+/*
+ * 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.sling.testing.mock.caconfig;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Splits a list of key/value pairs which may contain nested configuration and nested configuration lists in it's parts.
+ */
+class ConfigurationDataParts {
+
+    private final Map<String,Object> values = new TreeMap<>();
+    private final Map<String,Map<String,Object>> maps = new TreeMap<>();
+    private final Map<String,Collection<Map<String,Object>>> collections = new TreeMap<>();
+
+    @SuppressWarnings("unchecked")
+    ConfigurationDataParts(Map<String,Object> input) {
+        for (Map.Entry<String, Object> entry : input.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof Map) {
+                maps.put(key, (Map)value);
+            }
+            else if (value instanceof Collection) {
+                collections.put(key, (Collection)value);
+            }
+            else {
+                values.put(key, value);
+            }
+        }
+    }
+
+    Map<String, Object> getValues() {
+        return values;
+    }
+
+    Map<String, Map<String, Object>> getMaps() {
+        return maps;
+    }
+
+    Map<String, Collection<Map<String, Object>>> getCollections() {
+        return collections;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationPersistHelper.java b/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationPersistHelper.java
new file mode 100644
index 0000000..db39882
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/caconfig/ConfigurationPersistHelper.java
@@ -0,0 +1,128 @@
+/*
+ * 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.sling.testing.mock.caconfig;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.caconfig.management.ConfigurationManager;
+import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer;
+import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData;
+import org.apache.sling.caconfig.spi.ConfigurationPersistData;
+import org.apache.sling.testing.mock.sling.context.SlingContextImpl;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Writes context-aware configuration contains in maps and nested maps via {@link ConfigurationManager} to repository.
+ */
+class ConfigurationPersistHelper {
+
+    private final ConfigurationManager configManager;
+    private final ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy;
+    private final Resource contextResource;
+
+    /**
+     * @param context Sling context
+     * @param contextPath Context path
+     */
+    ConfigurationPersistHelper(@NotNull SlingContextImpl context, @NotNull String contextPath) {
+        configManager = context.getService(ConfigurationManager.class);
+        configurationPersistenceStrategy = context.getService(ConfigurationPersistenceStrategyMultiplexer.class);
+        contextResource = context.resourceResolver().getResource(contextPath);
+        if (contextResource == null) {
+            throw new IllegalArgumentException("No resource found at" + contextPath);
+        }
+    }
+
+    /**
+     * Writes configuration parameters using the primary configured persistence
+     * provider.
+     * @param configName Config name
+     * @param values Configuration values
+     */
+    void writeConfiguration(@NotNull String configName, @NotNull Map<String, Object> values) {
+        // write properties of main configuration
+        ConfigurationDataParts parts = new ConfigurationDataParts(values);
+        configManager.persistConfiguration(contextResource, configName, new ConfigurationPersistData(parts.getValues()));
+
+        // write nested configuration and nested configuration collections
+        for (Map.Entry<String,Map<String,Object>> nestedMap : parts.getMaps().entrySet()) {
+            writeConfiguration(getNestedConfigName(configName, nestedMap.getKey()), nestedMap.getValue());
+        }
+        for (Map.Entry<String,Collection<Map<String,Object>>> nestedCollection : parts.getCollections().entrySet()) {
+            writeConfigurationCollection(getNestedConfigName(configName, nestedCollection.getKey()), nestedCollection.getValue());
+        }
+    }
+
+    /**
+     * Writes a collection of configuration parameters using the primary configured persistence provider.
+     * @param configName Config name
+     * @param values Configuration values
+     */
+    void writeConfigurationCollection(@NotNull String configName, @NotNull Collection<@NotNull Map<String, Object>> values) {
+        // split each collection item map in it's parts
+        Map<String, ConfigurationDataParts> partsCollection = new HashMap<>();
+        int index = 0;
+        for (Map<String, Object> map : values) {
+            partsCollection.put("item" + (index++), new ConfigurationDataParts(map));
+        }
+
+        // write properties of main configuration collection
+        List<ConfigurationPersistData> items = partsCollection.entrySet().stream()
+                .map(entry -> new ConfigurationPersistData(entry.getValue().getValues()).collectionItemName(entry.getKey()))
+                .collect(Collectors.toList());
+        configManager.persistConfigurationCollection(contextResource, configName,
+                new ConfigurationCollectionPersistData(items));
+
+        // write nested configuration and nested configuration collections
+        for (Map.Entry<String, ConfigurationDataParts> entry : partsCollection.entrySet()) {
+            String itemName = entry.getKey();
+            ConfigurationDataParts parts = entry.getValue();
+            for (Map.Entry<String,Map<String,Object>> nestedMap : parts.getMaps().entrySet()) {
+                writeConfiguration(getNestedCollectionItemConfigName(configName, itemName, nestedMap.getKey()), nestedMap.getValue());
+            }
+            for (Map.Entry<String,Collection<Map<String,Object>>> nestedCollection : parts.getCollections().entrySet()) {
+                writeConfigurationCollection(getNestedCollectionItemConfigName(configName, itemName, nestedCollection.getKey()), nestedCollection.getValue());
+            }
+        }
+    }
+
+    private String getNestedConfigName(@NotNull String configName, @NotNull String key) {
+        String nestedKey = configName + "/" + key;
+        String nestedConfigName = configurationPersistenceStrategy.getConfigName(nestedKey, null);
+        if (nestedConfigName == null) {
+            throw new IllegalArgumentException("Nested configuration not supported for " + nestedKey);
+        }
+        return nestedConfigName;
+    }
+
+    private String getNestedCollectionItemConfigName(@NotNull String configName, @NotNull String itemName, @NotNull String key) {
+        String nestedKey = configName + "/" + itemName + "/" + key;
+        String nestedConfigName = configurationPersistenceStrategy.getCollectionItemConfigName(nestedKey, null);
+        if (nestedConfigName == null) {
+            throw new IllegalArgumentException("Nested configuration not supported for " + nestedKey);
+        }
+        return nestedConfigName;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfig.java b/src/main/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfig.java
index 7bde85b..7ffaf71 100644
--- a/src/main/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfig.java
+++ b/src/main/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfig.java
@@ -18,17 +18,11 @@
  */
 package org.apache.sling.testing.mock.caconfig;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.resource.Resource;
 import org.apache.sling.caconfig.annotation.Configuration;
-import org.apache.sling.caconfig.management.ConfigurationManager;
-import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData;
-import org.apache.sling.caconfig.spi.ConfigurationPersistData;
 import org.apache.sling.testing.mock.osgi.MapUtil;
 import org.apache.sling.testing.mock.sling.context.SlingContextImpl;
 import org.jetbrains.annotations.NotNull;
@@ -76,7 +70,7 @@
      * Writes configuration parameters using the primary configured persistence
      * provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configClass Configuration class
      * @param values Configuration values
      */
@@ -89,23 +83,21 @@
      * Writes configuration parameters using the primary configured persistence
      * provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configName Config name
      * @param values Configuration values
      */
-    @SuppressWarnings("null")
     public static void writeConfiguration(@NotNull SlingContextImpl context, @NotNull String contextPath, @NotNull String configName,
             @NotNull Map<String, Object> values) {
-        ConfigurationManager configManager = context.getService(ConfigurationManager.class);
-        Resource contextResource = context.resourceResolver().getResource(contextPath);
-        configManager.persistConfiguration(contextResource, configName, new ConfigurationPersistData(values));
+        ConfigurationPersistHelper helper = new ConfigurationPersistHelper(context, contextPath);
+        helper.writeConfiguration(configName, values);
     }
 
     /**
      * Writes configuration parameters using the primary configured persistence
      * provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configClass Configuration class
      * @param values Configuration values
      */
@@ -117,7 +109,7 @@
      * Writes configuration parameters using the primary configured persistence
      * provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configName Config name
      * @param values Configuration values
      */
@@ -129,7 +121,7 @@
      * Writes a collection of configuration parameters using the primary
      * configured persistence provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configClass Configuration class
      * @param values Configuration values
      */
@@ -142,25 +134,14 @@
      * Writes a collection of configuration parameters using the primary
      * configured persistence provider.
      * @param context Sling context
-     * @param contextPath Configuration id
+     * @param contextPath Context path
      * @param configName Config name
      * @param values Configuration values
      */
-    @SuppressWarnings("null")
     public static void writeConfigurationCollection(@NotNull SlingContextImpl context, @NotNull String contextPath, @NotNull String configName,
             @NotNull Collection<@NotNull Map<String, Object>> values) {
-        ConfigurationManager configManager = context.getService(ConfigurationManager.class);
-        Resource contextResource = context.resourceResolver().getResource(contextPath);
-        if (contextResource == null) {
-            throw new IllegalArgumentException("No resource found at" + contextPath);
-        }
-        List<ConfigurationPersistData> items = new ArrayList<>();
-        int index = 0;
-        for (Map<String, Object> map : values) {
-            items.add(new ConfigurationPersistData(map).collectionItemName("item" + (index++)));
-        }
-        configManager.persistConfigurationCollection(contextResource, configName,
-                new ConfigurationCollectionPersistData(items));
+        ConfigurationPersistHelper helper = new ConfigurationPersistHelper(context, contextPath);
+        helper.writeConfigurationCollection(configName, values);
     }
 
     @SuppressWarnings("null")
diff --git a/src/test/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfigTest.java b/src/test/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfigTest.java
index 412c04a..0e448e5 100644
--- a/src/test/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfigTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/caconfig/MockContextAwareConfigTest.java
@@ -19,6 +19,7 @@
 package org.apache.sling.testing.mock.caconfig;
 
 import static org.apache.sling.testing.mock.caconfig.ContextPlugins.CACONFIG;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -28,9 +29,16 @@
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.caconfig.ConfigurationBuilder;
 import org.apache.sling.testing.mock.caconfig.example.ListConfig;
+import org.apache.sling.testing.mock.caconfig.example.NestedConfig;
+import org.apache.sling.testing.mock.caconfig.example.NestedConfigSub;
+import org.apache.sling.testing.mock.caconfig.example.NestedConfigSub2;
+import org.apache.sling.testing.mock.caconfig.example.NestedListConfig;
 import org.apache.sling.testing.mock.caconfig.example.SimpleConfig;
+import org.apache.sling.testing.mock.sling.builder.ImmutableValueMap;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -38,7 +46,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
-@SuppressWarnings("null")
 public class MockContextAwareConfigTest {
 
     @Rule
@@ -53,28 +60,25 @@
         context.create().resource("/content/region/site", "sling:configRef", "/conf/region/site");
 
         context.currentResource(context.create().resource("/content/region/site/en"));
-
-        MockContextAwareConfig.writeConfiguration(context, "/content/region/site", SimpleConfig.class,
-                "stringParam", "value1");
-
-        MockContextAwareConfig.writeConfigurationCollection(context, "/content/region/site", ListConfig.class, ImmutableList.of(
-                ImmutableMap.<String,Object>of("stringParam", "value1"),
-                ImmutableMap.<String,Object>of("stringParam", "value2")));
     }
 
     @Test
     public void testSingletonConfig() {
-        Resource resource = context.request().getResource();
-        SimpleConfig config = resource.adaptTo(ConfigurationBuilder.class).as(SimpleConfig.class);
-        assertNotNull(config);
+        MockContextAwareConfig.writeConfiguration(context, "/content/region/site", SimpleConfig.class,
+                "stringParam", "value1");
+
+        SimpleConfig config = getConfig(SimpleConfig.class);
         assertEquals("value1", config.stringParam());
         assertEquals(5, config.intParam());
     }
 
     @Test
-    public void testConfigCollection() {
-        Resource resource = context.request().getResource();
-        Collection<ListConfig> config = resource.adaptTo(ConfigurationBuilder.class).asCollection(ListConfig.class);
+    public void testCollectionConfig() {
+        MockContextAwareConfig.writeConfigurationCollection(context, "/content/region/site", ListConfig.class, ImmutableList.of(
+                ImmutableMap.<String,Object>of("stringParam", "value1"),
+                ImmutableMap.<String,Object>of("stringParam", "value2")));
+
+        Collection<ListConfig> config = getConfigCollection(ListConfig.class);
         assertEquals(2, config.size());
         Iterator<ListConfig> items = config.iterator();
 
@@ -87,4 +91,102 @@
         assertEquals(5, item2.intParam());
     }
 
+    @Test
+    public void testNestedSingletonConfig() {
+        MockContextAwareConfig.writeConfiguration(context, "/content/region/site", NestedConfig.class,
+                "stringParam", "value1",
+                "sub", ImmutableList.of(
+                        ImmutableValueMap.of("subStringParam", "v1", "intParam", 5, "stringArrayParam", new String[] {"v1a","v1b"}),
+                        ImmutableValueMap.of("subStringParam", "v2")),
+                "sub2", ImmutableValueMap.of(
+                        "sub2StringParam", "v3",
+                        "sub", ImmutableValueMap.of("subStringParam", "v4"),
+                        "subList", ImmutableList.of(ImmutableValueMap.of("subStringParam", "v5a"),ImmutableValueMap.of("subStringParam", "v5b"))),
+                "sub2List", ImmutableList.of(
+                        ImmutableValueMap.of("sub2StringParam", "v6")));
+
+        NestedConfig config = getConfig(NestedConfig.class);
+        assertEquals("value1", config.stringParam());
+
+        NestedConfigSub[] sub = config.sub();
+        assertEquals(2, sub.length);
+        assertEquals("v1", sub[0].subStringParam());
+        assertEquals(5, sub[0].intParam());
+        assertArrayEquals(new String[] {"v1a","v1b"}, sub[0].stringArrayParam());
+        assertEquals("v2", sub[1].subStringParam());
+
+        NestedConfigSub2 sub2 = config.sub2();
+        assertEquals("v3", sub2.sub2StringParam());
+        assertEquals("v4", sub2.sub().subStringParam());
+        NestedConfigSub[] sub2_sublist = sub2.subList();
+        assertEquals(2, sub2_sublist.length);
+        assertEquals("v5a", sub2_sublist[0].subStringParam());
+        assertEquals("v5b", sub2_sublist[1].subStringParam());
+
+        NestedConfigSub2[] sub2list = config.sub2List();
+        assertEquals(1, sub2list.length);
+        assertEquals("v6", sub2list[0].sub2StringParam());
+    }
+
+    @Test
+    public void testNestedCollectionConfigConfig() {
+        MockContextAwareConfig.writeConfigurationCollection(context, "/content/region/site", NestedListConfig.class, ImmutableList.of(
+                ImmutableMap.<String,Object>of("stringParam", "value1",
+                        "sub", ImmutableList.of(
+                                ImmutableValueMap.of("subStringParam", "v1", "intParam", 5, "stringArrayParam", new String[] {"v1a","v1b"}),
+                                ImmutableValueMap.of("subStringParam", "v2")),
+                        "sub2", ImmutableValueMap.of(
+                                "sub2StringParam", "v3",
+                                "sub", ImmutableValueMap.of("subStringParam", "v4"),
+                                "subList", ImmutableList.of(ImmutableValueMap.of("subStringParam", "v5a"),ImmutableValueMap.of("subStringParam", "v5b"))),
+                        "sub2List", ImmutableList.of(
+                                ImmutableValueMap.of("sub2StringParam", "v6"))),
+                ImmutableMap.<String,Object>of("stringParam", "value2")));
+
+        Collection<NestedListConfig> config = getConfigCollection(NestedListConfig.class);
+        assertEquals(2, config.size());
+        Iterator<NestedListConfig> items = config.iterator();
+
+        NestedListConfig item1 = items.next();
+        assertEquals("value1", item1.stringParam());
+
+        NestedConfigSub[] sub = item1.sub();
+        assertEquals(2, sub.length);
+        assertEquals("v1", sub[0].subStringParam());
+        assertEquals(5, sub[0].intParam());
+        assertArrayEquals(new String[] {"v1a","v1b"}, sub[0].stringArrayParam());
+        assertEquals("v2", sub[1].subStringParam());
+
+        NestedConfigSub2 sub2 = item1.sub2();
+        assertEquals("v3", sub2.sub2StringParam());
+        assertEquals("v4", sub2.sub().subStringParam());
+        NestedConfigSub[] sub2_sublist = sub2.subList();
+        assertEquals(2, sub2_sublist.length);
+        assertEquals("v5a", sub2_sublist[0].subStringParam());
+        assertEquals("v5b", sub2_sublist[1].subStringParam());
+
+        NestedConfigSub2[] sub2list = item1.sub2List();
+        assertEquals(1, sub2list.length);
+        assertEquals("v6", sub2list[0].sub2StringParam());
+
+        NestedListConfig item2 = items.next();
+        assertEquals("value2", item2.stringParam());
+    }
+
+    @SuppressWarnings("null")
+    private <T> @NotNull T getConfig(@NotNull Class<T> configClass) {
+        Resource resource = context.request().getResource();
+        T result = resource.adaptTo(ConfigurationBuilder.class).as(configClass);
+        assertNotNull(result);
+        return result;
+    }
+
+    @SuppressWarnings("null")
+    private <T> @NotNull Collection<@Nullable T> getConfigCollection(@NotNull Class<T> configClass) {
+        Resource resource = context.request().getResource();
+        Collection<@Nullable T> result = resource.adaptTo(ConfigurationBuilder.class).asCollection(configClass);
+        assertNotNull(result);
+        return result;
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfig.java b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfig.java
new file mode 100644
index 0000000..80fd4aa
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfig.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sling.testing.mock.caconfig.example;
+
+import org.apache.sling.caconfig.annotation.Configuration;
+
+/**
+ * Nested configuration example.
+ */
+@Configuration
+public @interface NestedConfig {
+
+  String stringParam();
+
+  NestedConfigSub[] sub();
+
+  NestedConfigSub2 sub2();
+
+  NestedConfigSub2[] sub2List();
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub.java b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub.java
new file mode 100644
index 0000000..8714c9e
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub.java
@@ -0,0 +1,34 @@
+/*
+ * 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.sling.testing.mock.caconfig.example;
+
+/**
+ * Nested sub config.
+ */
+public @interface NestedConfigSub {
+
+  String subStringParam();
+
+  int intParam();
+
+  String[] stringArrayParam() default {
+      "value1", "value2"
+  };
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub2.java b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub2.java
new file mode 100644
index 0000000..c640056
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedConfigSub2.java
@@ -0,0 +1,32 @@
+/*
+ * 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.sling.testing.mock.caconfig.example;
+
+/**
+ * Nested sub config 2.
+ */
+public @interface NestedConfigSub2 {
+
+  String sub2StringParam();
+
+  NestedConfigSub sub();
+
+  NestedConfigSub[] subList();
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedListConfig.java b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedListConfig.java
new file mode 100644
index 0000000..28e7c27
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/caconfig/example/NestedListConfig.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sling.testing.mock.caconfig.example;
+
+import org.apache.sling.caconfig.annotation.Configuration;
+
+/**
+ * Nested configuration list example.
+ */
+@Configuration(collection = true)
+public @interface NestedListConfig {
+
+  String stringParam();
+
+  NestedConfigSub[] sub();
+
+  NestedConfigSub2 sub2();
+
+  NestedConfigSub2[] sub2List();
+
+}