This is an automated email from the ASF dual-hosted git repository.
sseifert pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-caconfig-mock-plugin.git
The following commit(s) were added to refs/heads/master by this push:
new 8d6b363 SLING-11839 Support writing nested configurations and
configuration collections
8d6b363 is described below
commit 8d6b363600277858541e314a22558043c8eb928f
Author: Stefan Seifert <[email protected]>
AuthorDate: Thu Apr 20 13:59:06 2023 +0200
SLING-11839 Support writing nested configurations and configuration
collections
---
.../mock/caconfig/ConfigurationDataParts.java | 63 ++++++++++
.../mock/caconfig/ConfigurationPersistHelper.java | 128 ++++++++++++++++++++
.../mock/caconfig/MockContextAwareConfig.java | 39 ++-----
.../mock/caconfig/MockContextAwareConfigTest.java | 130 ++++++++++++++++++---
.../mock/caconfig/example/NestedConfig.java | 37 ++++++
.../mock/caconfig/example/NestedConfigSub.java | 34 ++++++
.../mock/caconfig/example/NestedConfigSub2.java | 32 +++++
.../mock/caconfig/example/NestedListConfig.java | 37 ++++++
8 files changed, 457 insertions(+), 43 deletions(-)
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 @@ public final class MockContextAwareConfig {
* 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 @@ public final class MockContextAwareConfig {
* 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 @@ public final class MockContextAwareConfig {
* 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 @@ public final class MockContextAwareConfig {
* 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 @@ public final class MockContextAwareConfig {
* 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 java.util.Iterator;
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 org.junit.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-@SuppressWarnings("null")
public class MockContextAwareConfigTest {
@Rule
@@ -53,28 +60,25 @@ public class MockContextAwareConfigTest {
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 @@ public class MockContextAwareConfigTest {
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();
+
+}