blob: bfe0805ba7355a4e2f08b001f337be88944c19e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 Wind River Systems 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.debug.edc.services;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.IDsfService;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
@SuppressWarnings("rawtypes")
/**
* Convenience class to help track DSF services that a given
* client needs to use. This class is based on the DsfServicesTracker
* but is designed to be thread safe so clients can use it to get
* a service reference from any thread. This is important for EDC
* services because they are not restricted to the Dsf thread.
*
* @since 2.0
*/
public class EDCServicesTracker {
private static String getServiceFilter(String sessionId) {
return ("(" + IDsfService.PROP_SESSION_ID + "=" + sessionId + ")").intern(); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
}
final private static class ServiceKey
{
private final String fClassName;
private final String fFilter;
private final int fHashCode;
private final String fHashString;
public ServiceKey(Class<?> clazz, String filter) {
fClassName = clazz != null ? clazz.getName() : null;
fFilter = filter;
fHashString = 'C' + (fClassName == null ? "" : fClassName) + //$NON-NLS-1$
'F' + (fFilter == null ? "" : fFilter); //$NON-NLS-1$
fHashCode = fHashString.hashCode();
}
@Override
public boolean equals(Object other) {
// hashcodes are not guaranteed to be unique, but objects that are equal must have the same hashcode
// thus we can optimize by first comparing hashcodes
return other instanceof ServiceKey &&
((((ServiceKey)other).fHashCode == this.fHashCode) && (((ServiceKey)other).fHashString.equals(this.fHashString)));
}
@Override
public int hashCode() {
return fHashCode;
}
}
private final String fSessionId;
private volatile boolean fDisposed = false;
private final BundleContext fBundleContext;
private final Map<ServiceKey,ServiceReference> fServiceReferences = Collections.synchronizedMap(new HashMap<ServiceKey,ServiceReference>());
private final Map<ServiceReference,Object> fServices = Collections.synchronizedMap(new HashMap<ServiceReference,Object>());
private final String fServiceFilter;
private final ServiceListener fListner = new ServiceListener() {
public void serviceChanged(final ServiceEvent event) {
// Only listen to unregister events.
if (event.getType() != ServiceEvent.UNREGISTERING) {
return;
}
// If session is not active anymore, just exit. The tracker should
// soon be disposed.
DsfSession session = DsfSession.getSession(fSessionId);
if (session == null) {
return;
}
handleUnregisterEvent(event);
}
};
private void handleUnregisterEvent(ServiceEvent event) {
synchronized (fServiceReferences)
{
for (Iterator<Map.Entry<ServiceKey, ServiceReference>> itr = fServiceReferences.entrySet().iterator(); itr.hasNext();) {
Map.Entry<ServiceKey, ServiceReference> entry = itr.next();
if ( entry.getValue().equals(event.getServiceReference()) ) {
itr.remove();
}
}
if (fServices.remove(event.getServiceReference()) != null) {
fBundleContext.ungetService(event.getServiceReference());
}
}
}
/**
* Only constructor.
* @param bundleContext Context of the plugin that the client lives in.
* @param sessionId The DSF session that this tracker will be used for.
*/
public EDCServicesTracker(BundleContext bundleContext, String sessionId) {
fSessionId = sessionId;
fBundleContext = bundleContext;
fServiceFilter = getServiceFilter(sessionId);
try {
fBundleContext.addServiceListener(fListner, fServiceFilter);
} catch (InvalidSyntaxException e) {
assert false : "Invalid session ID syntax"; //$NON-NLS-1$
}
}
/**
* Retrieves a service reference for given service class and optional filter.
* Filter should be used if there are multiple instances of the desired service
* running within the same session.
* @param serviceClass class of the desired service
* @param custom filter to use when searching for the service, this filter will
* be used instead of the standard filter so it should also specify the desired
* session-ID
* @return OSGI service reference object to the desired service, null if not found
*/
public ServiceReference getServiceReference(Class serviceClass, String filter) {
if (fDisposed) {
return null;
}
// If the session is not active, all of its services are gone.
DsfSession session = DsfSession.getSession(fSessionId);
if (session == null) {
return null;
}
ServiceKey key = new ServiceKey(serviceClass, filter != null ? filter : fServiceFilter);
if (fServiceReferences.containsKey(key)) {
return fServiceReferences.get(key);
}
try {
ServiceReference[] references = fBundleContext.getServiceReferences(key.fClassName, key.fFilter);
assert references == null || references.length <= 1;
if (references == null || references.length == 0) {
return null;
} else {
fServiceReferences.put(key, references[0]);
return references[0];
}
} catch(InvalidSyntaxException e) {
assert false : "Invalid session ID syntax"; //$NON-NLS-1$
} catch(IllegalStateException e) {
// Can occur when plugin is shutting down.
}
return null;
}
/**
* Convenience class to retrieve a service based on class name only.
* @param serviceClass class of the desired service
* @return instance of the desired service, null if not found
*/
public <V> V getService(Class<V> serviceClass) {
return getService(serviceClass, null);
}
/**
* Retrieves the service given service class and optional filter.
* Filter should be used if there are multiple instances of the desired service
* running within the same session.
* @param serviceClass class of the desired service
* @param custom filter to use when searching for the service, this filter will
* be used instead of the standard filter so it should also specify the desired
* session-ID
* @return instance of the desired service, null if not found
*/
@SuppressWarnings("unchecked")
public <V> V getService(Class<V> serviceClass, String filter) {
ServiceReference serviceRef = getServiceReference(serviceClass, filter);
if (serviceRef == null) {
return null;
} else {
if (fServices.containsKey(serviceRef)) {
return (V)fServices.get(serviceRef);
} else {
V service = (V)fBundleContext.getService(serviceRef);
fServices.put(serviceRef, service);
return service;
}
}
}
/**
* Un-gets all the references held by this tracker. Must be called
* to avoid leaking OSGI service references.
*/
public void dispose() {
assert !fDisposed;
fDisposed = true;
doDispose();
}
private void doDispose() {
synchronized (fServices)
{
try {
fBundleContext.removeServiceListener(fListner);
for (Iterator<ServiceReference> itr = fServices.keySet().iterator(); itr.hasNext();) {
fBundleContext.ungetService(itr.next());
}
} catch (IllegalStateException e) {
// May be thrown during shutdown (bug 293049).
}
}
fServices.clear();
fServiceReferences.clear();
}
@Override
protected void finalize() throws Throwable {
assert fDisposed;
super.finalize();
}
}