blob: c6955b77cc988fd381ed82364e438c0ca43803d3 [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.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);
}
}