You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ss...@apache.org on 2021/12/22 18:55:02 UTC

[sling-org-apache-sling-models-caconfig] 02/02: SLING-7256 implement injector

This is an automated email from the ASF dual-hosted git repository.

sseifert pushed a commit to branch feature/SLING-7256-caconfig-injector
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-caconfig.git

commit d025ba9b18c205e1dab4bbd55672fe051d76b761
Author: Stefan Seifert <st...@users.noreply.github.com>
AuthorDate: Wed Dec 22 17:07:43 2021 +0100

    SLING-7256 implement injector
---
 pom.xml                                            |  34 ++--
 .../annotations/ContextAwareConfiguration.java     |   9 +
 .../ContextAwareConfigurationInjector.java         | 189 +++++++++++++++++++
 .../ContextAwareConfigurationProcessor.java        |  38 ++++
 .../caconfig/example/caconfig/ListConfig.java      |  32 ++++
 .../caconfig/example/caconfig/SingleConfig.java    |  32 ++++
 .../caconfig/model/ConfigurationValuesModel.java   |  58 ++++++
 .../example/model/InvalidAnnotationListModel.java  |  39 ++++
 .../example/model/InvalidAnnotationModel.java      |  37 ++++
 .../caconfig/example/model/InvalidInjectModel.java |  41 +++++
 .../caconfig/example/model/InvalidSetModel.java    |  40 +++++
 .../example/model/ListConfigAdaptModel.java        |  57 ++++++
 .../caconfig/example/model/ListConfigGetter.java   |  32 ++++
 .../caconfig/example/model/ListConfigModel.java    |  57 ++++++
 .../example/model/ListConfigValueMapModel.java     |  57 ++++++
 .../example/model/SingleConfigAdaptModel.java      |  38 ++++
 .../caconfig/example/model/SingleConfigGetter.java |  25 +++
 .../caconfig/example/model/SingleConfigModel.java  |  39 ++++
 .../example/model/SingleConfigValueMapModel.java   |  38 ++++
 .../ContextAwareConfigurationInjectorTest.java     | 199 +++++++++++++++++++++
 20 files changed, 1078 insertions(+), 13 deletions(-)

diff --git a/pom.xml b/pom.xml
index c790c39..80a2f50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,12 @@
 
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.16.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.models.api</artifactId>
             <version>1.3.8</version>
             <scope>provided</scope>
@@ -79,16 +85,16 @@
             <version>1.3.4</version>
             <scope>provided</scope>
         </dependency>
-
         <dependency>
-            <groupId>org.jetbrains</groupId>
-            <artifactId>annotations</artifactId>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.4</version>
             <scope>provided</scope>
         </dependency>
+
         <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.api</artifactId>
-            <version>2.4.0</version>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -110,7 +116,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.testing.osgi-mock.junit5</artifactId>
+            <artifactId>org.apache.sling.testing.sling-mock.junit5</artifactId>
             <version>3.2.2</version>
             <scope>test</scope>
         </dependency>
@@ -121,14 +127,16 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>4.2.0</version>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.caconfig-mock-plugin</artifactId>
+            <version>1.3.6</version>
+            <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-junit-jupiter</artifactId>
-            <version>4.2.0</version>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3.2</version>
+            <scope>test</scope>
         </dependency>
 
     </dependencies>
diff --git a/src/main/java/org/apache/sling/models/caconfig/annotations/ContextAwareConfiguration.java b/src/main/java/org/apache/sling/models/caconfig/annotations/ContextAwareConfiguration.java
index 6c330c0..0c527b5 100644
--- a/src/main/java/org/apache/sling/models/caconfig/annotations/ContextAwareConfiguration.java
+++ b/src/main/java/org/apache/sling/models/caconfig/annotations/ContextAwareConfiguration.java
@@ -30,6 +30,8 @@ import org.apache.sling.models.spi.injectorspecific.InjectAnnotation;
 
 /**
  * Annotation to be used on either methods, fields or constructor parameters to let Sling Models inject a context-aware configuration.
+ * The annotation supports the same features and semantics as {@link org.apache.sling.caconfig.ConfigurationBuilder}.
+ * For configuration collections, you can use arrays, {@link java.util.List} or {@link java.util.Collection} variables.
  */
 @Target({ METHOD, FIELD, PARAMETER })
 @Retention(RUNTIME)
