blob: 1cd6cd9e2d686e77b6a5f1fb2995b6740fe67d7f [file] [log] [blame]
/*
* 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;
}
}