blob: 4961c905212c56a816dc30ab84ebe1ef0c492fe2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.internal.core.services.osgi;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.e4.core.services.IDisposable;
import org.eclipse.e4.core.services.context.ContextChangeEvent;
import org.eclipse.e4.core.services.context.IContextFunction;
import org.eclipse.e4.core.services.context.IEclipseContext;
import org.eclipse.e4.core.services.context.IRunAndTrack;
import org.eclipse.e4.core.services.context.spi.IContextConstants;
import org.eclipse.e4.core.services.context.spi.ILookupStrategy;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* A context strategy that provides access to OSGi services.
* <p>
* OSGi services are looked up by service class name.
*/
public class OSGiContextStrategy implements ILookupStrategy, IDisposable, ServiceTrackerCustomizer,
IRunAndTrack {
class ServiceData {
// the service name
String name;
ServiceTracker tracker;
// the contexts using this service (IEclipseContext -> null)
final Map<IEclipseContext, Object> users = Collections
.synchronizedMap(new WeakHashMap<IEclipseContext, Object>());
ServiceData(String name) {
this.name = name;
}
public void addContext(IEclipseContext originatingContext) {
users.put(originatingContext, null);
// track this context so we can cleanup when the context is disposed
originatingContext.runAndTrack(OSGiContextStrategy.this, null);
}
public IEclipseContext[] getUsingContexts() {
return users.keySet().toArray(new IEclipseContext[users.size()]);
}
}
/**
* Maintains a cache of registered context functions, indexed by context function key.
*/
class ContextFunctionCache implements ServiceListener {
final Map<String, ServiceReference> functionKeys = Collections
.synchronizedMap(new HashMap<String, ServiceReference>());
public ContextFunctionCache() {
try {
String filter = "(" + Constants.OBJECTCLASS + '=' + IContextFunction.SERVICE_NAME //$NON-NLS-1$
+ ')';
bundleContext.addServiceListener(this, filter);
// process all services already registered
ServiceReference[] existing = bundleContext.getServiceReferences(
IContextFunction.SERVICE_NAME, null);
for (int i = 0; i < existing.length; i++)
add(existing[i]);
} catch (InvalidSyntaxException e) {
// should never happen
throw new RuntimeException(e);
}
}
/**
* Process an added service reference to a context function.
*/
private void add(ServiceReference ref) {
String key = (String) ref.getProperty(IContextFunction.SERVICE_CONTEXT_KEY);
if (key != null)
functionKeys.put(key, ref);
}
public ServiceReference lookup(String key) {
return functionKeys.get(key);
}
public void dispose() {
bundleContext.removeServiceListener(this);
functionKeys.clear();
}
/**
* Process a removed service reference from a context function.
*/
private void remove(ServiceReference ref) {
String key = (String) ref.getProperty(IContextFunction.SERVICE_CONTEXT_KEY);
if (key != null)
functionKeys.remove(key);
}
public void serviceChanged(ServiceEvent event) {
switch (event.getType()) {
case ServiceEvent.REGISTERED:
add(event.getServiceReference());
break;
case ServiceEvent.UNREGISTERING:
remove(event.getServiceReference());
break;
}
}
}
final BundleContext bundleContext;
private ContextFunctionCache functionCache;
/**
* Map of String (service name) -> ServiceData
*/
private Map<String, ServiceData> services = Collections
.synchronizedMap(new HashMap<String, ServiceData>());
public OSGiContextStrategy(BundleContext bc) {
super();
this.bundleContext = bc;
functionCache = new ContextFunctionCache();
}
public Object addingService(ServiceReference reference) {
String name = serviceName(reference);
Object newValue = bundleContext.getService(reference);
if (newValue == null)
return null;
// for performance we store the concrete service object with each context that requested it
ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data == null)
return null;
for (IEclipseContext user : data.getUsingContexts())
user.set(name, newValue);
return newValue;
}
public boolean containsKey(String name, IEclipseContext context) {
// first look for a registered IContextFunction matching the name
if (getContextFunction(name) != null)
return true;
// next, look for a matching service
return bundleContext.getServiceReference(name) != null;
}
public void dispose() {
synchronized (services) {
for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();)
it.next().tracker.close();
services.clear();
}
functionCache.dispose();
}
/**
* Returns the service data corresponding to the given name, or <code>null</code> if no such
* data is available.
*/
private ServiceData getServiceData(String name) {
ServiceData data = services.get(name);
if (data == null)
return null;
if (data.users.isEmpty()) {
data.tracker.close();
services.remove(name);
return null;
}
return data;
}
public Object lookup(String name, IEclipseContext originatingContext) {
if (name == null)
return null;
ServiceData data = getServiceData(name);
if (data == null) {
// first look for a registered IContextFunction matching the name
ServiceReference ref = getContextFunction(name);
if (ref != null)
return bundleContext.getService(ref);
// services must be fully qualified type names
if (name.indexOf('.') == -1)
return null;
// create a tracker to retrieve the service with the given name
data = new ServiceData(name);
try {
data.tracker = new ServiceTracker(bundleContext, name, this);
} catch (IllegalArgumentException iae) {
// we get these when the variables requested are not valid names
return null;
}
// add the context immediately so cleanReferences doesn't remove it
data.addContext(originatingContext);
services.put(name, data);
// just opening a tracker will cause values to be set by the tracker
// callback methods
data.tracker.open();
} else {
data.addContext(originatingContext);
}
return data.tracker.getService();
}
/**
* Returns an IContextFunction service that computes values for the given name, or
* <code>null</code> if there is no matching service.
*/
private ServiceReference getContextFunction(String name) {
return functionCache.lookup(name);
}
public void modifiedService(ServiceReference reference, Object service) {
String name = serviceName(reference);
ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data == null)
return;
for (IEclipseContext user : data.getUsingContexts())
user.set(name, service);
}
public void removedService(ServiceReference reference, Object service) {
String name = serviceName(reference);
// must set to null rather than removing so injection continues to work
ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data != null) {
for (IEclipseContext user : data.getUsingContexts())
user.set(name, null);
}
bundleContext.ungetService(reference);
}
/**
* Returns the service name for a service reference
*/
private String serviceName(ServiceReference reference) {
return ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
}
/**
* Listen for changes on all contexts that have obtained a service from this strategy, so that
* we can do appropriate cleanup of our caches when the requesting context is disposed.
*/
public boolean notify(ContextChangeEvent event) {
IEclipseContext context = event.getContext();
if (context == null)
return false;
if (event.getEventType() != ContextChangeEvent.DISPOSE) {
// do a lookup so the listener isn't removed
context.get(IContextConstants.PARENT);
return true;
}
synchronized (services) {
for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();) {
ServiceData data = it.next();
data.users.remove(context);
// if there are no more references, discard the service
if (data.users.isEmpty()) {
it.remove();
data.tracker.close();
}
}
}
return true;
}
}