| /* |
| * 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.adapter.AdapterFactory; |
| 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; |
| |
| @RunWith(MockitoJUnitRunner.class) |
| public class ImplementsExtendsTest { |
| |
| @Mock |
| private BundleContext bundleContext; |
| |
| @Mock |
| private Bundle bundle; |
| |
| @Mock |
| private BundleEvent bundleEvent; |
| |
| private ModelAdapterFactory factory; |
| |
| private ServiceRegistration<AdapterFactory>[] 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(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 = AdapterFactoryTest.createModelAdapterFactory(bundleContext); |
| 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; |
| } |
| |
| } |