ソースを参照

Added support for listening to service changes.

Sascha Zelzer 14 年 前
コミット
0d69041c66

+ 27 - 0
Libs/PluginFramework/ctkPluginContext.cpp

@@ -120,10 +120,19 @@ QObject* ctkPluginContext::getService(ctkServiceReference reference)
   return reference.d_func()->getService(d->plugin->q_func());
 }
 
+ bool ctkPluginContext::ungetService(const ctkServiceReference& reference)
+ {
+   Q_D(ctkPluginContext);
+   d->isPluginContextValid();
+   ctkServiceReference ref = reference;
+   return ref.d_func()->ungetService(d->plugin->q_func(), true);
+ }
+
 bool ctkPluginContext::connectPluginListener(const QObject* receiver, const char* method,
                                              Qt::ConnectionType type)
 {
   Q_D(ctkPluginContext);
+  d->isPluginContextValid();
   // TODO check permissions for a direct connection
   return receiver->connect(&(d->plugin->fwCtx->listeners), SIGNAL(pluginChanged(ctkPluginEvent)), method, type);
 }
@@ -131,5 +140,23 @@ bool ctkPluginContext::connectPluginListener(const QObject* receiver, const char
 bool ctkPluginContext::connectFrameworkListener(const QObject* receiver, const char* method, Qt::ConnectionType type)
 {
   Q_D(ctkPluginContext);
+  d->isPluginContextValid();
+  // TODO check permissions for a direct connection
   return receiver->connect(&(d->plugin->fwCtx->listeners), SIGNAL(frameworkEvent(ctkPluginFrameworkEvent)), method, type);
 }
+
+void ctkPluginContext::connectServiceListener(QObject* receiver, const char* slot,
+                                             const QString& filter)
+{
+  Q_D(ctkPluginContext);
+  d->isPluginContextValid();
+  d->plugin->fwCtx->listeners.addServiceSlot(getPlugin(), receiver, slot, filter);
+}
+
+void ctkPluginContext::disconnectServiceListener(QObject* receiver,
+                                                const char* slot)
+{
+  Q_D(ctkPluginContext);
+  d->isPluginContextValid();
+  d->plugin->fwCtx->listeners.removeServiceSlot(getPlugin(), receiver, slot);
+}

+ 129 - 2
Libs/PluginFramework/ctkPluginContext.h

@@ -350,6 +350,47 @@
     QObject* getService(ctkServiceReference reference);
 
     /**
+     * Releases the service object referenced by the specified
+     * <code>ctkServiceReference</code> object. If the context plugin's use count
+     * for the service is zero, this method returns <code>false</code>.
+     * Otherwise, the context plugins's use count for the service is decremented
+     * by one.
+     *
+     * <p>
+     * The service's service object should no longer be used and all references
+     * to it should be destroyed when a bundle's use count for the service drops
+     * to zero.
+     *
+     * <p>
+     * The following steps are required to unget the service object:
+     * <ol>
+     * <li>If the context plugin's use count for the service is zero or the
+     * service has been unregistered, <code>false</code> is returned.
+     * <li>The context plugin's use count for this service is decremented by
+     * one.
+     * <li>If the context plugin's use count for the service is currently zero
+     * and the service was registered with a <code>ctkServiceFactory</code> object,
+     * the
+     * {@link ctkServiceFactory#ungetService(ctkPlugin*, ctkServiceRegistration*, QObject*)}
+     * method is called to release the service object for the context plugin.
+     * <li><code>true</code> is returned.
+     * </ol>
+     *
+     * @param reference A reference to the service to be released.
+     * @return <code>false</code> if the context plugin's use count for the
+     *         service is zero or if the service has been unregistered;
+     *         <code>true</code> otherwise.
+     * @throws std::logic_error If this ctkPluginContext is no
+     *         longer valid.
+     * @throws std::invalid_argument If the specified
+     *         <code>ctkServiceReference</code> was not created by the same
+     *         framework instance as this <code>ctkPluginContext</code>.
+     * @see #getService
+     * @see ctkServiceFactory
+     */
+    bool ungetService(const ctkServiceReference& reference);
+
+    /**
      * Installs a plugin from the specified <code>QIODevice</code> object.
      *
      * <p>
@@ -409,10 +450,96 @@
      */
     ctkPlugin* installPlugin(const QUrl& location, QIODevice* in = 0);
 
+    /**
+     * Connects the specified <code>slot</code> to the context
+     * plugins's signal which is emitted when a plugin has
+     * a lifecycle state change. The signature of the slot
+     * must be "slotName(ctkPluginEvent)".
+     *
+     * @param receiver The object to connect to.
+     * @param slot The slot to be connected.
+     * @param type The Qt connection type.
+     * @throws std::logic_error If this ctkPluginContext is no
+     *         longer valid.
+     * @see ctkPluginEvent
+     * @see ctkEventBus
+     */
+    bool connectPluginListener(const QObject* receiver, const char* slot, Qt::ConnectionType type = Qt::QueuedConnection);
+
+    /**
+     * Connects the specified <code>slot</code> to the context
+     * plugin's signal which emits general Framework events. The signature
+     * of the slot must be "slotName(ctkPluginFrameworkEvent)".
+     *
+     * @param receiver The object to connect to.
+     * @param slot The slot to be connected.
+     * @param type The Qt connection type.
+     * @throws std::logic_error If this ctkPluginContext is no
+     *         longer valid.
+     * @see ctkPluginFrameworkEvent
+     * @see ctkEventBus
+     */
+    bool connectFrameworkListener(const QObject* receiver, const char* slot, Qt::ConnectionType type = Qt::QueuedConnection);
 
-    bool connectPluginListener(const QObject* receiver, const char* method, Qt::ConnectionType type = Qt::QueuedConnection);
+    /**
+     * Connects the specified <code>slot</code> with the
+     * specified <code>filter</code> to the context plugins's signal emitting
+     * service events when a service has a lifecycle state change. The signature
+     * of the slot must be "slotName(const ctkServiceEvent&)", but only the name
+     * of the slot must be provided as the argument.
+     * See {@link ctkLDAPSearchFilter} for a description of
+     * the filter syntax.
+     *
+     * <p>
+     * If the context plugin's list of listeners already contains a the same
+     * slot for the given receiver, then this
+     * method replaces that slot's filter (which may be <code>null</code>)
+     * with the specified one (which may be <code>null</code>).
+     *
+     * <p>
+     * The slot is called if the filter criteria is met. To filter based
+     * upon the class of the service, the filter should reference the
+     * {@link ctkPluginConstants#OBJECTCLASS} property. If <code>filter</code> is
+     * <code>null</code>, all services are considered to match the filter.
+     *
+     * <p>
+     * When using a <code>filter</code>, it is possible that the
+     * <code>ctkServiceEvent</code>s for the complete lifecycle of a service
+     * will not be delivered to the slot. For example, if the
+     * <code>filter</code> only matches when the property <code>x</code> has
+     * the value <code>1</code>, the listener will not be called if the
+     * service is registered with the property <code>x</code> not set to the
+     * value <code>1</code>. Subsequently, when the service is modified
+     * setting property <code>x</code> to the value <code>1</code>, the
+     * filter will match and the slot will be called with a
+     * <code>ServiceEvent</code> of type <code>MODIFIED</code>. Thus, the
+     * slot will not be called with a <code>ServiceEvent</code> of type
+     * <code>REGISTERED</code>.
+     *
+     * @param receiver The object to connect to.
+     * @param slot The name of the slot to be connected.
+     * @param filter The filter criteria.
+     * @param type The Qt connection type.
+     * @throws std::invalid_argument If <code>filter</code> contains an
+     *         invalid filter string that cannot be parsed.
+     * @throws std::logic_error If this ctkPluginContext is no
+     *         longer valid.
+     * @see ctkServiceEvent
+     * @see disconnectServiceListener()
+     * @see ctkEventBus
+     */
+    void connectServiceListener(QObject* receiver, const char* slot,
+                                const QString& filter = QString());
 
-    bool connectFrameworkListener(const QObject* receiver, const char* method, Qt::ConnectionType type = Qt::QueuedConnection);
+    /**
+     * Disconnects a slot which has been previously connected
+     * with a call to connectServiceListener().
+     *
+     * @param receiver The object containing the slot.
+     * @param slot The slot to be disconnected.
+     * @see connectServiceListener()
+     */
+    void disconnectServiceListener(QObject* receiver, const char* slot);
 
   protected:
 

+ 244 - 6
Libs/PluginFramework/ctkPluginFrameworkListeners.cpp

@@ -21,19 +21,257 @@
 
 #include "ctkPluginFrameworkListeners_p.h"
 
+#include "ctkPluginConstants.h"
+#include "ctkLDAPExpr_p.h"
+#include "ctkServiceReferencePrivate.h"
 
-  void ctkPluginFrameworkListeners::frameworkError(ctkPlugin* p, const std::exception& e)
+#include <QStringListIterator>
+#include <QDebug>
+
+const int ctkPluginFrameworkListeners::OBJECTCLASS_IX = 0;
+const int ctkPluginFrameworkListeners::SERVICE_ID_IX = 1;
+const int ctkPluginFrameworkListeners::SERVICE_PID_IX = 2;
+
+ctkPluginFrameworkListeners::ctkPluginFrameworkListeners()
+{
+  hashedServiceKeys << ctkPluginConstants::OBJECTCLASS.toLower()
+      << ctkPluginConstants::SERVICE_ID.toLower()
+      << ctkPluginConstants::SERVICE_PID.toLower();
+
+  for (int i = 0; i < hashedServiceKeys.size(); ++i)
+  {
+    cache.push_back(QHash<QString, QList<ctkServiceSlotEntry> >());
+  }
+}
+
+void ctkPluginFrameworkListeners::addServiceSlot(
+    ctkPlugin* plugin, QObject* receiver,
+    const char* slot, const QString& filter)
+{
+  QMutexLocker lock(&mutex); Q_UNUSED(lock)
+  ctkServiceSlotEntry sse(plugin, receiver, slot, filter);
+  if (serviceSet.contains(sse))
+  {
+    removeServiceSlot(plugin, receiver, slot);
+  }
+  serviceSet.insert(sse);
+  checkSimple(sse);
+}
+
+void ctkPluginFrameworkListeners::removeServiceSlot(ctkPlugin* plugin,
+                                                    QObject* receiver,
+                                                    const char* slot)
+{
+  QMutexLocker lock(&mutex); Q_UNUSED(lock)
+
+  ctkServiceSlotEntry entryToRemove(plugin, receiver, slot);
+  QMutableSetIterator<ctkServiceSlotEntry> it(serviceSet);
+  while (it.hasNext())
+  {
+    ctkServiceSlotEntry currentEntry = it.next();
+    if (currentEntry == entryToRemove)
+    {
+      currentEntry.setRemoved(true);
+      //listeners.framework.hooks.handleServiceListenerUnreg(sle);
+      removeFromCache(currentEntry);
+      it.remove();
+      break;
+    }
+  }
+}
+
+QSet<ctkServiceSlotEntry> ctkPluginFrameworkListeners::getMatchingServiceSlots(
+    const ctkServiceReference& sr)
+{
+  QMutexLocker lock(&mutex); Q_UNUSED(lock);
+
+  QSet<ctkServiceSlotEntry> set;
+  // Check complicated or empty listener filters
+  int n = 0;
+  foreach (ctkServiceSlotEntry sse, complicatedListeners)
+  {
+    ++n;
+    if (sse.getLDAPExpr().isNull() || sse.getLDAPExpr().evaluate(sr.d_func()->getProperties(), false))
+    {
+      set.insert(sse);
+    }
+  }
+
+  //if (listeners.framework.debug.ldap)
+  {
+    qDebug() << "Added" << set.size() << "out of" << n
+      << "listeners with complicated filters";
+  }
+
+  // Check the cache
+  QStringList c = sr.getProperty(ctkPluginConstants::OBJECTCLASS).toStringList();
+  foreach (QString objClass, c)
+  {
+    addToSet(set, OBJECTCLASS_IX, objClass);
+  }
+
+  bool ok = false;
+  qlonglong service_id = sr.getProperty(ctkPluginConstants::SERVICE_ID).toLongLong(&ok);
+  if (ok)
+  {
+    addToSet(set, SERVICE_ID_IX, QString::number(service_id));
+  }
+
+  QStringList service_pids = sr.getProperty(ctkPluginConstants::SERVICE_PID).toStringList();
+  foreach (QString service_pid, service_pids)
+  {
+    addToSet(set, SERVICE_PID_IX, service_pid);
+  }
+
+  return set;
+}
+
+void ctkPluginFrameworkListeners::frameworkError(ctkPlugin* p, const std::exception& e)
+{
+  emit frameworkEvent(ctkPluginFrameworkEvent(ctkPluginFrameworkEvent::ERROR, p, e));
+}
+
+void ctkPluginFrameworkListeners::emitFrameworkEvent(const ctkPluginFrameworkEvent& event)
+{
+  emit frameworkEvent(event);
+}
+
+void ctkPluginFrameworkListeners::emitPluginChanged(const ctkPluginEvent& event)
+{
+  emit pluginChanged(event);
+}
+
+void ctkPluginFrameworkListeners::serviceChanged(
+    const QSet<ctkServiceSlotEntry>& receivers,
+    const ctkServiceEvent& evt)
+{
+  QSet<ctkServiceSlotEntry> matchBefore;
+  serviceChanged(receivers, evt, matchBefore);
+}
+
+void ctkPluginFrameworkListeners::serviceChanged(
+    const QSet<ctkServiceSlotEntry>& receivers,
+    const ctkServiceEvent& evt,
+    QSet<ctkServiceSlotEntry>& matchBefore)
+{
+  ctkServiceReference sr = evt.getServiceReference();
+  //QStringList classes = sr.getProperty(ctkPluginConstants::OBJECTCLASS).toStringList();
+  int n = 0;
+
+  //framework.hooks.filterServiceEventReceivers(evt, receivers);
+
+  foreach (ctkServiceSlotEntry l, receivers)
+  {
+    if (!matchBefore.isEmpty())
+    {
+      matchBefore.remove(l);
+    }
+
+    // TODO permission checks
+    //if (l.bundle.hasPermission(new ServicePermission(sr, ServicePermission.GET))) {
+    //foreach (QString clazz, classes)
+    //{
+    try
+    {
+      ++n;
+      l.invokeSlot(evt);
+    }
+    catch (const std::exception& pe)
+    {
+      frameworkError(l.getPlugin(), pe);
+    }
+    //break;
+    //}
+    //}
+  }
+
+  //if (framework.debug.ldap)
   {
-    emit frameworkEvent(ctkPluginFrameworkEvent(ctkPluginFrameworkEvent::ERROR, p, e));
+    qDebug() << "Notified" << n << " listeners";
   }
+}
 
-  void ctkPluginFrameworkListeners::emitFrameworkEvent(const ctkPluginFrameworkEvent& event)
+void ctkPluginFrameworkListeners::removeFromCache(const ctkServiceSlotEntry& sse)
+{
+  if (!sse.getLocalCache().isEmpty())
   {
-    emit frameworkEvent(event);
+    for (int i = 0; i < hashedServiceKeys.size(); ++i)
+    {
+      QHash<QString, QList<ctkServiceSlotEntry> >& keymap = cache[i];
+      QStringList& l = sse.getLocalCache()[i];
+      QStringListIterator it(l);
+      while (it.hasNext())
+      {
+        QString value = it.next();
+        QList<ctkServiceSlotEntry>& sses = keymap[value];
+        sses.removeAll(sse);
+        if (sses.isEmpty())
+        {
+          keymap.remove(value);
+        }
+      }
+    }
   }
+  else
+  {
+    complicatedListeners.removeAll(sse);
+  }
+}
 
-  void ctkPluginFrameworkListeners::emitPluginChanged(const ctkPluginEvent& event)
+void ctkPluginFrameworkListeners::checkSimple(const ctkServiceSlotEntry& sse)
+{
+  if (sse.getLDAPExpr().isNull()) // || listeners.nocacheldap) {
   {
-    emit pluginChanged(event);
+    complicatedListeners.push_back(sse);
+  }
+  else
+  {
+    ctkLDAPExpr::LocalCache local_cache;
+    if (sse.getLDAPExpr().isSimple(hashedServiceKeys, local_cache, false))
+    {
+      sse.getLocalCache() = local_cache;
+      for (int i = 0; i < hashedServiceKeys.size(); ++i)
+      {
+        QStringListIterator it(local_cache[i]);
+        while (it.hasNext())
+        {
+          QString value = it.next();
+          QList<ctkServiceSlotEntry>& sses = cache[i][value];
+          sses.push_back(sse);
+        }
+      }
+    }
+    else
+    {
+      //if (listeners.framework.debug.ldap)
+      {
+        qDebug() << "## DEBUG: Too complicated filter:" << sse.getFilter();
+      }
+      complicatedListeners.push_back(sse);
+    }
+  }
+}
 
+void ctkPluginFrameworkListeners::addToSet(QSet<ctkServiceSlotEntry>& set,
+                                           int cache_ix, const QString& val)
+{
+  QList<ctkServiceSlotEntry>& l = cache[cache_ix][val];
+  if (!l.isEmpty())
+  {
+    //if (listeners.framework.debug.ldap)
+    {
+      qDebug() << hashedServiceKeys[cache_ix] << "matches" << l.size();
+    }
+    foreach (ctkServiceSlotEntry entry, l)
+    {
+      set.insert(entry);
+    }
+  }
+  else
+  {
+    //if (listeners.framework.debug.ldap)
+    {
+      qDebug() << hashedServiceKeys[cache_ix] << "matches none";
+    }
+  }
 }

+ 88 - 3
Libs/PluginFramework/ctkPluginFrameworkListeners_p.h

@@ -23,10 +23,15 @@
 #define CTKPLUGINFRAMEWORKLISTENERS_H
 
 #include <QObject>
+#include <QHash>
+#include <QSet>
+#include <QMutex>
 
-#include <ctkPluginEvent.h>
-#include <ctkPluginFrameworkEvent.h>
-
+#include "ctkPluginEvent.h"
+#include "ctkPluginFrameworkEvent.h"
+#include "ctkServiceReference.h"
+#include "ctkServiceSlotEntry_p.h"
+#include "ctkServiceEvent.h"
 
 class ctkPluginFrameworkListeners : public QObject
 {
@@ -35,8 +40,55 @@ class ctkPluginFrameworkListeners : public QObject
 
 public:
 
+  ctkPluginFrameworkListeners();
+
+  /**
+   * Add a slot receiving service envents with filter to the current framework.
+   * If no filter is wanted, call with a null filter.
+   *
+   * @param plugin Who wants to add the slot.
+   * @param listener Object to add.
+   * @param filter LDAP String used for filtering event before calling listener.
+   */
+  void addServiceSlot(ctkPlugin* plugin, QObject* receiver,
+                      const char* slot, const QString& filter);
+
+  /**
+   * Remove a slot connected to service events.
+   *
+   * @param plugin The plugin removing this listener.
+   * @param receiver The receiver containing the slot.
+   * @param slot The slot in the receiver.
+   */
+  void removeServiceSlot(ctkPlugin* plugin, QObject* receiver,
+                         const char* slot);
+
+  /**
+   * Gets the slots interested in modifications of the service reference
+   *
+   * @param The reference related to the event describing the service modification.
+   * @return A set of listeners to notify.
+   */
+  QSet<ctkServiceSlotEntry> getMatchingServiceSlots(const ctkServiceReference& sr);
+
+  /**
+   * Convenience method for throwing framework error event.
+   *
+   * @param p Plugin which caused the error.
+   * @param e The exception.
+   */
   void frameworkError(ctkPlugin* p, const std::exception& e);
 
+  /**
+   * Receive notification that a service has had a change occur in its lifecycle.
+   */
+  void serviceChanged(const QSet<ctkServiceSlotEntry>& receivers,
+                      const ctkServiceEvent& evt,
+                      QSet<ctkServiceSlotEntry>& matchBefore);
+
+  void serviceChanged(const QSet<ctkServiceSlotEntry>& receivers,
+                      const ctkServiceEvent& evt);
+
   void emitPluginChanged(const ctkPluginEvent& event);
 
   void emitFrameworkEvent(const ctkPluginFrameworkEvent& event);
@@ -47,6 +99,39 @@ signals:
 
   void frameworkEvent(const ctkPluginFrameworkEvent& event);
 
+private:
+
+  QMutex mutex;
+
+  QList<QString> hashedServiceKeys;
+  static const int OBJECTCLASS_IX; // = 0;
+  static const int SERVICE_ID_IX; // = 1;
+  static const int SERVICE_PID_IX; // = 2;
+
+  // Service listeners with complicated or empty filters
+  QList<ctkServiceSlotEntry> complicatedListeners;
+
+  // Service listeners with "simple" filters are cached
+  QList<QHash<QString, QList<ctkServiceSlotEntry> > > cache;
+
+  QSet<ctkServiceSlotEntry> serviceSet;
+
+  /**
+   * Remove all references to a service slot from the service listener
+   * cache.
+   */
+  void removeFromCache(const ctkServiceSlotEntry& sse);
+
+  /**
+   * Checks if the specified service slot's filter is simple enough
+   * to cache.
+   */
+  void checkSimple(const ctkServiceSlotEntry& sse);
+
+  /**
+   * Add all members of the specified list to the specified set.
+   */
+  void addToSet(QSet<ctkServiceSlotEntry>& set, int cache_ix, const QString& val);
 };
 
 

+ 158 - 0
Libs/PluginFramework/ctkServiceSlotEntry.cpp

@@ -0,0 +1,158 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed 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.
+
+=============================================================================*/
+
+
+#include "ctkServiceSlotEntry_p.h"
+
+#include "ctkLDAPExpr_p.h"
+#include "ctkPlugin.h"
+
+#include <QSharedData>
+
+class ctkServiceSlotEntryData : public QSharedData
+{
+public:
+
+  ctkServiceSlotEntryData(ctkPlugin* p, QObject* receiver,
+                          const char* slot)
+    : plugin(p), receiver(receiver),
+      slot(slot), removed(false)
+  {
+
+  }
+
+  /**
+   * The elements of "simple" filters are cached, for easy lookup.
+   *
+   * The grammar for simple filters is as follows:
+   *
+   * <pre>
+   * Simple = '(' attr '=' value ')'
+   *        | '(' '|' Simple+ ')'
+   * </pre>
+   * where <code>attr</code> is one of {@link Constants#OBJECTCLASS},
+   * {@link Constants#SERVICE_ID} or {@link Constants#SERVICE_PID}, and
+   * <code>value</code> must not contain a wildcard character.
+   * <p>
+   * The index of the vector determines which key the cache is for
+   * (see {@link ServiceListenerState#hashedKeys}). For each key, there is
+   * a vector pointing out the values which are accepted by this
+   * ServiceListenerEntry's filter. This cache is maintained to make
+   * it easy to remove this service listener.
+   */
+  ctkLDAPExpr::LocalCache local_cache;
+
+  ctkLDAPExpr ldap;
+  ctkPlugin* plugin;
+  QObject* receiver;
+  const char* slot;
+  bool removed;
+
+};
+
+ctkServiceSlotEntry::ctkServiceSlotEntry(
+    ctkPlugin* p, QObject* receiver, const char* slot, const QString& filter)
+  : d(new ctkServiceSlotEntryData(p, receiver, slot))
+{
+  if (!filter.isNull())
+  {
+    d->ldap = ctkLDAPExpr(filter);
+  }
+}
+
+ctkServiceSlotEntry::ctkServiceSlotEntry(const ctkServiceSlotEntry& other)
+  : d(other.d)
+{
+
+}
+
+ctkServiceSlotEntry::ctkServiceSlotEntry()
+  : d(new ctkServiceSlotEntryData(0, 0, 0))
+{
+
+}
+
+ctkServiceSlotEntry& ctkServiceSlotEntry::operator=(const ctkServiceSlotEntry& other)
+{
+  d = other.d;
+  return *this;
+}
+
+ctkServiceSlotEntry::~ctkServiceSlotEntry()
+{
+
+}
+
+bool ctkServiceSlotEntry::operator==(const ctkServiceSlotEntry& other) const
+{
+  return d->plugin == other.d->plugin &&
+         d->receiver == other.d->receiver &&
+         d->slot == other.d->slot;
+}
+
+void ctkServiceSlotEntry::invokeSlot(const ctkServiceEvent &event)
+{
+  if (!QMetaObject::invokeMethod(d->receiver, d->slot,
+                                 Qt::DirectConnection,
+                                 Q_ARG(ctkServiceEvent, event)))
+  {
+    throw std::runtime_error(
+                QString("Slot %1 of %2 could not be invoked. A call to "
+                        "ctkPluginContext::connectServiceListener() must only contain "
+                        "the slot name, not the whole signature.").
+                arg(d->slot).arg(d->receiver->metaObject()->className()).toStdString());
+  }
+}
+
+void ctkServiceSlotEntry::setRemoved(bool removed)
+{
+  d->removed = removed;
+}
+
+bool ctkServiceSlotEntry::isRemoved() const
+{
+  return d->removed;
+}
+
+ctkPlugin* ctkServiceSlotEntry::getPlugin() const
+{
+  return d->plugin;
+}
+
+ctkLDAPExpr ctkServiceSlotEntry::getLDAPExpr() const
+{
+  return d->ldap;
+}
+
+QString ctkServiceSlotEntry::getFilter() const
+{
+  return d->ldap.isNull() ? QString() : d->ldap.toString();
+}
+
+ctkLDAPExpr::LocalCache& ctkServiceSlotEntry::getLocalCache() const
+{
+  return d->local_cache;
+}
+
+uint qHash(const ctkServiceSlotEntry& serviceSlot)
+{
+  return qHash(serviceSlot.getPlugin());
+}

+ 87 - 0
Libs/PluginFramework/ctkServiceSlotEntry_p.h

@@ -0,0 +1,87 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed 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.
+
+=============================================================================*/
+
+
+#ifndef CTKSERVICESLOTENTRY_P_H
+#define CTKSERVICESLOTENTRY_P_H
+
+
+#include <QHash>
+#include <QList>
+#include <QString>
+#include <QStringList>
+#include <QExplicitlySharedDataPointer>
+
+#include "ctkServiceEvent.h"
+#include "ctkLDAPExpr_p.h"
+
+class ctkPlugin;
+class ctkServiceSlotEntryData;
+
+class QObject;
+
+
+/**
+ * Data structure for saving information about slots registered for
+ * receiving service lifecycle events.
+ */
+class ctkServiceSlotEntry
+{
+
+public:
+
+  ctkServiceSlotEntry(ctkPlugin* p, QObject* receiver, const char* slot,
+                      const QString& filter = QString());
+
+  ctkServiceSlotEntry(const ctkServiceSlotEntry& other);
+
+  // default constructor for use in QSet
+  ctkServiceSlotEntry();
+  ~ctkServiceSlotEntry();
+
+  // assignment operator for use in QSet
+  ctkServiceSlotEntry& operator=(const ctkServiceSlotEntry& other);
+
+  bool operator==(const ctkServiceSlotEntry& other) const;
+
+  void invokeSlot(const ctkServiceEvent& event);
+
+  void setRemoved(bool removed);
+
+  bool isRemoved() const;
+
+  ctkPlugin* getPlugin() const;
+
+  ctkLDAPExpr getLDAPExpr() const;
+
+  QString getFilter() const;
+
+  ctkLDAPExpr::LocalCache& getLocalCache() const;
+
+private:
+
+  QExplicitlySharedDataPointer<ctkServiceSlotEntryData> d;
+
+};
+
+uint qHash(const ctkServiceSlotEntry& serviceSlot);
+
+#endif // CTKSERVICESLOTENTRY_P_H