/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI 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.openengsb.core.test; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.EnumerationUtils; import org.junit.Before; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.openengsb.core.api.Connector; import org.openengsb.core.api.ConnectorInstanceFactory; import org.openengsb.core.api.ConnectorProvider; import org.openengsb.core.api.Domain; import org.openengsb.core.api.DomainProvider; import org.openengsb.core.api.context.ContextHolder; import org.openengsb.core.api.descriptor.ServiceDescriptor; import org.openengsb.core.api.l10n.LocalizableString; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper methods to mock the core {@link org.openengsb.core.api.OsgiUtilsService} service responsible for working with * the OpenEngSB osgi registry. * * ServiceManagement-operations are performed via the {@link BundleContext}. All these calls are handled using two maps * to mock a service-registry (serviceReferences, services) */ public abstract class AbstractOsgiMockServiceTest extends AbstractOpenEngSBTest { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractOsgiMockServiceTest.class); protected BundleContext bundleContext; protected Bundle bundle; /** * This map keeps track of service-references and their properties */ private Map<ServiceReference<?>, Dictionary<String, Object>> serviceReferences = new HashMap<ServiceReference<?>, Dictionary<String, Object>>(); private Map<ServiceListener, Filter> listeners = new HashMap<ServiceListener, Filter>(); /** * This map keeps track of service-references and their corresponding service-object */ private Map<ServiceReference<?>, Object> services = new HashMap<ServiceReference<?>, Object>(); private Long serviceId = Long.MAX_VALUE; @SuppressWarnings("unchecked") @Before public void prepareServiceRegistry() throws Exception { bundleContext = mock(BundleContext.class); /* * redirect calls to getAllServiceReferences to getServiceReferences, since we do not care for * Classloader-restrictions in unit-tests */ when(bundleContext.getAllServiceReferences(anyString(), anyString())).thenAnswer( new Answer<ServiceReference<?>[]>() { @Override public ServiceReference<?>[] answer(InvocationOnMock invocation) throws Throwable { String clazz = (String) invocation.getArguments()[0]; String filter = (String) invocation.getArguments()[1]; return bundleContext.getServiceReferences(clazz, filter); } }); when(bundleContext.getServiceReference(any(Class.class))).thenAnswer(new Answer<ServiceReference<?>>() { @Override public ServiceReference<?> answer(InvocationOnMock invocation) throws Throwable { Class<?> clazz = (Class<?>) invocation.getArguments()[0]; return bundleContext.getServiceReference(clazz.getName()); } }); when(bundleContext.getServiceReference(anyString())).thenAnswer(new Answer<ServiceReference<?>>() { @Override public ServiceReference<?> answer(InvocationOnMock invocation) throws Throwable { String clazz = (String) invocation.getArguments()[0]; ServiceReference<?>[] serviceReferences = bundleContext.getServiceReferences(clazz, null); if (serviceReferences == null) { return null; } return serviceReferences[0]; } }); when(bundleContext.getServiceReferences(any(Class.class), anyString())).thenAnswer(new Answer<Collection<?>>() { @Override public Collection<?> answer(InvocationOnMock invocation) throws Throwable { Class<?> clazz = (Class<?>) invocation.getArguments()[0]; String filter = (String) invocation.getArguments()[1]; ServiceReference<?>[] references = bundleContext.getAllServiceReferences(clazz.getName(), filter); return Arrays.asList(references); } }); /* * retrieve a service-instance from the serviceReferencesMap */ when(bundleContext.getServiceReferences(anyString(), anyString())).thenAnswer( new Answer<ServiceReference<?>[]>() { @Override public ServiceReference<?>[] answer(InvocationOnMock invocation) throws Throwable { String clazz = (String) invocation.getArguments()[0]; String filterString = (String) invocation.getArguments()[1]; if (clazz != null) { if (filterString == null) { filterString = String.format("(%s=%s)", Constants.OBJECTCLASS, clazz); } else { filterString = String.format("(&(%s=%s)%s)", Constants.OBJECTCLASS, clazz, filterString); } } Filter filter = FrameworkUtil.createFilter(filterString); Collection<ServiceReference<?>> result = new ArrayList<ServiceReference<?>>(); synchronized (serviceReferences) { for (Map.Entry<ServiceReference<?>, Dictionary<String, Object>> entry : serviceReferences .entrySet()) { if (filter.match(entry.getValue())) { result.add(entry.getKey()); } } if (result.isEmpty()) { return null; } return result.toArray(new ServiceReference<?>[result.size()]); } } }); /* * retrieves a service-object from the services-map */ when(bundleContext.getService(any(ServiceReference.class))).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { ServiceReference<?> ref = (ServiceReference<?>) invocation.getArguments()[0]; return services.get(ref); } }); /* * register a new service. This step involves creating mock-objects for ServiceRegistration and * ServiceReference. */ when(bundleContext.registerService(any(String[].class), any(), any(Dictionary.class))).thenAnswer( new Answer<ServiceRegistration<?>>() { @Override public ServiceRegistration<?> answer(InvocationOnMock invocation) throws Throwable { String[] clazzes = (String[]) invocation.getArguments()[0]; final Object service = invocation.getArguments()[1]; Dictionary<String, Object> dict = (Dictionary<String, Object>) invocation.getArguments()[2]; return registerServiceInBundlecontext(clazzes, service, dict); } }); when(bundleContext.registerService(anyString(), any(), any(Dictionary.class))).thenAnswer( new Answer<ServiceRegistration<?>>() { @Override public ServiceRegistration<?> answer(InvocationOnMock invocation) throws Throwable { String clazz = (String) invocation.getArguments()[0]; final Object service = invocation.getArguments()[1]; Dictionary<String, Object> dict = (Dictionary<String, Object>) invocation.getArguments()[2]; return registerServiceInBundlecontext(new String[]{ clazz }, service, dict); } }); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { ServiceListener listener = (ServiceListener) invocation.getArguments()[0]; String filter = (String) invocation.getArguments()[1]; synchronized (listeners) { if (filter == null) { listeners.put(listener, null); } else { listeners.put(listener, FrameworkUtil.createFilter(filter)); } } return null; } }).when(bundleContext).addServiceListener(any(ServiceListener.class), anyString()); bundle = mock(Bundle.class); when(bundle.getBundleContext()).thenReturn(bundleContext); when(bundleContext.getBundle()).thenReturn(bundle); /* * since we ignore ClassLoader-visibility issues in unit-tests, just load the class */ when(bundle.loadClass(anyString())).thenAnswer(new Answer<Class<?>>() { @Override public Class<?> answer(InvocationOnMock invocation) throws Throwable { return this.getClass().getClassLoader().loadClass((String) invocation.getArguments()[0]); } }); when(bundle.getHeaders()).thenReturn(new Hashtable<String, String>()); } public void clearRegistry() throws Exception { bundleContext = null; serviceReferences = null; services = null; } /** * create a mock of the specified class and register the service under that interface. It also adds the given id as * property to the service. */ protected <T> T mockService(Class<T> serviceClass, String id) { T serviceMock = mock(serviceClass); registerService(serviceMock, id, serviceClass); return serviceMock; } /** * registers the service with the given properties under the given interfaces */ protected void registerService(Object service, Dictionary<String, Object> props, Class<?>... interfazes) { String[] interfaceNames = new String[interfazes.length]; for (int i = 0; i < interfazes.length; i++) { interfaceNames[i] = interfazes[i].getCanonicalName(); } registerService(service, props, interfaceNames); } /** * registers the service with the given properties under the given interfaces */ protected ServiceReference<?> registerService(Object service, Dictionary<String, Object> props, String... interfazes) { return registerServiceInBundlecontext(interfazes, service, props).getReference(); } /** * registers the service under the given interfaces and sets its location-property accordingly: * location.context=location */ protected void registerServiceAtLocation(Object service, String location, String context, Class<?>... interfazes) { Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put("location." + context, new String[]{ location }); registerService(service, props, interfazes); } /** * registers the service under the given interfaces and sets its location-property for the current context * accordingly: location.context=location */ protected void registerServiceAtLocation(Object service, String location, Class<?>... interfazes) { registerServiceAtLocation(service, location, ContextHolder.get().getCurrentContextId(), interfazes); } /** * registers the service under the given interfaces and sets it's location in the root-context */ protected void registerServiceAtRootLocation(Object service, String location, Class<?>... interfazes) { Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put("location.root", new String[]{ location }); registerService(service, props, interfazes); } /** * registers the service under the given interfaces, settint its "id"-property to the given id */ protected void registerService(Object service, String id, Class<?>... interfaces) { Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(Constants.SERVICE_PID, id); registerService(service, props, interfaces); } protected void registerServiceViaId(Object service, String id, Class<?>... interfaces) { registerService(service, id, interfaces); } private <T> ServiceReference<T> putService(T service, Dictionary<String, Object> props) { @SuppressWarnings("unchecked") ServiceReference<T> serviceReference = mock(ServiceReference.class); long serviceId = --this.serviceId; LOGGER.info("registering service with ID: " + serviceId); props.put(Constants.SERVICE_ID, serviceId); services.put(serviceReference, service); synchronized (serviceReferences) { serviceReferences.put(serviceReference, props); } when(serviceReference.getProperty(anyString())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return serviceReferences.get(invocation.getMock()).get(invocation.getArguments()[0]); } }); when(serviceReference.getBundle()).thenReturn(bundle); when(serviceReference.getPropertyKeys()).thenAnswer(new Answer<String[]>() { @Override public String[] answer(InvocationOnMock invocation) throws Throwable { Dictionary<String, Object> dictionary = serviceReferences.get(invocation.getMock()); List<?> list = EnumerationUtils.toList(dictionary.keys()); @SuppressWarnings("unchecked") Collection<String> typedCollection = CollectionUtils.typedCollection(list, String.class); return typedCollection.toArray(new String[0]); } }); return serviceReference; } /** * creates a mock of {@link ConnectorInstanceFactory} for the given connectorType and domains. * * Only {@link ConnectorInstanceFactory#createNewInstance(String)} is mocked to return a {@link Connector}-mock that * contains the given String as id. * * Also the factory is registered as a service with the required properties */ protected ConnectorInstanceFactory createFactoryMock(String connector, final Class<? extends Connector> connectorClass, String... domains) throws Exception { ConnectorInstanceFactory factory = mock(ConnectorInstanceFactory.class); when(factory.createNewInstance(anyString())).thenAnswer(new Answer<Connector>() { @Override public Connector answer(InvocationOnMock invocation) throws Throwable { Connector result = mock(connectorClass); String id = (String) invocation.getArguments()[0]; when(result.getInstanceId()).thenReturn(id); return result; } }); when(factory.applyAttributes(any(Connector.class), anyMap())).thenAnswer(new Answer<Connector>() { @Override public Connector answer(InvocationOnMock invocation) throws Throwable { return (Connector) invocation.getArguments()[0]; } }); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(org.openengsb.core.api.Constants.CONNECTOR_KEY, connector); props.put(org.openengsb.core.api.Constants.DOMAIN_KEY, domains); registerService(factory, props, ConnectorInstanceFactory.class); return factory; } /** * creates a DomainProvider with for the given interface and name. This creates a mock of {@link DomainProvider} * where all String-methods return the name again. * * Also the service is registered with the mocked service-registry with the given name as domain-value */ protected DomainProvider createDomainProviderMock(final Class<? extends Domain> interfaze, String name) { DomainProvider domainProviderMock = mock(DomainProvider.class); LocalizableString testDomainLocalizedStringMock = mock(LocalizableString.class); when(testDomainLocalizedStringMock.getString(Mockito.<Locale> any())).thenReturn(name); when(domainProviderMock.getId()).thenReturn(name); when(domainProviderMock.getName()).thenReturn(testDomainLocalizedStringMock); when(domainProviderMock.getDescription()).thenReturn(testDomainLocalizedStringMock); when(domainProviderMock.getDomainInterface()).thenAnswer(new Answer<Class<? extends Domain>>() { @Override public Class<? extends Domain> answer(InvocationOnMock invocation) { return interfaze; } }); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(org.openengsb.core.api.Constants.DOMAIN_KEY, name); registerService(domainProviderMock, props, DomainProvider.class); return domainProviderMock; } /** * creates a {@link LocalizableString} that returns the given value for all {@link Locale}s */ protected LocalizableString mockLocalizeableString(String value) { LocalizableString mock2 = mock(LocalizableString.class); when(mock2.getString(any(Locale.class))).thenReturn(value); return mock2; } /** * creates a ConnectorProvider with for the given connectorType and domains. This creates a mock of * {@link ConnectorProvider} that returns a descriptor-mock when calling {@link ConnectorProvider#getDescriptor()} * Also the service is registered with the mocked service-registry */ protected ConnectorProvider createConnectorProviderMock(String connectorType, String... domains) { ConnectorProvider connectorProvider = mock(ConnectorProvider.class); when(connectorProvider.getId()).thenReturn(connectorType); Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(org.openengsb.core.api.Constants.CONNECTOR_KEY, connectorType); props.put(org.openengsb.core.api.Constants.DOMAIN_KEY, domains); registerService(connectorProvider, props, ConnectorProvider.class); ServiceDescriptor descriptor = mock(ServiceDescriptor.class); when(descriptor.getId()).thenReturn(connectorType); LocalizableString name = mockLocalizeableString("service.name"); when(descriptor.getName()).thenReturn(name); LocalizableString desc = mockLocalizeableString("service.description"); when(descriptor.getDescription()).thenReturn(desc); when(connectorProvider.getDescriptor()).thenReturn(descriptor); return connectorProvider; } @SuppressWarnings("unchecked") private ServiceRegistration<?> registerServiceInBundlecontext(String[] clazzes, final Object service, Dictionary<String, Object> dict) { dict.put(Constants.OBJECTCLASS, clazzes); final ServiceReference<?> serviceReference = putService(service, dict); ServiceRegistration<?> result = mock(ServiceRegistration.class); // unregistering removes the service from both maps doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { synchronized (services) { services.remove(serviceReference); } synchronized (serviceReferences) { Dictionary<String, Object> props = serviceReferences.remove(serviceReference); updateServiceListeners(ServiceEvent.UNREGISTERING, serviceReference, props); return null; } } }).when(result).unregister(); // when properties are replaced, place a copy of the new properties-dictionary in the // serviceReferences-map (that overwrites the old dictionary) doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { Dictionary<String, Object> arg = (Dictionary<String, Object>) invocation.getArguments()[0]; Dictionary<String, Object> newDict = new Hashtable<String, Object>(); Enumeration<String> keys = arg.keys(); while (keys.hasMoreElements()) { String next = keys.nextElement(); newDict.put(next, arg.get(next)); } synchronized (serviceReferences) { serviceReferences.put(serviceReference, newDict); } return null; } }).when(result).setProperties(any(Dictionary.class)); when(result.getReference()).thenAnswer(new ValueAnswer<ServiceReference<?>>(serviceReference)); updateServiceListeners(ServiceEvent.REGISTERED, serviceReference, dict); return result; } public void updateServiceListeners(int eventType, final ServiceReference<?> serviceReference, Dictionary<String, Object> dict) { synchronized (listeners) { for (Entry<ServiceListener, Filter> entry : listeners.entrySet()) { Filter filter = entry.getValue(); if (filter == null || filter.match(dict)) { entry.getKey().serviceChanged(new ServiceEvent(eventType, serviceReference)); } } } } protected <T> ServiceList<T> makeServiceList(Class<T> serviceClass) { ServiceTracker<T, T> serviceTracker = new ServiceTracker<T, T>(bundleContext, serviceClass.getName(), null); ServiceList<T> serviceList = new ServiceList<T>(serviceTracker); return serviceList; } protected <T> ServiceReferenceList<T> makeServiceReferenceList(Class<T> serviceClass) { ServiceTracker<T, T> serviceTracker = new ServiceTracker<T, T>(bundleContext, serviceClass.getName(), null); return new ServiceReferenceList<T>(serviceTracker); } }