@@ -38,6 +40,13 @@ import org.apache.sling.models.spi.injectorspecific.InjectAnnotation;
 public @interface ContextAwareConfiguration {
 
     /**
+     * Define configuration name.
+     * Optional if used together with a context-aware configuration annotation class, which implictely defines a configuration name.
+     * @return Configuration name
+     */
+    public String name() default "";
+
+    /**
      * if set to REQUIRED injection is mandatory, if set to OPTIONAL injection is optional, in case of DEFAULT
      * the standard annotations ({@link org.apache.sling.models.annotations.Optional}, {@link org.apache.sling.models.annotations.Required}) are used.
      * If even those are not available the default injection strategy defined on the {@link org.apache.sling.models.annotations.Model} applies.
diff --git a/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjector.java b/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjector.java
new file mode 100644
index 0000000..a475a5f
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjector.java
@@ -0,0 +1,189 @@
+/*
+ * 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.models.caconfig.impl.injectors;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.caconfig.ConfigurationBuilder;
+import org.apache.sling.caconfig.ConfigurationResolveException;
+import org.apache.sling.caconfig.ConfigurationResolver;
+import org.apache.sling.caconfig.annotation.Configuration;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.spi.AcceptsNullName;
+import org.apache.sling.models.spi.DisposalCallbackRegistry;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
+import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.propertytypes.ServiceRanking;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { Injector.class, StaticInjectAnnotationProcessorFactory.class, AcceptsNullName.class })
+@ServiceRanking(6000)
+public class ContextAwareConfigurationInjector implements Injector, StaticInjectAnnotationProcessorFactory, AcceptsNullName {
+
+    private static final Logger log = LoggerFactory.getLogger(ContextAwareConfigurationInjector.class);
+
+    @Reference
+    private ConfigurationResolver configurationResolver;
+
+    @Override
+    public @NotNull String getName() {
+        return "caconfig";
+    }
+
+    @Override
+    public InjectAnnotationProcessor2 createAnnotationProcessor(AnnotatedElement element) {
+        // check if the element has the expected annotation
+        ContextAwareConfiguration annotation = getAnnotation(element);
+        if (annotation != null) {
+            return new ContextAwareConfigurationProcessor(annotation);
+        }
+        return null;
+    }
+
+    @Override
+    public Object getValue(@NotNull Object adaptable, String name,
+            @NotNull Type declaredType, @NotNull AnnotatedElement element,
+            @NotNull DisposalCallbackRegistry callbackRegistry) {
+
+        ContextAwareConfiguration annotation = getAnnotation(element);
+        if (annotation == null) {
+            // support injections only with explicit @ContextAwareConfiguration annotation
+            log.warn("Injection only supported using @ContextAwareConfiguration annotation.");
+            return null;
+        }
+
+        // get resource
+        Resource resource = getResource(adaptable);
+        if (resource == null) {
+            log.warn("Unable to get resource from {}", adaptable);
+            return null;
+        }
+
+        // initialize configuration builder
+        ConfigurationBuilder configurationBuilder = configurationResolver.get(resource);
+        if (StringUtils.isNotBlank(annotation.name())) {
+            configurationBuilder = configurationBuilder.name(annotation.name());
+        }
+
+        // detect from declared type if a single configuration or configuration collection is requested and return the configuration
+        if (declaredType instanceof Class) {
+            Class<?> clazz = (Class<?>)declaredType;
+            if (clazz.isArray()) {
+                Collection<?> result = getConfigurationCollection(configurationBuilder, clazz.getComponentType());
+                Object array = Array.newInstance(clazz.getComponentType(), result.size());
+                Iterator<?> resultIterator = result.iterator();
+                int i = 0;
+                while (resultIterator.hasNext()) {
+                    Array.set(array, i++, resultIterator.next());
+                }
+                return array;
+            }
+            else {
+                return getConfiguration(configurationBuilder, clazz);
+            }
+        }
+        else if (declaredType instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType)declaredType;
+            if (parameterizedType.getActualTypeArguments().length != 1) {
+                return null;
+            }
+            Class<?> collectionType = (Class<?>) parameterizedType.getRawType();
+            if (!(collectionType.equals(Collection.class) || collectionType.equals(List.class))) {
+                return null;
+            }
+            Class<?> clazz = (Class<?>) parameterizedType.getActualTypeArguments()[0];
+            Collection<?> result = getConfigurationCollection(configurationBuilder, clazz);
+            if (collectionType.equals(List.class)) {
+                return new ArrayList<>(result);
+            }
+            else {
+                return result;
+            }
+        }
+        else {
+            log.warn("Cannot handle type {}", declaredType);
+            return null;
+        }
+    }
+
+    private @Nullable ContextAwareConfiguration getAnnotation(AnnotatedElement element) {
+        return element.getAnnotation(ContextAwareConfiguration.class);
+    }
+
+    private @Nullable Resource getResource(@NotNull Object adaptable) {
+        if (adaptable instanceof Resource) {
+            return (Resource)adaptable;
+        }
+        if (adaptable instanceof SlingHttpServletRequest) {
+            SlingHttpServletRequest request = (SlingHttpServletRequest)adaptable;
+            return request.getResource();
+        }
+        return null;
+    }
+
+    private @Nullable Object getConfiguration(@NotNull ConfigurationBuilder configurationBuilder, @NotNull Class<?> clazz) {
+        try {
+            if (clazz.equals(ValueMap.class)) {
+                return configurationBuilder.asValueMap();
+            }
+            if (isContextAwareConfigAnnotationClass(clazz)) {
+                return configurationBuilder.as(clazz);
+            }
+            return configurationBuilder.asAdaptable(clazz);
+        }
+        catch (ConfigurationResolveException ex) {
+            throw new ConfigurationResolveException("Class " + clazz.getName() + ": " + ex.getMessage(), ex);
+        }
+    }
+
+    private @NotNull Collection<?> getConfigurationCollection(@NotNull ConfigurationBuilder configurationBuilder, @NotNull Class<?> clazz) {
+        try {
+            if (clazz.equals(ValueMap.class)) {
+                return configurationBuilder.asValueMapCollection();
+            }
+            if (isContextAwareConfigAnnotationClass(clazz)) {
+                return configurationBuilder.asCollection(clazz);
+            }
+            return configurationBuilder.asAdaptableCollection(clazz);
+        }
+        catch (ConfigurationResolveException ex) {
+            throw new ConfigurationResolveException("Class " + clazz.getName() + ": " + ex.getMessage(), ex);
+        }
+    }
+
+    private boolean isContextAwareConfigAnnotationClass(Class<?> clazz) {
+        return clazz.isAnnotation() && clazz.isAnnotationPresent(Configuration.class);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationProcessor.java b/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationProcessor.java
new file mode 100644
index 0000000..30bcec4
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationProcessor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.models.caconfig.impl.injectors;
+
+import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor2;
+
+class ContextAwareConfigurationProcessor extends AbstractInjectAnnotationProcessor2 {
+
+    private final ContextAwareConfiguration annotation;
+
+    public ContextAwareConfigurationProcessor(ContextAwareConfiguration annotation) {
+        this.annotation = annotation;
+    }
+
+    @Override
+    public InjectionStrategy getInjectionStrategy() {
+        return annotation.injectionStrategy();
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/caconfig/ListConfig.java b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/ListConfig.java
new file mode 100644
index 0000000..104419d
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/ListConfig.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.models.caconfig.example.caconfig;
+
+import org.apache.sling.caconfig.annotation.Configuration;
+
+@Configuration(collection = true)
+public @interface ListConfig {
+
+    String stringParam();
+
+    int intParam() default 5;
+
+    boolean boolParam();
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/caconfig/SingleConfig.java b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/SingleConfig.java
new file mode 100644
index 0000000..61ee707
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/SingleConfig.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.models.caconfig.example.caconfig;
+
+import org.apache.sling.caconfig.annotation.Configuration;
+
+@Configuration(name = "testSingleConfig")
+public @interface SingleConfig {
+
+    String stringParam();
+
+    int intParam() default 5;
+
+    boolean boolParam();
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/caconfig/model/ConfigurationValuesModel.java b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/model/ConfigurationValuesModel.java
new file mode 100644
index 0000000..664463d
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/caconfig/model/ConfigurationValuesModel.java
@@ -0,0 +1,58 @@
+/*
+ * 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.models.caconfig.example.caconfig.model;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.DefaultInjectionStrategy;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
+
+/**
+ * This model can be used to adapt configuration resource to a model.
+ */
+@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
+public class ConfigurationValuesModel {
+
+    @ValueMapValue
+    private String stringParam;
+    @ValueMapValue
+    private int intParam;
+    @ValueMapValue
+    private boolean boolParam;
+
+    public String getStringParam() {
+        return stringParam;
+    }
+    public void setStringParam(String stringParam) {
+        this.stringParam = stringParam;
+    }
+    public int getIntParam() {
+        return intParam;
+    }
+    public void setIntParam(int intParam) {
+        this.intParam = intParam;
+    }
+    public boolean isBoolParam() {
+        return boolParam;
+    }
+    public void setBoolParam(boolean boolParam) {
+        this.boolParam = boolParam;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationListModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationListModel.java
new file mode 100644
index 0000000..71f2571
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationListModel.java
@@ -0,0 +1,39 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class InvalidAnnotationListModel {
+
+    // "Model" is not a valid caconfig annotation
+    @ContextAwareConfiguration
+    private List<Model> configList;
+
+    public List<Model> getConfigList() {
+        return configList;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationModel.java
new file mode 100644
index 0000000..f17ed25
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidAnnotationModel.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.models.caconfig.example.model;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class InvalidAnnotationModel {
+
+    // "Model" is not a valid caconfig annotation
+    @ContextAwareConfiguration
+    private Model config;
+
+    public Model getConfig() {
+        return config;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidInjectModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidInjectModel.java
new file mode 100644
index 0000000..3d98300
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidInjectModel.java
@@ -0,0 +1,41 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Source;
+import org.apache.sling.models.caconfig.example.caconfig.SingleConfig;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class InvalidInjectModel {
+
+    // only injection with explicit annotation @ContextAwareConfiguration is supported
+    @Inject
+    @Source("caconfig")
+    private SingleConfig config;
+
+    public SingleConfig getConfig() {
+        return config;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidSetModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidSetModel.java
new file mode 100644
index 0000000..615cdc3
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/InvalidSetModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import java.util.Set;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.caconfig.example.caconfig.ListConfig;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class InvalidSetModel {
+
+    // Set is not supported as collection type
+    @ContextAwareConfiguration
+    private Set<ListConfig> configList;
+
+    public Set<ListConfig> getConfigList() {
+        return configList;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigAdaptModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigAdaptModel.java
new file mode 100644
index 0000000..59838b2
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigAdaptModel.java
@@ -0,0 +1,57 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.caconfig.example.caconfig.model.ConfigurationValuesModel;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class ListConfigAdaptModel implements ListConfigGetter<ConfigurationValuesModel> {
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private List<ConfigurationValuesModel> configList;
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private Collection<ConfigurationValuesModel> configCollection;
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private ConfigurationValuesModel[] configArray;
+
+    @Override
+    public List<ConfigurationValuesModel> getConfigList() {
+        return configList;
+    }
+
+    @Override
+    public Collection<ConfigurationValuesModel> getConfigCollection() {
+        return configCollection;
+    }
+
+    @Override
+    public ConfigurationValuesModel[] getConfigArray() {
+        return configArray;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigGetter.java b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigGetter.java
new file mode 100644
index 0000000..a552c7a
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigGetter.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.models.caconfig.example.model;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface ListConfigGetter<T> {
+
+    List<T> getConfigList();
+
+    Collection<T> getConfigCollection();
+
+    T[] getConfigArray();
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigModel.java
new file mode 100644
index 0000000..ecc65df
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigModel.java
@@ -0,0 +1,57 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.caconfig.example.caconfig.ListConfig;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class ListConfigModel implements ListConfigGetter<ListConfig> {
+
+    @ContextAwareConfiguration
+    private List<ListConfig> configList;
+
+    @ContextAwareConfiguration
+    private Collection<ListConfig> configCollection;
+
+    @ContextAwareConfiguration
+    private ListConfig[] configArray;
+
+    @Override
+    public List<ListConfig> getConfigList() {
+        return configList;
+    }
+
+    @Override
+    public Collection<ListConfig> getConfigCollection() {
+        return configCollection;
+    }
+
+    @Override
+    public ListConfig[] getConfigArray() {
+        return configArray;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigValueMapModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigValueMapModel.java
new file mode 100644
index 0000000..a83b6c4
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/ListConfigValueMapModel.java
@@ -0,0 +1,57 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class ListConfigValueMapModel implements ListConfigGetter<ValueMap> {
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private List<ValueMap> configList;
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private Collection<ValueMap> configCollection;
+
+    @ContextAwareConfiguration(name = "org.apache.sling.models.caconfig.example.caconfig.ListConfig")
+    private ValueMap[] configArray;
+
+    @Override
+    public List<ValueMap> getConfigList() {
+        return configList;
+    }
+
+    @Override
+    public Collection<ValueMap> getConfigCollection() {
+        return configCollection;
+    }
+
+    @Override
+    public ValueMap[] getConfigArray() {
+        return configArray;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigAdaptModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigAdaptModel.java
new file mode 100644
index 0000000..ccfaf50
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigAdaptModel.java
@@ -0,0 +1,38 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.caconfig.example.caconfig.model.ConfigurationValuesModel;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class SingleConfigAdaptModel implements SingleConfigGetter<ConfigurationValuesModel> {
+
+    @ContextAwareConfiguration(name = "testSingleConfig")
+    private ConfigurationValuesModel config;
+
+    @Override
+    public ConfigurationValuesModel getConfig() {
+        return config;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigGetter.java b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigGetter.java
new file mode 100644
index 0000000..a69e327
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigGetter.java
@@ -0,0 +1,25 @@
+/*
+ * 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.models.caconfig.example.model;
+
+public interface SingleConfigGetter<T> {
+
+    T getConfig();
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigModel.java
new file mode 100644
index 0000000..1243232
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigModel.java
@@ -0,0 +1,39 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+import org.apache.sling.models.caconfig.example.caconfig.SingleConfig;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class, ResourceResolver.class })
+public class SingleConfigModel implements SingleConfigGetter<SingleConfig> {
+
+    @ContextAwareConfiguration
+    private SingleConfig config;
+
+    @Override
+    public SingleConfig getConfig() {
+        return config;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigValueMapModel.java b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigValueMapModel.java
new file mode 100644
index 0000000..10a8939
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/example/model/SingleConfigValueMapModel.java
@@ -0,0 +1,38 @@
+/*
+ * 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.models.caconfig.example.model;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.caconfig.annotations.ContextAwareConfiguration;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
+public class SingleConfigValueMapModel implements SingleConfigGetter<ValueMap> {
+
+    @ContextAwareConfiguration(name = "testSingleConfig")
+    private ValueMap config;
+
+    @Override
+    public ValueMap getConfig() {
+        return config;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjectorTest.java b/src/test/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjectorTest.java
new file mode 100644
index 0000000..977ae5e
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/caconfig/impl/injectors/ContextAwareConfigurationInjectorTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.models.caconfig.impl.injectors;
+
+import static org.apache.sling.testing.mock.caconfig.ContextPlugins.CACONFIG;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.sling.api.adapter.Adaptable;
+import org.apache.sling.models.caconfig.example.caconfig.ListConfig;
+import org.apache.sling.models.caconfig.example.caconfig.SingleConfig;
+import org.apache.sling.models.caconfig.example.caconfig.model.ConfigurationValuesModel;
+import org.apache.sling.models.caconfig.example.model.InvalidAnnotationListModel;
+import org.apache.sling.models.caconfig.example.model.InvalidAnnotationModel;
+import org.apache.sling.models.caconfig.example.model.InvalidInjectModel;
+import org.apache.sling.models.caconfig.example.model.InvalidSetModel;
+import org.apache.sling.models.caconfig.example.model.ListConfigAdaptModel;
+import org.apache.sling.models.caconfig.example.model.ListConfigGetter;
+import org.apache.sling.models.caconfig.example.model.ListConfigModel;
+import org.apache.sling.models.caconfig.example.model.ListConfigValueMapModel;
+import org.apache.sling.models.caconfig.example.model.SingleConfigAdaptModel;
+import org.apache.sling.models.caconfig.example.model.SingleConfigGetter;
+import org.apache.sling.models.caconfig.example.model.SingleConfigModel;
+import org.apache.sling.models.caconfig.example.model.SingleConfigValueMapModel;
+import org.apache.sling.testing.mock.caconfig.MockContextAwareConfig;
+import org.apache.sling.testing.mock.sling.junit5.SlingContext;
+import org.apache.sling.testing.mock.sling.junit5.SlingContextBuilder;
+import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@ExtendWith(SlingContextExtension.class)
+class ContextAwareConfigurationInjectorTest {
+
+    private final SlingContext context = new SlingContextBuilder()
+            .plugin(CACONFIG)
+            .build();
+
+    @BeforeEach
+    void setUp() {
+        MockContextAwareConfig.registerAnnotationPackages(context, "org.apache.sling.models.caconfig.example.caconfig");
+        context.addModelsForPackage("org.apache.sling.models.caconfig.example.model");
+
+        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", SingleConfig.class,
+                "stringParam", "value1");
+
+        MockContextAwareConfig.writeConfigurationCollection(context, "/content/region/site", ListConfig.class, ImmutableList.of(
+                ImmutableMap.<String,Object>of("stringParam", "item1"),
+                ImmutableMap.<String,Object>of("stringParam", "item2")));
+
+        context.registerInjectActivateService(ContextAwareConfigurationInjector.class);
+    }
+
+    @Test
+    void testSingleConfigModel_Request() {
+        assertSingleConfig(SingleConfigModel.class, context.request(), SingleConfig::stringParam);
+    }
+
+    @Test
+    void testSingleConfigModel_Resource() {
+        assertSingleConfig(SingleConfigModel.class, context.currentResource(), SingleConfig::stringParam);
+    }
+
+    @Test
+    void testSingleConfigValueMapModel_Request() {
+        assertSingleConfig(SingleConfigValueMapModel.class, context.request(), map -> map.get("stringParam", String.class));
+    }
+
+    @Test
+    void testSingleConfigValueMapModel_Resource() {
+        assertSingleConfig(SingleConfigValueMapModel.class, context.currentResource(), map -> map.get("stringParam", String.class));
+    }
+
+    @Test
+    void testSingleConfigAdaptModel_Request() {
+        assertSingleConfig(SingleConfigAdaptModel.class, context.request(), ConfigurationValuesModel::getStringParam);
+    }
+
+    @Test
+    void testSingleConfigAdaptModel_Resource() {
+        assertSingleConfig(SingleConfigAdaptModel.class, context.currentResource(), ConfigurationValuesModel::getStringParam);
+    }
+
+    @Test
+    void testListConfigModel_Request() {
+        assertListConfig(ListConfigModel.class, context.request(), ListConfig::stringParam);
+    }
+
+    @Test
+    void testListConfigModel_Resource() {
+        assertListConfig(ListConfigModel.class, context.currentResource(), ListConfig::stringParam);
+    }
+
+    @Test
+    void testListConfigValueMapModel_Request() {
+        assertListConfig(ListConfigValueMapModel.class, context.request(), map -> map.get("stringParam", String.class));
+    }
+
+    @Test
+    void testListConfigValueMapModel_Resource() {
+        assertListConfig(ListConfigValueMapModel.class, context.currentResource(), map -> map.get("stringParam", String.class));
+    }
+
+    @Test
+    void testListConfigAdaptModel_Request() {
+        assertListConfig(ListConfigAdaptModel.class, context.request(), ConfigurationValuesModel::getStringParam);
+    }
+
+    @Test
+    void testListConfigAdaptModel_Resource() {
+        assertListConfig(ListConfigAdaptModel.class, context.currentResource(), ConfigurationValuesModel::getStringParam);
+    }
+
+    private <T> void assertSingleConfig(Class<? extends SingleConfigGetter<T>> modelClass, Adaptable adaptable, Function<T,String> extractor) {
+        SingleConfigGetter<T> model = adaptable.adaptTo(modelClass);
+        assertNotNull(model);
+        T config = model.getConfig();
+        assertEquals("value1", extractor.apply(config));
+    }
+
+    private <T> void assertListConfig(Class<? extends ListConfigGetter<T>> modelClass, Adaptable adaptable, Function<T,String> extractor) {
+        ListConfigGetter<T> model = adaptable.adaptTo(modelClass);
+        assertNotNull(model);
+        assertListValues(model.getConfigList(), extractor);
+        assertListValues(ImmutableList.copyOf(model.getConfigCollection()), extractor);
+        assertListValues(ImmutableList.copyOf(model.getConfigArray()), extractor);
+    }
+
+    private <T> void assertListValues(List<T> configList, Function<T,String> extractor) {
+        assertNotNull(configList);
+        assertEquals(2, configList.size());
+        assertEquals("item1", extractor.apply(configList.get(0)));
+        assertEquals("item2", extractor.apply(configList.get(1)));
+    }
+
+    @Test
+    void testInvalid_SingleConfigModel_ResourceResolver() {
+        SingleConfigModel model = context.resourceResolver().adaptTo(SingleConfigModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    void testInvalidInjectModel() {
+        InvalidInjectModel model = context.request().adaptTo(InvalidInjectModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    void testInvalidAnnotationModel() {
+        InvalidAnnotationModel model = context.request().adaptTo(InvalidAnnotationModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    void testInvalidAnnotationListModel() {
+        InvalidAnnotationListModel model = context.request().adaptTo(InvalidAnnotationListModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    @SuppressWarnings("null")
+    void testInvalidSetModel() {
+        InvalidSetModel model = context.request().adaptTo(InvalidSetModel.class);
+        assertNull(model);
+    }
+
+}