| /* |
| * 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.aries.mocks; |
| |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.Manifest; |
| |
| import junit.framework.AssertionFailedError; |
| |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceEvent; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceListener; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| |
| import org.apache.aries.unittest.mocks.Skeleton; |
| |
| /** |
| * |
| */ |
| /** |
| * This class is a partial implementation of BundleContext. Its main function |
| * is to provide a service registry implementation |
| */ |
| public class BundleContextMock |
| { |
| /** The service registry */ |
| private static Map<String, List<ServiceData>> registry = new HashMap<String, List<ServiceData>>(); |
| /** A list of bundles installed into the runtime */ |
| private static List<Bundle> bundles = new ArrayList<Bundle>(); |
| /** A list of service listeners */ |
| private static List<ServiceListener> listeners = new ArrayList<ServiceListener>(); |
| /** The next service id to be assigned */ |
| private static long nextId = 0; |
| |
| private static class MockServiceFactory implements ServiceFactory |
| { |
| private final Object service; |
| |
| public MockServiceFactory(Object obj) |
| { |
| service = obj; |
| } |
| |
| public Object getService(Bundle arg0, ServiceRegistration arg1) |
| { |
| return service; |
| } |
| |
| public void ungetService(Bundle arg0, ServiceRegistration arg1, Object arg2) |
| { |
| } |
| } |
| |
| private static class FilteredServiceListener implements ServiceListener |
| { |
| private Filter filter; |
| private final ServiceListener listener; |
| |
| public FilteredServiceListener(String f, ServiceListener l) |
| { |
| listener = l; |
| |
| if (f != null) { |
| try { |
| filter = FrameworkUtil.createFilter(f); |
| } catch (InvalidSyntaxException e) { |
| AssertionFailedError err = new AssertionFailedError("The filter " + f + " is invalid"); |
| err.initCause(e); |
| |
| throw err; |
| } |
| } |
| } |
| |
| public void serviceChanged(ServiceEvent arg0) |
| { |
| if (matches(arg0)) listener.serviceChanged(arg0); |
| } |
| |
| private boolean matches(ServiceEvent arg0) |
| { |
| if (filter == null) return true; |
| |
| ServiceReference ref = arg0.getServiceReference(); |
| |
| if (Skeleton.isSkeleton(ref)) { |
| Object template = Skeleton.getSkeleton(ref).getTemplateObject(); |
| |
| if (template instanceof ServiceData) { |
| return filter.match(((ServiceData)template).getProperties()); |
| } |
| } |
| |
| return filter.match(ref); |
| } |
| |
| @Override |
| public boolean equals(Object obj) |
| { |
| if (obj == null) return false; |
| else if (obj instanceof FilteredServiceListener) { |
| return listener.equals(((FilteredServiceListener)obj).listener); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return listener.hashCode(); |
| } |
| } |
| |
| /** |
| * This class represents the information registered about a service. It also |
| * implements part of the ServiceRegistration and ServiceReference interfaces. |
| */ |
| private class ServiceData implements Comparable<ServiceReference> |
| { |
| /** The service that was registered */ |
| private ServiceFactory serviceImpl; |
| /** the service properties */ |
| @SuppressWarnings("unused") |
| private final Hashtable<String, Object> serviceProps = new Hashtable<String, Object>(); |
| /** The interfaces the service publishes with */ |
| private String[] interfaceNames; |
| /** The bundle that defines this service */ |
| private Bundle registeringBundle; |
| |
| /** |
| * This method unregisters the service from the registry. |
| */ |
| public void unregister() |
| { |
| for (String interfaceName : interfaceNames) { |
| List<ServiceData> list = registry.get(interfaceName); |
| if (list != null) { |
| list.remove(this); |
| if (list.isEmpty()) { |
| registry.remove(interfaceName); |
| } |
| } |
| } |
| notifyAllListeners(ServiceEvent.UNREGISTERING); |
| registeringBundle = null; |
| } |
| |
| /** |
| * This method is used to register the service data in the registry |
| */ |
| public void register() |
| { |
| for (String interfaceName : interfaceNames) { |
| List<ServiceData> list = registry.get(interfaceName); |
| if (list == null) { |
| list = new ArrayList<ServiceData>(); |
| registry.put(interfaceName, list); |
| } |
| list.add(this); |
| } |
| notifyAllListeners(ServiceEvent.REGISTERED); |
| } |
| |
| private void notifyAllListeners(int eventType) { |
| List<ServiceListener> copy = new ArrayList<ServiceListener>(listeners.size()); |
| copy.addAll(listeners); |
| for(ServiceListener listener : copy) { |
| listener.serviceChanged(new ServiceEvent(eventType, Skeleton.newMock(this, ServiceReference.class))); |
| } |
| } |
| |
| /** |
| * Change the service properties |
| * @param newProps |
| */ |
| public void setProperties(Dictionary<String,Object> newProps) |
| { |
| // make sure we don't overwrite framework properties |
| newProps.put(Constants.OBJECTCLASS, serviceProps.get(Constants.OBJECTCLASS)); |
| newProps.put(Constants.SERVICE_ID, serviceProps.get(Constants.SERVICE_ID)); |
| |
| Enumeration<String> keys = newProps.keys(); |
| |
| serviceProps.clear(); |
| while (keys.hasMoreElements()) { |
| String key = keys.nextElement(); |
| serviceProps.put(key, newProps.get(key)); |
| } |
| |
| notifyAllListeners(ServiceEvent.MODIFIED); |
| } |
| |
| /** |
| * This implements the isAssignableTo method from ServiceReference. |
| * |
| * @param b |
| * @param className |
| * @return true if the referenced service can be assigned to the requested |
| * class name. |
| */ |
| public boolean isAssignableTo(Bundle b, String className) |
| { |
| boolean result = false; |
| |
| for (String iName : interfaceNames) |
| { |
| result = iName.equals(className); |
| |
| if (result) break; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns the requested service property. |
| * @param key the property to return. |
| * @return the property value. |
| */ |
| public Object getProperty(String key) |
| { |
| return serviceProps.get(key); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if(o == null) return false; |
| |
| if(o == this) return true; |
| |
| if (o instanceof ServiceData) { |
| ServiceData other = (ServiceData) o; |
| return serviceImpl == other.serviceImpl; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return serviceImpl.hashCode(); |
| } |
| |
| /** |
| * @return the keys of all the service properties. |
| */ |
| public String[] getPropertyKeys() |
| { |
| Enumeration<String> e = serviceProps.keys(); |
| |
| String[] toReturn = new String[serviceProps.size()]; |
| |
| for(int i = 0 ; i < serviceProps.size(); i++) |
| toReturn[i] = e.nextElement(); |
| |
| return toReturn; |
| } |
| |
| /** |
| * @return the bundle this service reference was registered against. |
| */ |
| public Bundle getBundle() |
| { |
| return registeringBundle; |
| } |
| |
| /** |
| * @return a service reference for this service registration. |
| */ |
| public ServiceReference getReference() |
| { |
| return Skeleton.newMock(this, ServiceReference.class); |
| } |
| |
| public Hashtable<String, Object> getProperties() |
| { |
| return new Hashtable<String, Object>(serviceProps); |
| } |
| |
| /** |
| * Implement the standard behaviour of the registry |
| */ |
| public int compareTo(ServiceReference o) { |
| Integer rank = (Integer) serviceProps.get(Constants.SERVICE_RANKING); |
| if(rank == null) |
| rank = 0; |
| |
| Integer otherRank = (Integer) o.getProperty(Constants.SERVICE_RANKING); |
| if(otherRank == null) |
| otherRank = 0; |
| //Higher rank = higher order |
| int result = rank.compareTo(otherRank); |
| |
| if(result == 0) { |
| Long id = (Long) serviceProps.get(Constants.SERVICE_ID); |
| Long otherId = (Long) o.getProperty(Constants.SERVICE_ID); |
| //higher id = lower order |
| return otherId.compareTo(id); |
| } |
| return result; |
| } |
| } |
| |
| /** The bundle associated with this bundle context */ |
| private Bundle bundle; |
| |
| /** |
| * Default constructor, widely used in the tests. |
| */ |
| public BundleContextMock() |
| { |
| bundle = Skeleton.newMock(new BundleMock("test." + new Random(System.currentTimeMillis()).nextInt(), |
| new Hashtable<Object, Object>()), Bundle.class); |
| } |
| |
| /** |
| * Constructor used by BundleMock, it ensures the bundle and its context are wired together correctly. |
| * |
| * TODO We have to many Bundle mocks objects for a single OSGi bundle, we need to update this. |
| * |
| * @param b |
| */ |
| public BundleContextMock(Bundle b) |
| { |
| bundle = b; |
| } |
| |
| /** |
| * This checks that we have at least one service with this interface name. |
| * |
| * @param interfaceName the name of the interface. |
| */ |
| public static void assertServiceExists(String interfaceName) |
| { |
| assertTrue("No service registered with interface " + interfaceName + ". Services found: " + registry.keySet(), registry.containsKey(interfaceName)); |
| } |
| |
| /** |
| * This checks that we have at no services with this interface name. |
| * |
| * @param interfaceName the name of the interface. |
| */ |
| public static void assertNoServiceExists(String interfaceName) |
| { |
| assertFalse("Services registered with interface " + interfaceName + ". Services found: " + registry.keySet(), registry.containsKey(interfaceName)); |
| } |
| |
| /** |
| * This implements the registerService method from BundleContext. |
| * |
| * @param interFace |
| * @param service |
| * @param properties |
| * @return the ServiceRegistration object for this service. |
| */ |
| public ServiceRegistration registerService(String interFace, final Object service, Dictionary<String, Object> properties) |
| { |
| // validate that the service implements interFace |
| try { |
| Class<?> clazz = Class.forName(interFace, false, service.getClass().getClassLoader()); |
| |
| if (!!!clazz.isInstance(service) && !!!(service instanceof ServiceFactory)) { |
| throw new AssertionFailedError("The service " + service + " does not implement " + interFace); |
| } |
| } catch (ClassNotFoundException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| } |
| |
| return registerService(new String[] {interFace}, service, properties); |
| } |
| |
| /** |
| * This implements the registerService method from BundleContext. |
| * |
| * @param interfaces |
| * @param service |
| * @param properties |
| * @return the ServiceRegistration object for this service. |
| */ |
| public ServiceRegistration registerService(String[] interfaces, Object service, Dictionary<String, Object> properties) |
| { |
| if (properties == null) properties = new Hashtable<String, Object>(); |
| |
| ServiceData data = new ServiceData(); |
| // cast the service to a service factory because in our framework we only ever register |
| // a service factory. If we every put a non-service factory object in that is a failure. |
| properties.put(Constants.OBJECTCLASS, interfaces); |
| properties.put(Constants.SERVICE_ID, nextId++); |
| if (service instanceof ServiceFactory) { |
| data.serviceImpl = (ServiceFactory)service; |
| } else { |
| data.serviceImpl = new MockServiceFactory(service); |
| } |
| data.interfaceNames = interfaces; |
| data.registeringBundle = bundle; |
| |
| Enumeration<String> keys = properties.keys(); |
| |
| while (keys.hasMoreElements()) { |
| String key = keys.nextElement(); |
| data.serviceProps.put(key, properties.get(key)); |
| } |
| |
| data.register(); |
| |
| return Skeleton.newMock(data, ServiceRegistration.class); |
| } |
| |
| /** |
| * This helper method is used to get the service from the registry with the |
| * given interface name. |
| * |
| * <p>This should really return multiple services. |
| * </p> |
| * |
| * @param interfaceName the interface name. |
| * @param bundle the bundle name. |
| * @return the registered service. |
| */ |
| public static Object getService(String interfaceName, Bundle bundle) |
| { |
| List<ServiceData> datum = registry.get(interfaceName); |
| |
| if (datum == null) return null; |
| else if (datum.isEmpty()) return null; |
| // this is safe for now, but may not be when we do other scoped components. |
| else { |
| ServiceRegistration reg = Skeleton.newMock(ServiceRegistration.class); |
| return datum.iterator().next().serviceImpl.getService(bundle, reg); |
| } |
| } |
| |
| /** |
| * A mock implementation of the getServiceReferences method. It does not currently |
| * process the filter, this is probably a bit hard, so we might cheat when we do. |
| * |
| * <p>Note this does not check that the service classes are visible to the |
| * caller as OSGi does. It is equivalent to getAllServiceReferences. |
| * </p> |
| * |
| * @param className the name of the class the lookup is for. |
| * @param filter |
| * @return an array of matching service references. |
| * @throws InvalidSyntaxException |
| */ |
| public ServiceReference[] getServiceReferences(String className, String filter) throws InvalidSyntaxException |
| { |
| List<ServiceData> data = new ArrayList<ServiceData>(); |
| |
| if (className != null) { |
| List<ServiceData> tmpData = registry.get(className); |
| if (tmpData != null) data.addAll(tmpData); |
| } else { |
| data = new ArrayList<ServiceData>(); |
| for (List<ServiceData> value : registry.values()) |
| data.addAll(value); |
| } |
| |
| ServiceReference[] refs; |
| |
| if (data == null) { |
| refs = null; |
| } else { |
| |
| if (filter != null) { |
| Filter f = FrameworkUtil.createFilter(filter); |
| |
| Iterator<ServiceData> it = data.iterator(); |
| |
| while (it.hasNext()) { |
| ServiceData sd = it.next(); |
| |
| if (!!!f.match(sd.getProperties())) it.remove(); |
| } |
| } |
| |
| if (data.isEmpty()) return null; |
| |
| refs = new ServiceReference[data.size()]; |
| for (int i = 0; i < refs.length; i++) { |
| refs[i] = Skeleton.newMock(data.get(i), ServiceReference.class); |
| } |
| } |
| |
| return refs; |
| } |
| |
| /** |
| * Gets the first matching service reference. |
| * |
| * @param className the class name wanted. |
| * @return the matchine service, or null if one cannot be found. |
| */ |
| public ServiceReference getServiceReference(String className) |
| { |
| ServiceReference[] refs; |
| try { |
| refs = getServiceReferences(className, null); |
| if (refs != null) return refs[0]; |
| |
| return null; |
| } catch (InvalidSyntaxException e) { |
| // should never happen. |
| e.printStackTrace(); |
| } |
| return null; |
| } |
| |
| /** |
| * This method finds all the service references in the registry with the |
| * matching class name and filter. |
| * |
| * @param className |
| * @param filter |
| * @return the matching service references. |
| * @throws InvalidSyntaxException |
| */ |
| public ServiceReference[] getAllServiceReferences(String className, String filter) throws InvalidSyntaxException |
| { |
| return getServiceReferences(className, filter); |
| } |
| |
| /** |
| * Retrieve a service from the registry. |
| * @param ref the service reference. |
| * @return the returned service. |
| */ |
| public Object getService(ServiceReference ref) |
| { |
| ServiceData data = (ServiceData)Skeleton.getSkeleton(ref).getTemplateObject(); |
| |
| return data.serviceImpl.getService(getBundle(), Skeleton.newMock(data, ServiceRegistration.class)); |
| } |
| |
| /** |
| * This method implements the installBundle method from BundleContext. It |
| * makes use of the java.util.jar package to parse the manifest from the input |
| * stream. |
| * |
| * @param location the location of the bundle. |
| * @param is the input stream to read from. |
| * @return the created bundle. |
| * @throws BundleException |
| */ |
| public Bundle installBundle(String location, InputStream is) throws BundleException |
| { |
| Bundle b; |
| JarInputStream jis; |
| try { |
| jis = new JarInputStream(is); |
| |
| Manifest man = jis.getManifest(); |
| |
| b = createBundle(man, null); |
| |
| } catch (IOException e) { |
| throw new BundleException(e.getMessage(), e); |
| } |
| |
| return b; |
| } |
| |
| /** |
| * Create a mock bundle correctly configured using the supplied manifest and |
| * location. |
| * |
| * @param man the manifest to load. |
| * @param location the location on disk. |
| * @return the created bundle |
| * @throws MalformedURLException |
| */ |
| private Bundle createBundle(Manifest man, String location) throws MalformedURLException |
| { |
| Attributes attribs = man.getMainAttributes(); |
| String symbolicName = attribs.getValue(Constants.BUNDLE_SYMBOLICNAME); |
| |
| Hashtable<Object, Object> attribMap = new Hashtable<Object, Object>(); |
| |
| for (Map.Entry<Object, Object> entry : attribs.entrySet()) { |
| Attributes.Name name = (Attributes.Name)entry.getKey(); |
| attribMap.put(name.toString(), entry.getValue()); |
| } |
| |
| BundleMock mock = new BundleMock(symbolicName, attribMap, location); |
| |
| mock.addToClassPath(new File("build/unittest/classes").toURL()); |
| |
| Bundle b = Skeleton.newMock(mock, Bundle.class); |
| |
| bundles.add(b); |
| |
| return b; |
| } |
| |
| /** |
| * Asks to install an OSGi bundle from the given location. |
| * |
| * @param location the location of the bundle on the file system. |
| * @return the installed bundle. |
| * @throws BundleException |
| */ |
| public Bundle installBundle(String location) throws BundleException |
| { |
| try { |
| URI uri = new URI(location.replaceAll(" ", "%20")); |
| |
| File baseDir = new File(uri); |
| Manifest man = null; |
| //check if it is a directory |
| if (baseDir.isDirectory()){ |
| man = new Manifest(new FileInputStream(new File(baseDir, "META-INF/MANIFEST.MF"))); |
| } |
| //if it isn't assume it is a jar file |
| else{ |
| InputStream is = new FileInputStream(baseDir); |
| JarInputStream jis = new JarInputStream(is); |
| man = jis.getManifest(); |
| jis.close(); |
| if (man == null){ |
| throw new BundleException("Null manifest"); |
| } |
| } |
| |
| return createBundle(man, location); |
| } catch (IOException e) { |
| throw new BundleException(e.getMessage(), e); |
| } catch (URISyntaxException e) { |
| // TODO Auto-generated catch block |
| throw new BundleException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * @return all the bundles in the system |
| */ |
| public Bundle[] getBundles() |
| { |
| return bundles.toArray(new Bundle[bundles.size()]); |
| } |
| |
| /** |
| * Add a service listener. |
| * |
| * @param listener |
| * @param filter |
| */ |
| public void addServiceListener(ServiceListener listener, String filter) |
| { |
| listeners.add(new FilteredServiceListener(filter, listener)); |
| } |
| |
| /** |
| * Add a service listener. |
| * |
| * @param listener |
| */ |
| public void addServiceListener(ServiceListener listener) |
| { |
| listeners.add(listener); |
| } |
| |
| /** |
| * Remove a service listener |
| * @param listener |
| */ |
| public void removeServiceListener(ServiceListener listener) |
| { |
| listeners.remove(new FilteredServiceListener(null, listener)); |
| } |
| |
| public String getProperty(String name) |
| { |
| if (Constants.FRAMEWORK_VERSION.equals(name)) { |
| return "4.1"; |
| } |
| /*added System.getProperty so that tests can set a system property |
| * but it is retrieved via the BundleContext. |
| * This allows tests to emulate different properties being set on the |
| * context, helpful for the feature pack launcher/kernel relationship |
| */ |
| return System.getProperty(name); |
| } |
| |
| /** |
| * @return the bundle associated with this bundle context (if we created one). |
| */ |
| public Bundle getBundle() |
| { |
| return bundle; |
| } |
| |
| /** |
| * This method clears the service registry. |
| */ |
| public static void clear() |
| { |
| registry.clear(); |
| bundles.clear(); |
| listeners.clear(); |
| nextId = 0; |
| } |
| |
| public static List<ServiceListener> getServiceListeners() |
| { |
| return listeners; |
| } |
| |
| public void addBundle(Bundle b) |
| { |
| bundles.add(b); |
| } |
| } |