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 <[email protected]> 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 "[email protected]" <[email protected]>.
