/* * 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.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.net.MalformedURLException; import java.net.URL; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Vector; 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.factory.ModelClassException; import org.apache.sling.models.impl.injectors.ValueMapInjector; import org.apache.sling.models.spi.ImplementationPicker; import org.apache.sling.models.testmodels.classes.implextend.EvenSimplerPropertyModel; import org.apache.sling.models.testmodels.classes.implextend.ExtendsClassPropertyModel; import org.apache.sling.models.testmodels.classes.implextend.ImplementsInterfacePropertyModel; import org.apache.sling.models.testmodels.classes.implextend.ImplementsInterfacePropertyModel2; import org.apache.sling.models.testmodels.classes.implextend.InvalidImplementsInterfacePropertyModel; import org.apache.sling.models.testmodels.classes.implextend.InvalidSampleServiceInterface; import org.apache.sling.models.testmodels.classes.implextend.SampleServiceInterface; import org.apache.sling.models.testmodels.classes.implextend.SimplePropertyModel; import org.junit.After; import org.junit.Assert; 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.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; @RunWith(MockitoJUnitRunner.class) public class ImplementsExtendsTest { @Mock private ComponentContext componentCtx; @Mock private BundleContext bundleContext; @Mock private Bundle bundle; @Mock private BundleEvent bundleEvent; private ModelAdapterFactory factory; private ServiceRegistration[] registeredAdapterFactories; private ImplementationPicker firstImplementationPicker = new FirstImplementationPicker(); private ServicePropertiesMap firstImplementationPickerProps = new ServicePropertiesMap(3, Integer.MAX_VALUE); @SuppressWarnings("unchecked") @Before public void setup() throws ClassNotFoundException, MalformedURLException { when(componentCtx.getBundleContext()).thenReturn(bundleContext); when(componentCtx.getProperties()).thenReturn(new Hashtable<String, Object>()); when(bundleContext.registerService(anyString(), anyObject(), any(Dictionary.class))).then(new Answer<ServiceRegistration>() { @Override public ServiceRegistration answer(InvocationOnMock invocation) throws Throwable { final Dictionary<String, Object> props = (Dictionary<String, Object>)invocation.getArguments()[2]; ServiceRegistration reg = mock(ServiceRegistration.class); ServiceReference ref = mock(ServiceReference.class); when(reg.getReference()).thenReturn(ref); when(ref.getProperty(anyString())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { String key = (String)invocation.getArguments()[0]; return props.get(key); } }); return reg; } }); factory = new ModelAdapterFactory(); factory.activate(componentCtx); factory.bindInjector(new ValueMapInjector(), new ServicePropertiesMap(2, 2)); factory.bindImplementationPicker(firstImplementationPicker, firstImplementationPickerProps); // simulate bundle add for ModelPackageBundleListener Dictionary<String, String> headers = new Hashtable<String,String>(); headers.put(ModelPackageBundleListener.PACKAGE_HEADER, "org.apache.sling.models.testmodels.classes.implextend"); when(bundle.getHeaders()).thenReturn(headers); Vector<URL> classUrls = new Vector<URL>(); classUrls.add(getClassUrl(ExtendsClassPropertyModel.class)); classUrls.add(getClassUrl(ImplementsInterfacePropertyModel.class)); classUrls.add(getClassUrl(ImplementsInterfacePropertyModel2.class)); classUrls.add(getClassUrl(InvalidImplementsInterfacePropertyModel.class)); classUrls.add(getClassUrl(InvalidSampleServiceInterface.class)); classUrls.add(getClassUrl(SampleServiceInterface.class)); classUrls.add(getClassUrl(SimplePropertyModel.class)); when(bundle.findEntries(anyString(), anyString(), anyBoolean())).thenReturn(classUrls.elements()); when(bundle.loadClass(anyString())).then(new Answer<Class<?>>() { @Override public Class<?> answer(InvocationOnMock invocation) throws ClassNotFoundException { String className = (String)invocation.getArguments()[0]; return ImplementsExtendsTest.this.getClass().getClassLoader().loadClass(className); } }); registeredAdapterFactories = (ServiceRegistration[])factory.listener.addingBundle(bundle, bundleEvent); } private URL getClassUrl(Class<?> clazz) throws MalformedURLException { String path = "file:/" + clazz.getName().replace('.', '/') + ".class"; return new URL(path); } @After public void tearDown() { // simulate bundle remove for ModelPackageBundleListener factory.listener.removedBundle(bundle, bundleEvent, registeredAdapterFactories); // make sure adaption is not longer possible: implementation class mapping is removed Resource res = getMockResourceWithProps(); try { factory.getAdapter(res, SampleServiceInterface.class); Assert.fail("Getting the model for interface 'SampleServiceInterface' should fail after the accroding adapter factory has been unregistered"); } catch (ModelClassException e) { } } /** * Try to adapt to interface, with an different implementation class that has the @Model annotation */ @Test public void testImplementsInterfaceModel() { Resource res = getMockResourceWithProps(); SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class); assertNotNull(model); assertEquals(ImplementsInterfacePropertyModel.class, model.getClass()); assertEquals("first-value|null|third-value", model.getAllProperties()); assertTrue(factory.canCreateFromAdaptable(res, SampleServiceInterface.class)); } /** * Try to adapt in a case where there is no picker available. * This causes the extend adaptation to fail. */ @Test public void testImplementsNoPickerWithAdapterEqualsImplementation() { factory.unbindImplementationPicker(firstImplementationPicker, firstImplementationPickerProps); Resource res = getMockResourceWithProps(); SampleServiceInterface model = factory.getAdapter(res, ImplementsInterfacePropertyModel.class); assertNotNull(model); assertEquals("first-value|null|third-value", model.getAllProperties()); assertTrue(factory.canCreateFromAdaptable(res, ImplementsInterfacePropertyModel.class)); } /** * Try to adapt in a case where there is no picker available. * The case where the class is the adapter still works. */ @Test(expected=ModelClassException.class) public void testImplementsNoPickerWithDifferentImplementations() { factory.unbindImplementationPicker(firstImplementationPicker, firstImplementationPickerProps); Resource res = getMockResourceWithProps(); factory.getAdapter(res, SampleServiceInterface.class); } /** * Ensure that the implementation class itself cannot be adapted to if it is not part of the "adapter" property in the annotation. */ /* -- disabled because this cannot work in unit test where the adapterFactory is called directly -- it is enabled in integration tests @Test public void testImplementsInterfaceModel_ImplClassNotMapped() { Resource res = getMockResourceWithProps(); ImplementsInterfacePropertyModel model = factory.getAdapter(res, ImplementsInterfacePropertyModel.class); assertNull(model); } */ /** * Test implementation class with a mapping that is not valid (an interface that is not implemented). */ @Test(expected=ModelClassException.class) public void testInvalidImplementsInterfaceModel() { Resource res = getMockResourceWithProps(); factory.getAdapter(res, InvalidSampleServiceInterface.class); } /** * Test to adapt to a superclass of the implementation class with the appropriate mapping in the @Model annotation. */ @Test public void testExtendsClassModel() { Resource res = getMockResourceWithProps(); // this is not having a model annotation nor does implement an interface/extend a class with a model annotation SimplePropertyModel model = factory.getAdapter(res, SimplePropertyModel.class); assertNotNull(model); assertEquals("!first-value|null|third-value!", model.getAllProperties()); assertTrue(factory.canCreateFromAdaptable(res, SimplePropertyModel.class)); EvenSimplerPropertyModel simplerModel = factory.getAdapter(res, EvenSimplerPropertyModel.class); assertNotNull(simplerModel); assertEquals("first-value", model.getFirst()); assertTrue(factory.canCreateFromAdaptable(res, EvenSimplerPropertyModel.class)); } /** * Try to adapt to interface, with an different implementation class that has the @Model annotation */ @Test public void testImplementsInterfaceModelWithPickLastImplementationPicker() { factory.bindImplementationPicker(new AdapterImplementationsTest.LastImplementationPicker(), new ServicePropertiesMap(3, 1)); Resource res = getMockResourceWithProps(); SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class); assertNotNull(model); assertEquals(ImplementsInterfacePropertyModel2.class, model.getClass()); assertEquals("first-value|null|third-value", model.getAllProperties()); assertTrue(factory.canCreateFromAdaptable(res, SampleServiceInterface.class)); } private Resource getMockResourceWithProps() { Map<String, Object> map = new HashMap<String, Object>(); map.put("first", "first-value"); map.put("third", "third-value"); ValueMap vm = new ValueMapDecorator(map); Resource res = mock(Resource.class); when(res.adaptTo(ValueMap.class)).thenReturn(vm); return res; } }