blob: 29ede37db9baa5e39f116b8582a3931b0e7711a2 [file] [log] [blame]
/*
* Copyright (c) 2009 Siemens Enterprise Communications GmbH & Co. KG,
* Germany. All rights reserved.
*
* Siemens Enterprise Communications GmbH & Co. KG is a Trademark Licensee
* of Siemens AG.
*
* This material, including documentation and any related computer programs,
* is protected by copyright controlled by Siemens Enterprise Communications
* GmbH & Co. KG and its licensors. All rights are 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
*/
package org.eclipse.ecf.osgi.services.discovery.local;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.ecf.osgi.services.discovery.DiscoveredServiceNotification;
import org.eclipse.ecf.osgi.services.discovery.DiscoveredServiceTracker;
import org.eclipse.ecf.osgi.services.discovery.ServiceEndpointDescription;
import org.eclipse.ecf.osgi.services.discovery.ServicePublication;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.ServiceTracker;
public class FileBasedDiscoveryImpl {
/**
* ServiceRegistration property identifying Discovery's default strategy for
* distribution of published service information. It's up to the Discovery
* service to provide and support this property. Value of this property is
* of type String.
*
* TODO do we support this property?
*/
public static final String PROP_KEY_PUBLISH_STRATEGY = "osgi.discovery.strategy.publication";
/**
* Constant for a "push" publication strategy: published service information
* is actively pushed to the network for discovery.
*/
public static final String PROP_VAL_PUBLISH_STRATEGY_PUSH = "push";
/**
* Constant for a "pull" publication strategy: published service information
* is available just upon lookup requests.
*/
public static final String PROP_VAL_PUBLISH_STRATEGY_PULL = "pull";
private static BundleContext context;
private static LogService logService;
private static Map /* <SLPServiceDescriptionAdapter> */inMemoryCache = Collections
.synchronizedMap(new HashMap());
private ServiceTracker spTracker = null;
private DSTTracker discoTrackerCustomizer = null;
private ServiceTracker discoTracker = null;
private BundleTracker bt = null;
/**
* Constructor.
*
* @param bc
* the BundleContext of the containing bundle.
* @param logger
* a LogService instance
*/
public FileBasedDiscoveryImpl(final BundleContext bc,
final LogService logger) {
logService = logger;
context = bc;
}
/**
* Initialization method called by Activator.
*/
public void init() {
log(LogService.LOG_DEBUG, "init");
discoTrackerCustomizer = new DSTTracker(context);
discoTracker = new ServiceTracker(context,
DiscoveredServiceTracker.class.getName(),
discoTrackerCustomizer);
discoTracker.open();
spTracker = new ServiceTracker(context,
ServicePublication.class.getName(),
new ServicePublicationTracker(context, this));
spTracker.open();
// We track active and starting bundles as per Remote Services Admin spec
bt = new BundleTracker(context, Bundle.ACTIVE | Bundle.STARTING, new BundleTrackerImpl(this));
bt.open();
}
/**
* Shutdown method called by Activator.
*/
public void destroy() {
log(LogService.LOG_DEBUG, "destroy");
bt.close();
discoTracker.close();
spTracker.close();
}
/**
* Publishes a service.
*
* TODO: publish also for every endpoint interface TODO: use
* endpointLocation as service URL if given
*
* @param javaInterfaces
* collection of java interface names
* @param javaInterfacesAndVersions
* collection of versions, where the order of the version must
* match the order of the java interfaces
* @param javaInterfacesAndEndpointInterfaces
* optional collection of endpointinterface names, where the
* order must match the order of the java interfaces
* @param properties
* map of properties; keys must be Strings, values are of type
* object
* @param strategy
* optional string that defines the publish strategy
* @return a ServiceEndpointDescription or null, if an error occurred during
* creation of the ServiceDescription
*/
protected ServiceEndpointDescription publishService(
Collection/* <String> */javaInterfaces,
Collection/* <String> */javaInterfacesAndVersions,
Collection/* <String> */javaInterfacesAndEndpointInterfaces,
Map/* <String, Object> */properties, String strategy,
String endpointID) {
ServiceEndpointDescription svcDescr = new ServiceEndpointDescriptionImpl(
javaInterfaces, javaInterfacesAndVersions,
javaInterfacesAndEndpointInterfaces, properties, endpointID);
storeAndNotify(svcDescr);
return svcDescr;
}
private void storeAndNotify(final ServiceEndpointDescription svcDescr) {
// add it to the available Services
inMemoryCache.put(svcDescr.getEndpointID(), svcDescr);
// inform the listener about the new available service
notifyListenersOnNewServiceDescription(svcDescr);
}
protected void publishService(final ServiceEndpointDescription svcDescr) {
storeAndNotify(svcDescr);
}
/**
* Unpublishes a given service description.
*
* @param serviceDescription
* the service to unpublish
* @throws IllegalArgumentException
* if serviceDescription is null or does not contain at least
* one java interface
*/
protected void unpublishService(
final ServiceEndpointDescription serviceDescription) {
validateServiceDescription(serviceDescription);
log(LogService.LOG_DEBUG,
"unpublish service " + serviceDescription.toString());
inMemoryCache.remove(serviceDescription.getEndpointID());
notifyListenersOnRemovedServiceDescription(serviceDescription);
}
/**
*
*/
protected static synchronized void log(int logLevel, String msg) {
if (logService != null) {
logService.log(logLevel, msg);
}
}
/**
*
*/
protected static synchronized void log(int logLevel, String msg, Exception e) {
if (logService != null) {
logService.log(logLevel, msg, e);
}
}
/**
* @param logger
* the reference to the LogService which get called for logging
*
*/
public static void setLogService(final LogService logger) {
logService = logger;
}
/**
* This method checks if a given ServiceEndpointDescrioption follows the
* minimal requirements.
*
* @param serviceDescription
* the given ServiceEndpointDescription
* @throws IllegalArgumentException
* if serviceDescription is null or does not contain at least
* one java interface
*/
protected void validateServiceDescription(
ServiceEndpointDescription serviceDescription) {
if (serviceDescription == null)
throw new IllegalArgumentException(
"serviceDescription must not be null.");
if (serviceDescription.getProvidedInterfaces() == null) {
throw new IllegalArgumentException(
"Given set of Java interfaces must not be null");
}
String ifName = (String) serviceDescription.getProvidedInterfaces()
.iterator().next();
if (serviceDescription.getProvidedInterfaces() == null
|| serviceDescription.getProvidedInterfaces().size() <= 0
|| ifName == null || ifName.length() <= 0) {
throw new IllegalArgumentException(
"serviceDescription must contain at least one service interface name.");
}
}
/**
* Returns a Map of all registered DiscoveredServiceTracker trackers.
*
* TODO should it be a copy of the map for thread safety? This could lead to
* failures during usage of the copy. But we do not block the Tracker,
* registrations and deregistrations of DSTs.
*
* @return a copied Map of all registered DiscoveredServiceTracker trackers.
*/
protected Map getRegisteredServiceTracker() {
if (discoTrackerCustomizer == null) {
return new HashMap();
}
return new HashMap(discoTrackerCustomizer.getDsTrackers());
}
/**
* This method informs a just registered or modified service tracker if a
* service matches its properties.
*
* TODO: add suppression of informing trackers twice for the same SED
*
* @param tracker
* the just registered or modified DiscoveredServiceTracker
*/
public static void notifyOnAvailableSEDs(
final DiscoveredServiceTracker tracker, final Map matchingCriteria) {
List cachedServices = new ArrayList(inMemoryCache.values());
if (cachedServices != null) {
Collection matchingInterfaces = new ArrayList();
Collection matchingFilters = new ArrayList();
Iterator it = cachedServices.iterator();
while (it.hasNext()) {
ServiceEndpointDescription svcDescr = (ServiceEndpointDescription) it
.next();
matchingInterfaces.clear();
matchingFilters.clear();
if (isTrackerInterestedInSED(svcDescr, matchingCriteria,
matchingInterfaces, matchingFilters)) {
tracker.serviceChanged(new DiscoveredServiceNotificationImpl(
svcDescr, DiscoveredServiceNotification.AVAILABLE,
matchingInterfaces, matchingFilters));
}
}
}
}
/**
*
* @param svcDescr
*/
protected void notifyListenersOnNewServiceDescription(
ServiceEndpointDescription svcDescr) {
Collection matchingInterfaces = new ArrayList();
Collection matchingFilters = new ArrayList();
Map discoveredSTs = getRegisteredServiceTracker();
Iterator it = discoveredSTs.keySet().iterator();
while (it.hasNext()) {
DiscoveredServiceTracker st = (DiscoveredServiceTracker) it.next();
Map trackerProps = (Map) discoveredSTs.get(st);
matchingInterfaces.clear();
matchingFilters.clear();
if (isTrackerInterestedInSED(svcDescr, trackerProps,
matchingInterfaces, matchingFilters)) {
try {
st.serviceChanged(new DiscoveredServiceNotificationImpl(
svcDescr, DiscoveredServiceNotification.AVAILABLE,
matchingInterfaces, matchingFilters));
} catch (Exception e) {
log(LogService.LOG_ERROR,
"Exceptions where thrown while notifying about a new remote service.",
e);
}
}
}
}
/**
* Notifies all DSTTrackers about an unpublished service.
*
* @param svcDescr
* the unpublished ServiceEndpointDescription
*/
protected void notifyListenersOnRemovedServiceDescription(
ServiceEndpointDescription svcDescr) {
Collection matchingInterfaces = new ArrayList();
Collection matchingFilters = new ArrayList();
Map discoveredSTs = getRegisteredServiceTracker();
Iterator it = discoveredSTs.keySet().iterator();
while (it.hasNext()) {
DiscoveredServiceTracker st = (DiscoveredServiceTracker) it.next();
Map trackerProps = (Map) discoveredSTs.get(st);
matchingInterfaces.clear();
matchingFilters.clear();
// inform it if the listener has no Filter set
// or the filter matches the criteria
if (isTrackerInterestedInSED(svcDescr, trackerProps,
matchingInterfaces, matchingFilters)) {
try {
st.serviceChanged(new DiscoveredServiceNotificationImpl(
svcDescr,
DiscoveredServiceNotification.UNAVAILABLE,
matchingInterfaces, matchingFilters));
} catch (Exception e) {
log(LogService.LOG_ERROR,
"Exceptions where thrown while notifying about removal of a remote service.",
e);
}
}
}
}
/**
*
* @param svcDescr
*/
protected void notifyListenersOnModifiedServiceDescription(
ServiceEndpointDescription svcDescr) {
Collection matchingInterfaces = new ArrayList();
Collection matchingFilters = new ArrayList();
Map discoveredSTs = getRegisteredServiceTracker();
Iterator it = discoveredSTs.keySet().iterator();
while (it.hasNext()) {
DiscoveredServiceTracker st = (DiscoveredServiceTracker) it.next();
Map trackerProps = (Map) discoveredSTs.get(st);
// inform it if the listener has no Filter set
// or the filter matches the criteria
matchingInterfaces.clear();
matchingFilters.clear();
if (isTrackerInterestedInSED(svcDescr, trackerProps,
matchingInterfaces, matchingFilters)) {
try {
st.serviceChanged(new DiscoveredServiceNotificationImpl(
svcDescr, DiscoveredServiceNotification.MODIFIED,
matchingInterfaces, matchingFilters));
} catch (Exception e) {
log(LogService.LOG_ERROR,
"Exceptions where thrown while notifying about modification of a remote service.",
e);
}
}
}
}
/**
* Compares the properties of a registered DiscoveredServiceTracker with the
* SED properties. IF they match, it returns true.
*
* @param svcDescr
* @param trackerProperties
* @param matchingInterfaces
* (an out-argument) a collection which will contain all
* tracker's interface criteria matching with given
* ServiceEndpointDescription object
* @param matchingFilters
* (an out-argument) a collection which will contain all
* tracker's filter criteria matching with given
* ServiceEndpointDescription object
* @return true if the service tracker properties match the SEDs properties,
* else false
*/
public static boolean isTrackerInterestedInSED(
ServiceEndpointDescription svcDescr,
Map/* String, Object */trackerProperties,
Collection matchingInterfaces, Collection matchingFilters) {
Collection interfaceCriteria = (Collection) trackerProperties
.get(DiscoveredServiceTracker.INTERFACE_MATCH_CRITERIA);
Collection filter = (Collection) trackerProperties
.get(DiscoveredServiceTracker.FILTER_MATCH_CRITERIA);
boolean notify = false;
if (interfaceCriteria == null && filter == null) {
notify = true;
} else {
// if interface-criteria are defined on tracker
if (interfaceCriteria != null && !interfaceCriteria.isEmpty()) {
// then check whether tracker's interface-list contains one of
// SED's interfaces
Collection svcInterfaces = svcDescr.getProvidedInterfaces();
if (svcInterfaces == null) {
throw new RuntimeException("no interfaces provided");
}
Collection intersectionResult = new HashSet(interfaceCriteria);
intersectionResult.retainAll(svcInterfaces);
if (intersectionResult.size() > 0) {
notify = true;
if (matchingInterfaces != null) {
matchingInterfaces.addAll(intersectionResult);
}
}
}
// if filter-criteria are defined on tracker
if (filter != null && !filter.isEmpty()) {
// check whether one filter of tracker's filter-list matches to
// SED's properties
Iterator it = filter.iterator();
while (it.hasNext()) {
String currentFilter = (String) it.next();
try {
Filter f = context.createFilter(currentFilter);
if (f.match(new Hashtable(svcDescr.getProperties()))) {
notify = true;
if (matchingFilters != null) {
matchingFilters.add(currentFilter);
}
}
} catch (InvalidSyntaxException e) {
throw new RuntimeException(e.getMessage());
} catch (IllegalStateException isex) {
// TODO: check whether this catch block is needed.
isex.printStackTrace();
// ignore it
}
}
}
}
return notify;
}
/**
* Returns the list of known services without the locally published ones.
*
* @return a list of all remote services
*/
public Map getCachedServices() {
return new HashMap(inMemoryCache);
}
}