You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:55:21 UTC

[sling-org-apache-sling-models-impl] 08/16: SLING-3516 - adding ability to inject a list of grandchild resources. Thanks to Igor Sechyn for the initial patch!

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

rombert pushed a commit to annotated tag org.apache.sling.models.impl-1.0.6
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git

commit 0df8a43a76ae0892ccacaa9f33d8bb7dc9704e13
Author: Justin Edelson <ju...@apache.org>
AuthorDate: Tue Jun 17 19:53:52 2014 +0000

    SLING-3516 - adding ability to inject a list of grandchild resources. Thanks to Igor Sechyn for the initial patch!
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1603277 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/models/impl/ModelAdapterFactory.java     | 52 +++++++++++++++---
 .../impl/injectors/ChildResourceInjector.java      | 50 ++++++++++++++++-
 .../models/impl/ResourceModelClassesTest.java      | 64 +++++++++++++++++-----
 .../testmodels/classes/ChildResourceModel.java     | 19 ++++++-
 .../models/testmodels/classes/ParentModel.java     | 17 ++++++
 5 files changed, 180 insertions(+), 22 deletions(-)

diff --git a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
index effe935..abdd732 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
@@ -25,15 +25,18 @@ import java.lang.reflect.Field;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -622,10 +625,31 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
 
     private static boolean setField(Field field, Object createdObject, Object value) {
         if (value != null) {
-            if (!isAcceptableType(field.getType(), value) && value instanceof Adaptable) {
-                value = ((Adaptable) value).adaptTo(field.getType());
-                if (value == null) {
-                    return false;
+            if (!isAcceptableType(field.getType(), field.getGenericType(), value)) {
+                Class<?> declaredType = field.getType();
+                Type genericType = field.getGenericType();
+                if (value instanceof Adaptable) {
+                    value = ((Adaptable) value).adaptTo(field.getType());
+                    if (value == null) {
+                        return false;
+                    }
+                } else if (genericType instanceof ParameterizedType) {
+                    ParameterizedType type = (ParameterizedType) genericType;
+                    Class<?> collectionType = (Class<?>) declaredType;
+                    if (value instanceof Collection &&
+                            (collectionType.equals(Collection.class) || collectionType.equals(List.class)) &&
+                            type.getActualTypeArguments().length == 1) {
+                        List<Object> result = new ArrayList<Object>();
+                        for (Object valueObject : (Collection<?>) value) {
+                            if (valueObject instanceof Adaptable) {
+                                Object adapted = ((Adaptable) valueObject).adaptTo((Class<?>) type.getActualTypeArguments()[0]);
+                                if (adapted != null) {
+                                    result.add(adapted);
+                                }
+                            }
+                        }
+                        value = result;
+                    }
                 }
             }
             boolean accessible = field.isAccessible();
@@ -650,7 +674,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
 
     private static boolean setMethod(Method method, Map<Method, Object> methods, Object value) {
         if (value != null) {
-            if (!isAcceptableType(method.getReturnType(), value) && value instanceof Adaptable) {
+            if (!isAcceptableType(method.getReturnType(), method.getGenericReturnType(), value) && value instanceof Adaptable) {
                 value = ((Adaptable) value).adaptTo(method.getReturnType());
                 if (value == null) {
                     return false;
@@ -663,9 +687,23 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
         }
     }
 
-    private static boolean isAcceptableType(Class<?> type, Object value) {
+    private static boolean isAcceptableType(Class<?> type, Type genericType, Object value) {
         if (type.isInstance(value)) {
-            return true;
+            if ((type == Collection.class || type == List.class) && genericType instanceof ParameterizedType &&
+                    value instanceof Collection) {
+                Iterator<?> it = ((Collection<?>) value).iterator();
+                if (!it.hasNext()) {
+                    // empty collection, so it doesn't really matter
+                    return true;
+                } else {
+                    // this is not an ideal way to get the actual component type, but erasure...
+                    Class<?> actualComponentType = it.next().getClass();
+                    Class<?> desiredComponentType = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                    return desiredComponentType.isAssignableFrom(actualComponentType);
+                }
+            } else {
+                return true;
+            }
         }
 
         if (type == Integer.TYPE) {
diff --git a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java
index 500473b..53c5ba8 100644
--- a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java
+++ b/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java
@@ -17,7 +17,12 @@
 package org.apache.sling.models.impl.injectors;
 
 import java.lang.reflect.AnnotatedElement;
+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.lang.StringUtils;
 import org.apache.felix.scr.annotations.Component;
@@ -47,12 +52,55 @@ public class ChildResourceInjector implements Injector, InjectAnnotationProcesso
     public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element,
             DisposalCallbackRegistry callbackRegistry) {
         if (adaptable instanceof Resource) {
-            return ((Resource) adaptable).getChild(name);
+            Resource child = ((Resource) adaptable).getChild(name);
+            if (child != null) {
+                return getValue(child, declaredType);
+            }
+        }
+        return null;
+    }
+
+    private Object getValue(Resource adaptable, Type declaredType) {
+        if (declaredType instanceof Class) {
+            return adaptable;
+        } else if (isDeclaredTypeCollection(declaredType)) {
+            return getResultList(adaptable, declaredType);
         } else {
             return null;
         }
     }
 
+    private Object getResultList(Resource resource, Type declaredType) {
+       List<Resource> result = new ArrayList<Resource>();
+       Class<?> type = getActualType((ParameterizedType) declaredType);
+       if (type != null && resource != null) {
+           Iterator<Resource> children = resource.listChildren();
+           while (children.hasNext()) {
+               result.add(children.next());
+           }
+       }
+       return result;
+   }
+
+   private Class<?> getActualType(ParameterizedType declaredType) {
+       Type[] types = declaredType.getActualTypeArguments();
+       if (types != null && types.length > 0) {
+           return (Class<?>) types[0];
+       }
+       return null;
+   }
+
+    private boolean isDeclaredTypeCollection(Type declaredType) {
+       boolean isCollection = false;
+       if (declaredType instanceof ParameterizedType) {
+           ParameterizedType type = (ParameterizedType) declaredType;
+           Class<?> collectionType = (Class<?>) type.getRawType();
+           isCollection = collectionType.equals(Collection.class)
+                   || collectionType.equals(List.class);
+       }
+       return isCollection;
+   }
+
     @Override
     public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) {
         // check if the element has the expected annotation
diff --git a/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java b/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java
index 5818f44..8f7485f 100644
--- a/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java
+++ b/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java
@@ -19,6 +19,7 @@ package org.apache.sling.models.impl;
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -157,13 +158,26 @@ public class ResourceModelClassesTest {
     @Test
     public void testChildResource() {
         Resource child = mock(Resource.class);
+        Resource secondChild = mock(Resource.class);
+        Resource emptyChild = mock(Resource.class);
+
+        Resource firstGrandChild = mock(Resource.class);
+        Resource secondGrandChild = mock(Resource.class);
+        when(secondChild.listChildren()).thenReturn(Arrays.asList(firstGrandChild, secondGrandChild).iterator());
+        when(emptyChild.listChildren()).thenReturn(Collections.<Resource>emptySet().iterator());
 
         Resource res = mock(Resource.class);
         when(res.getChild("firstChild")).thenReturn(child);
+        when(res.getChild("secondChild")).thenReturn(secondChild);
+        when(res.getChild("emptyChild")).thenReturn(emptyChild);
 
         ChildResourceModel model = factory.getAdapter(res, ChildResourceModel.class);
         assertNotNull(model);
         assertEquals(child, model.getFirstChild());
+        assertEquals(2, model.getGrandChildren().size());
+        assertEquals(firstGrandChild, model.getGrandChildren().get(0));
+        assertEquals(secondGrandChild, model.getGrandChildren().get(1));
+        assertEquals(0, model.getEmptyGrandChildren().size());
     }
 
     @Test
@@ -183,30 +197,54 @@ public class ResourceModelClassesTest {
 
     @Test
     public void testChildModel() {
-        Object value = RandomStringUtils.randomAlphabetic(10);
-        Map<String, Object> props = Collections.singletonMap("property", value);
-        ValueMap map = new ValueMapDecorator(props);
+        Object firstValue = RandomStringUtils.randomAlphabetic(10);
+        ValueMap firstMap = new ValueMapDecorator(Collections.singletonMap("property", firstValue));
 
-        final Resource child = mock(Resource.class);
-        when(child.adaptTo(ValueMap.class)).thenReturn(map);
-        when(child.adaptTo(ChildModel.class)).thenAnswer(new Answer<ChildModel>() {
+        final Resource firstChild = mock(Resource.class);
+        when(firstChild.adaptTo(ValueMap.class)).thenReturn(firstMap);
+        when(firstChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap());
+
+        Object firstGrandChildValue = RandomStringUtils.randomAlphabetic(10);
+        ValueMap firstGrandChildMap = new ValueMapDecorator(Collections.singletonMap("property", firstGrandChildValue));
+        Object secondGrandChildValue = RandomStringUtils.randomAlphabetic(10);
+        ValueMap secondGrandChildMap = new ValueMapDecorator(Collections.singletonMap("property", secondGrandChildValue));
+
+        final Resource firstGrandChild = mock(Resource.class);
+        when(firstGrandChild.adaptTo(ValueMap.class)).thenReturn(firstGrandChildMap);
+        when(firstGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap());
+
+        final Resource secondGrandChild = mock(Resource.class);
+        when(secondGrandChild.adaptTo(ValueMap.class)).thenReturn(secondGrandChildMap);
+        when(secondGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap());
 
-            @Override
-            public ChildModel answer(InvocationOnMock invocation) throws Throwable {
-                return factory.getAdapter(child, ChildModel.class);
-            }
+        Resource secondChild = mock(Resource.class);
+        when(secondChild.listChildren()).thenReturn(Arrays.asList(firstGrandChild, secondGrandChild).iterator());
 
-        });
+        Resource emptyChild = mock(Resource.class);
+        when(emptyChild.listChildren()).thenReturn(Collections.<Resource>emptySet().iterator());
 
         Resource res = mock(Resource.class);
-        when(res.getChild("firstChild")).thenReturn(child);
+        when(res.getChild("firstChild")).thenReturn(firstChild);
+        when(res.getChild("secondChild")).thenReturn(secondChild);
+        when(res.getChild("emptyChild")).thenReturn(emptyChild);
 
         ParentModel model = factory.getAdapter(res, ParentModel.class);
         assertNotNull(model);
 
         ChildModel childModel = model.getFirstChild();
         assertNotNull(childModel);
-        assertEquals(value, childModel.getProperty());
+        assertEquals(firstValue, childModel.getProperty());
+        assertEquals(2, model.getGrandChildren().size());
+        assertEquals(firstGrandChildValue, model.getGrandChildren().get(0).getProperty());
+        assertEquals(secondGrandChildValue, model.getGrandChildren().get(1).getProperty());
+    }
+
+    private class AdaptToChildMap implements Answer<ChildModel> {
+
+        @Override
+        public ChildModel answer(InvocationOnMock invocation) throws Throwable {
+            return factory.getAdapter(invocation.getMock(), ChildModel.class);
+        }
     }
 
 }
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceModel.java
index 0bca5ec..c69242f 100644
--- a/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceModel.java
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceModel.java
@@ -16,7 +16,10 @@
  */
 package org.apache.sling.models.testmodels.classes;
 
+import java.util.List;
+
 import javax.inject.Inject;
+import javax.inject.Named;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.models.annotations.Model;
@@ -26,9 +29,23 @@ public class ChildResourceModel {
 
     @Inject
     private Resource firstChild;
-    
+
+    @Inject @Named("secondChild")
+    private List<Resource> grandChildren;
+
+    @Inject @Named("emptyChild")
+    private List<Resource> emptyGrandChildren;
+
     public Resource getFirstChild() {
         return firstChild;
     }
 
+    public List<Resource> getGrandChildren() {
+        return grandChildren;
+    }
+
+    public List<Resource> getEmptyGrandChildren() {
+        return emptyGrandChildren;
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/ParentModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/ParentModel.java
index c0da400..c714e32 100644
--- a/src/test/java/org/apache/sling/models/testmodels/classes/ParentModel.java
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/ParentModel.java
@@ -16,7 +16,10 @@
  */
 package org.apache.sling.models.testmodels.classes;
 
+import java.util.List;
+
 import javax.inject.Inject;
+import javax.inject.Named;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.models.annotations.Model;
@@ -27,7 +30,21 @@ public class ParentModel {
     @Inject
     private ChildModel firstChild;
 
+    @Inject @Named("secondChild")
+    private List<ChildModel> grandChildren;
+
+    @Inject @Named("emptyChild")
+    private List<ChildModel> emptyGrandChildren;
+
     public ChildModel getFirstChild() {
         return firstChild;
     }
+
+    public List<ChildModel> getGrandChildren() {
+        return grandChildren;
+    }
+
+    public List<ChildModel> getEmptyGrandChildren() {
+        return emptyGrandChildren;
+    }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.