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.1.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git
commit 9a66a4b9b9f5b21b390cdbb0d2274fb87397eeb8 Author: Justin Edelson <[email protected]> AuthorDate: Thu Aug 28 23:41:16 2014 +0000 SLING-3895 - supporting list adaptation for method and constructor injection git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1621231 13f79535-47bb-0310-9956-ffa450edef68 --- .../sling/models/impl/ModelAdapterFactory.java | 81 +++++++------- .../models/impl/ResourceModelClassesTest.java | 9 +- .../models/impl/ResourceModelConstructorTest.java | 119 +++++++++++++++++++++ .../models/impl/ResourceModelInterfacesTest.java | 46 ++++++-- .../classes/constructorinjection/ParentModel.java | 58 ++++++++++ .../models/testmodels/interfaces/ParentModel.java | 9 ++ 6 files changed, 266 insertions(+), 56 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 0f51f66..7d00ba8 100644 --- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java +++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java @@ -759,32 +759,10 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { private static boolean setField(Field field, Object createdObject, Object value) { if (value != null) { - 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; - } - } + value = adaptIfNecessary(value, field.getType(), field.getGenericType()); + // value may now be null due to the adaptation done above + if (value == null) { + return false; } boolean accessible = field.isAccessible(); try { @@ -808,11 +786,10 @@ 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(), method.getGenericReturnType(), value) && value instanceof Adaptable) { - value = ((Adaptable) value).adaptTo(method.getReturnType()); - if (value == null) { - return false; - } + value = adaptIfNecessary(value, method.getReturnType(), method.getGenericReturnType()); + // value may now be null due to the adaptation done above + if (value == null) { + return false; } methods.put(method, value); return true; @@ -823,17 +800,10 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { private static boolean setConstructorParameter(ConstructorParameter constructorParameter, List<Object> parameterValues, Object value) { if (value != null && constructorParameter.getType() instanceof Class<?>) { - Class<?> requestedType = (Class<?>)constructorParameter.getType(); - if (!isAcceptableType(requestedType, constructorParameter.getGenericType(), value)) { - if (value instanceof Adaptable) { - value = ((Adaptable) value).adaptTo(requestedType); - if (value == null) { - return false; - } - } - else { - return false; - } + value = adaptIfNecessary(value, (Class<?>) constructorParameter.getType(), constructorParameter.getGenericType()); + // value may now be null due to the adaptation done above + if (value == null) { + return false; } parameterValues.set(constructorParameter.getParameterIndex(), value); return true; @@ -842,6 +812,33 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } + private static Object adaptIfNecessary(Object value, Class<?> type, Type genericType) { + if (!isAcceptableType(type, genericType, value)) { + Class<?> declaredType = type; + if (value instanceof Adaptable) { + value = ((Adaptable) value).adaptTo(type); + } else if (genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Class<?> collectionType = (Class<?>) declaredType; + if (value instanceof Collection && + (collectionType.equals(Collection.class) || collectionType.equals(List.class)) && + parameterizedType.getActualTypeArguments().length == 1) { + List<Object> result = new ArrayList<Object>(); + for (Object valueObject : (Collection<?>) value) { + if (valueObject instanceof Adaptable) { + Object adapted = ((Adaptable) valueObject).adaptTo((Class<?>) parameterizedType.getActualTypeArguments()[0]); + if (adapted != null) { + result.add(adapted); + } + } + } + value = result; + } + } + } + return value; + } + private static boolean isAcceptableType(Class<?> type, Type genericType, Object value) { if (type.isInstance(value)) { if ((type == Collection.class || type == List.class) && genericType instanceof ParameterizedType && 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 625e5a0..888017b 100644 --- a/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java +++ b/src/test/java/org/apache/sling/models/impl/ResourceModelClassesTest.java @@ -259,7 +259,7 @@ public class ResourceModelClassesTest { final Resource firstChild = mock(Resource.class); when(firstChild.adaptTo(ValueMap.class)).thenReturn(firstMap); - when(firstChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap()); + when(firstChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); Object firstGrandChildValue = RandomStringUtils.randomAlphabetic(10); ValueMap firstGrandChildMap = new ValueMapDecorator(Collections.singletonMap("property", firstGrandChildValue)); @@ -268,11 +268,11 @@ public class ResourceModelClassesTest { final Resource firstGrandChild = mock(Resource.class); when(firstGrandChild.adaptTo(ValueMap.class)).thenReturn(firstGrandChildMap); - when(firstGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap()); + when(firstGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); final Resource secondGrandChild = mock(Resource.class); when(secondGrandChild.adaptTo(ValueMap.class)).thenReturn(secondGrandChildMap); - when(secondGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildMap()); + when(secondGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); Resource secondChild = mock(Resource.class); when(secondChild.listChildren()).thenReturn(Arrays.asList(firstGrandChild, secondGrandChild).iterator()); @@ -294,9 +294,10 @@ public class ResourceModelClassesTest { assertEquals(2, model.getGrandChildren().size()); assertEquals(firstGrandChildValue, model.getGrandChildren().get(0).getProperty()); assertEquals(secondGrandChildValue, model.getGrandChildren().get(1).getProperty()); + assertEquals(0, model.getEmptyGrandChildren().size()); } - private class AdaptToChildMap implements Answer<ChildModel> { + private class AdaptToChildModel implements Answer<ChildModel> { @Override public ChildModel answer(InvocationOnMock invocation) throws Throwable { diff --git a/src/test/java/org/apache/sling/models/impl/ResourceModelConstructorTest.java b/src/test/java/org/apache/sling/models/impl/ResourceModelConstructorTest.java new file mode 100644 index 0000000..cc553b8 --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/ResourceModelConstructorTest.java @@ -0,0 +1,119 @@ +/* + * 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.impl; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Hashtable; + +import org.apache.commons.lang.RandomStringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.models.impl.injectors.ChildResourceInjector; +import org.apache.sling.models.impl.injectors.ValueMapInjector; +import org.apache.sling.models.testmodels.classes.ChildModel; +import org.apache.sling.models.testmodels.classes.constructorinjection.ParentModel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; + +@RunWith(MockitoJUnitRunner.class) +public class ResourceModelConstructorTest { + + @Mock + private ComponentContext componentCtx; + + @Mock + private BundleContext bundleContext; + + private ModelAdapterFactory factory; + + @Before + public void setup() { + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + when(componentCtx.getProperties()).thenReturn(new Hashtable<String, Object>()); + + factory = new ModelAdapterFactory(); + factory.activate(componentCtx); + factory.bindInjector(new ValueMapInjector(), new ServicePropertiesMap(2, 2)); + factory.bindInjector(new ChildResourceInjector(), new ServicePropertiesMap(1, 1)); + } + + @Test + public void testChildModel() { + Object firstValue = RandomStringUtils.randomAlphabetic(10); + ValueMap firstMap = new ValueMapDecorator(Collections.singletonMap("property", firstValue)); + + final Resource firstChild = mock(Resource.class); + when(firstChild.adaptTo(ValueMap.class)).thenReturn(firstMap); + when(firstChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); + + 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 AdaptToChildModel()); + + final Resource secondGrandChild = mock(Resource.class); + when(secondGrandChild.adaptTo(ValueMap.class)).thenReturn(secondGrandChildMap); + when(secondGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); + + 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(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(firstValue, childModel.getProperty()); + assertEquals(2, model.getGrandChildren().size()); + assertEquals(firstGrandChildValue, model.getGrandChildren().get(0).getProperty()); + assertEquals(secondGrandChildValue, model.getGrandChildren().get(1).getProperty()); + assertEquals(0, model.getEmptyGrandChildren().size()); + } + + private class AdaptToChildModel 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/impl/ResourceModelInterfacesTest.java b/src/test/java/org/apache/sling/models/impl/ResourceModelInterfacesTest.java index 0696ff5..2e278e1 100644 --- a/src/test/java/org/apache/sling/models/impl/ResourceModelInterfacesTest.java +++ b/src/test/java/org/apache/sling/models/impl/ResourceModelInterfacesTest.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.Hashtable; @@ -102,7 +103,7 @@ public class ResourceModelInterfacesTest { verify(vm).get("required", String.class); } - + @Test public void testChildResource() { Resource child = mock(Resource.class); @@ -136,19 +137,33 @@ public class ResourceModelInterfacesTest { Map<String, Object> props = Collections.singletonMap("property", value); ValueMap map = new ValueMapDecorator(props); - 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(map); + when(firstChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); + + 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 AdaptToChildModel()); - @Override - public ChildModel answer(InvocationOnMock invocation) throws Throwable { - return factory.getAdapter(child, ChildModel.class); - } + final Resource secondGrandChild = mock(Resource.class); + when(secondGrandChild.adaptTo(ValueMap.class)).thenReturn(secondGrandChildMap); + when(secondGrandChild.adaptTo(ChildModel.class)).thenAnswer(new AdaptToChildModel()); - }); + 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); @@ -156,7 +171,18 @@ public class ResourceModelInterfacesTest { ChildModel childModel = model.getFirstChild(); assertNotNull(childModel); assertEquals(value, childModel.getProperty()); + assertEquals(2, model.getGrandChildren().size()); + assertEquals(firstGrandChildValue, model.getGrandChildren().get(0).getProperty()); + assertEquals(secondGrandChildValue, model.getGrandChildren().get(1).getProperty()); + assertEquals(0, model.getEmptyGrandChildren().size()); } + private class AdaptToChildModel 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/constructorinjection/ParentModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ParentModel.java new file mode 100644 index 0000000..7c30148 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ParentModel.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.testmodels.classes.constructorinjection; + +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; +import org.apache.sling.models.testmodels.classes.ChildModel; + +@Model(adaptables = Resource.class) +public class ParentModel { + + @Inject + public ParentModel(@Named("firstChild") ChildModel firstChild, @Named("secondChild") List<ChildModel> grandChildren, + @Named("emptyChild") List<ChildModel> emptyGrandChildren) { + this.firstChild = firstChild; + this.grandChildren = grandChildren; + this.emptyGrandChildren = emptyGrandChildren; + } + + private ChildModel firstChild; + + private List<ChildModel> grandChildren; + + private List<ChildModel> emptyGrandChildren; + + public ChildModel getFirstChild() { + return firstChild; + } + + public List<ChildModel> getGrandChildren() { + return grandChildren; + } + + public List<ChildModel> getEmptyGrandChildren() { + return emptyGrandChildren; + } +} diff --git a/src/test/java/org/apache/sling/models/testmodels/interfaces/ParentModel.java b/src/test/java/org/apache/sling/models/testmodels/interfaces/ParentModel.java index 5dceeb5..90c6c65 100644 --- a/src/test/java/org/apache/sling/models/testmodels/interfaces/ParentModel.java +++ b/src/test/java/org/apache/sling/models/testmodels/interfaces/ParentModel.java @@ -16,7 +16,10 @@ */ package org.apache.sling.models.testmodels.interfaces; +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,4 +29,10 @@ public interface ParentModel { @Inject public ChildModel getFirstChild(); + + @Inject @Named("secondChild") + public List<ChildModel> getGrandChildren(); + + @Inject @Named("emptyChild") + public List<ChildModel> getEmptyGrandChildren(); } -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
