浏览代码

Support for tracking services.

The helper class ctkServiceTracker can now be used to track lifecycle
changes of services.
Sascha Zelzer 14 年之前
父节点
当前提交
bf51a6c650

+ 6 - 0
Libs/PluginFramework/CMakeLists.txt

@@ -46,6 +46,7 @@ SET(KIT_include_directories
 SET(KIT_SRCS
   ctkLDAPSearchFilter.cpp
   ctkPlugin.cpp
+  #ctkPluginAbstractTracked.cpp
   ctkPluginArchive.cpp
   ctkPluginConstants.cpp
   ctkPluginContext.cpp
@@ -74,8 +75,12 @@ SET(KIT_SRCS
   ctkServiceReferencePrivate.cpp
   ctkServiceRegistration.cpp
   ctkServiceRegistrationPrivate.cpp
+  ctkServiceTracker.cpp
+  ctkServiceTrackerCustomizer.h
+  ctkServiceTrackerPrivate.cpp
   ctkServices.cpp
   ctkServiceSlotEntry.cpp
+  ctkTrackedService.cpp
   ctkPluginStorage.cpp
   ctkVersion.cpp
   ctkVersionRange.cpp
@@ -91,6 +96,7 @@ SET(KIT_SRCS
 SET(KIT_MOC_SRCS
   ctkPluginFrameworkListeners_p.h
   ctkServiceFactory.h
+  ctkTrackedService_p.h
 )
 
 # UI files

+ 343 - 0
Libs/PluginFramework/ctkPluginAbstractTracked.cpp

@@ -0,0 +1,343 @@
+/*=============================================================================
+
+  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 "ctkPluginAbstractTracked_p.h"
+
+#include <QDebug>
+
+template<class Item, class Related>
+const bool ctkPluginAbstractTracked<Item,Related>::DEBUG	= true;
+
+template<class Item, class Related>
+ctkPluginAbstractTracked<Item,Related>::ctkPluginAbstractTracked()
+{
+  closed = false;
+}
+
+template<class Item, class Related>
+ctkPluginAbstractTracked<Item,Related>::~ctkPluginAbstractTracked()
+{
+
+}
+
+template<class Item, class Related>
+bool ctkPluginAbstractTracked<Item,Related>::wait(unsigned long timeout)
+{
+  return waitCond.wait(this, timeout);
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::wakeAll()
+{
+  waitCond.wakeAll();
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::setInitial(const QList<Item>& list)
+{
+  foreach (Item item, list)
+  {
+    if (DEBUG)
+    {
+      qDebug() << "ctkPluginAbstractTracked::setInitial:" << item;
+    }
+    initial.push_back(item);
+  }
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::setInitial(const QList<Item*>& list)
+{
+  foreach (Item* item, list)
+  {
+    if (item == 0)
+    {
+      continue;
+    }
+    if (DEBUG)
+    {
+      qDebug() << "ctkPluginAbstractTracked::setInitial:" << item;
+    }
+    initial.push_back(item);
+  }
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::trackInitial()
+{
+  while (true)
+  {
+    Item item(0);
+    {
+      QMutexLocker lock(this);
+      if (closed || (initial.size() == 0))
+      {
+        /*
+         * if there are no more initial items
+         */
+        return; /* we are done */
+      }
+      /*
+       * move the first item from the initial list to the adding list
+       * within this synchronized block.
+       */
+      item = initial.takeFirst();
+      if (tracked.value(item) != 0)
+      {
+        /* if we are already tracking this item */
+        if (DEBUG)
+        {
+          qDebug() << "ctkPluginAbstractTracked::trackInitial[already tracked]: " << item;
+        }
+        continue; /* skip this item */
+      }
+      if (adding.contains(item))
+      {
+        /*
+         * if this item is already in the process of being added.
+         */
+        if (DEBUG)
+        {
+          qDebug() << "ctkPluginAbstractTracked::trackInitial[already adding]: " << item;
+        }
+        continue; /* skip this item */
+      }
+      adding.push_back(item);
+    }
+    if (DEBUG)
+    {
+      qDebug() << "ctkPluginAbstractTracked::trackInitial: " << item;
+    }
+    trackAdding(item, Related());
+    /*
+     * Begin tracking it. We call trackAdding
+     * since we have already put the item in the
+     * adding list.
+     */
+  }
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::close()
+{
+  closed = true;
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::track(Item item, Related related)
+{
+  QObject* object = 0;
+  {
+    QMutexLocker lock(this);
+    if (closed)
+    {
+      return;
+    }
+    object = tracked.value(item);
+    if (object == 0)
+    { /* we are not tracking the item */
+      if (adding.contains(item))
+      {
+        /* if this item is already in the process of being added. */
+        if (DEBUG)
+        {
+          qDebug() << "ctkPluginAbstractTracked::track[already adding]: " << item;
+        }
+        return;
+      }
+      adding.push_back(item); /* mark this item is being added */
+    }
+    else
+    { /* we are currently tracking this item */
+      if (DEBUG)
+      {
+        qDebug() << "ctkPluginAbstractTracked::track[modified]: " << item;
+      }
+      modified(); /* increment modification count */
+    }
+  }
+
+  if (object == 0)
+  { /* we are not tracking the item */
+    trackAdding(item, related);
+  }
+  else
+  {
+    /* Call customizer outside of synchronized region */
+    customizerModified(item, related, object);
+    /*
+     * If the customizer throws an unchecked exception, it is safe to
+     * let it propagate
+     */
+  }
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::untrack(Item item, Related related)
+{
+  QObject* object = 0;
+  {
+    QMutexLocker lock(this);
+    if (initial.removeOne(item))
+    { /* if this item is already in the list
+       * of initial references to process
+       */
+      if (DEBUG)
+      {
+        qDebug() << "ctkPluginAbstractTracked::untrack[removed from initial]: " << item;
+      }
+      return; /* we have removed it from the list and it will not be
+               * processed
+               */
+    }
+
+    if (adding.removeOne(item))
+    { /* if the item is in the process of
+       * being added
+       */
+      if (DEBUG)
+      {
+        qDebug() << "ctkPluginAbstractTracked::untrack[being added]: " << item;
+      }
+      return; /*
+           * in case the item is untracked while in the process of
+           * adding
+           */
+    }
+    object = tracked.take(item); /*
+                     * must remove from tracker before
+                     * calling customizer callback
+                     */
+    if (object == 0)
+    { /* are we actually tracking the item */
+      return;
+    }
+    modified(); /* increment modification count */
+  }
+  if (DEBUG)
+  {
+    qDebug() << "ctkPluginAbstractTracked::untrack[removed]: " << item;
+  }
+  /* Call customizer outside of synchronized region */
+  customizerRemoved(item, related, object);
+  /*
+   * If the customizer throws an unchecked exception, it is safe to let it
+   * propagate
+   */
+}
+
+template<class Item, class Related>
+int ctkPluginAbstractTracked<Item,Related>::size() const
+{
+  return tracked.size();
+}
+
+template<class Item, class Related>
+QObject* ctkPluginAbstractTracked<Item,Related>::getCustomizedObject(Item item) const
+{
+  return tracked.value(item);
+}
+
+template<class Item, class Related>
+QList<Item> ctkPluginAbstractTracked<Item,Related>::getTracked() const
+{
+  return tracked.keys();
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::modified()
+{
+  trackingCount.ref();
+}
+
+template<class Item, class Related>
+int ctkPluginAbstractTracked<Item,Related>::getTrackingCount() const
+{
+  return trackingCount;
+}
+
+template<class Item, class Related>
+bool ctkPluginAbstractTracked<Item,Related>::customizerAddingFinal(Item item, QObject* custom)
+{
+  QMutexLocker lock(this);
+  if (adding.removeOne(item) && !closed)
+  {
+    /*
+     * if the item was not untracked during the customizer
+     * callback
+     */
+    if (custom != 0)
+    {
+      tracked.insert(item, custom);
+      modified(); /* increment modification count */
+      waitCond.wakeAll(); /* notify any waiters */
+    }
+    return false;
+  }
+  else
+  {
+    return true;
+  }
+}
+
+template<class Item, class Related>
+void ctkPluginAbstractTracked<Item,Related>::trackAdding(Item item, Related related)
+{
+  if (DEBUG)
+  {
+    qDebug() << "ctkPluginAbstractTracked::trackAdding:" << item;
+  }
+  QObject* object = 0;
+  bool becameUntracked = false;
+  /* Call customizer outside of synchronized region */
+  try
+  {
+    object = customizerAdding(item, related);
+    becameUntracked = this->customizerAddingFinal(item, object);
+  }
+  catch (...)
+  {
+    /*
+     * If the customizer throws an exception, it will
+     * propagate after the cleanup code.
+     */
+    this->customizerAddingFinal(item, object);
+    throw;
+  }
+
+  /*
+   * The item became untracked during the customizer callback.
+   */
+  if (becameUntracked && (object != 0))
+  {
+    if (DEBUG)
+    {
+      qDebug() << "ctkPluginAbstractTracked::trackAdding[removed]: " << item;
+    }
+    /* Call customizer outside of synchronized region */
+    customizerRemoved(item, related, object);
+    /*
+     * If the customizer throws an unchecked exception, it is safe to
+     * let it propagate
+     */
+  }
+}

+ 263 - 0
Libs/PluginFramework/ctkPluginAbstractTracked_p.h

@@ -0,0 +1,263 @@
+/*=============================================================================
+
+  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 CTKPLUGINABSTRACTTRACKED_P_H
+#define CTKPLUGINABSTRACTTRACKED_P_H
+
+#include <QHash>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QLinkedList>
+
+/**
+ * Abstract class to track items. If a Tracker is reused (closed then reopened),
+ * then a new ctkPluginAbstractTracked object is used. This class acts as a map of tracked
+ * item -> customized object. Subclasses of this class will act as the listener
+ * object for the tracker. This class is used to synchronize access to the
+ * tracked items. This is not a public class. It is only for use by the
+ * implementation of the Tracker class.
+ *
+ * @ThreadSafe
+ */
+template<class Item, class Related>
+class ctkPluginAbstractTracked : public QMutex
+{
+
+public:
+
+  /* set this to true to compile in debug messages */
+  static const bool	DEBUG; //	= false;
+
+  /**
+   * ctkPluginAbstractTracked constructor.
+   */
+  ctkPluginAbstractTracked();
+
+  virtual ~ctkPluginAbstractTracked();
+
+  bool wait(unsigned long timeout);
+  void wakeAll();
+
+  /**
+   * Set initial list of items into tracker before events begin to be
+   * received.
+   *
+   * This method must be called from Tracker's open method while synchronized
+   * on this object in the same synchronized block as the add listener call.
+   *
+   * @param list The initial list of items to be tracked. <code>null</code>
+   *        entries in the list are ignored.
+   * @GuardedBy this
+   */
+  void setInitial(const QList<Item>& list);
+  void setInitial(const QList<Item*>& list);
+
+  /**
+   * Track the initial list of items. This is called after events can begin to
+   * be received.
+   *
+   * This method must be called from Tracker's open method while not
+   * synchronized on this object after the add listener call.
+   *
+   */
+  void trackInitial();
+
+  /**
+   * Called by the owning Tracker object when it is closed.
+   */
+  void close();
+
+  /**
+   * Begin to track an item.
+   *
+   * @param item Item to be tracked.
+   * @param related Action related object.
+   */
+  void track(Item item, Related related);
+
+  /**
+   * Discontinue tracking the item.
+   *
+   * @param item Item to be untracked.
+   * @param related Action related object.
+   */
+  void untrack(Item item, Related related);
+
+  /**
+   * Returns the number of tracked items.
+   *
+   * @return The number of tracked items.
+   *
+   * @GuardedBy this
+   */
+  int size() const;
+
+  /**
+   * Return the customized object for the specified item
+   *
+   * @param item The item to lookup in the map
+   * @return The customized object for the specified item.
+   *
+   * @GuardedBy this
+   */
+  QObject* getCustomizedObject(Item item) const;
+
+  /**
+   * Return the list of tracked items.
+   *
+   * @return The tracked items.
+   * @GuardedBy this
+   */
+  QList<Item> getTracked() const;
+
+  /**
+   * Increment the modification count. If this method is overridden, the
+   * overriding method MUST call this method to increment the tracking count.
+   *
+   * @GuardedBy this
+   */
+  virtual void modified();
+
+  /**
+   * Returns the tracking count for this <code>ServiceTracker</code> object.
+   *
+   * The tracking count is initialized to 0 when this object is opened. Every
+   * time an item is added, modified or removed from this object the tracking
+   * count is incremented.
+   *
+   * @GuardedBy this
+   * @return The tracking count for this object.
+   */
+  int getTrackingCount() const;
+
+  /**
+   * Call the specific customizer adding method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Item to be tracked.
+   * @param related Action related object.
+   * @return Customized object for the tracked item or <code>null</code> if
+   *         the item is not to be tracked.
+   */
+  virtual QObject* customizerAdding(Item item, Related related) = 0;
+
+  /**
+   * Call the specific customizer modified method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Tracked item.
+   * @param related Action related object.
+   * @param object Customized object for the tracked item.
+   */
+  virtual void customizerModified(Item item, Related related,
+      QObject* object) = 0;
+
+  /**
+   * Call the specific customizer removed method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Tracked item.
+   * @param related Action related object.
+   * @param object Customized object for the tracked item.
+   */
+  virtual void customizerRemoved(Item item, Related related,
+      QObject* object) = 0;
+
+  /**
+   * List of items in the process of being added. This is used to deal with
+   * nesting of events. Since events may be synchronously delivered, events
+   * can be nested. For example, when processing the adding of a service and
+   * the customizer causes the service to be unregistered, notification to the
+   * nested call to untrack that the service was unregistered can be made to
+   * the track method.
+   *
+   * Since the QList implementation is not synchronized, all access to
+   * this list must be protected by the same synchronized object for
+   * thread-safety.
+   *
+   * @GuardedBy this
+   */
+  QList<Item> adding;
+
+  /**
+   * true if the tracked object is closed.
+   *
+   * This field is volatile because it is set by one thread and read by
+   * another.
+   */
+  volatile bool	closed;
+
+  /**
+   * Initial list of items for the tracker. This is used to correctly process
+   * the initial items which could be modified before they are tracked. This
+   * is necessary since the initial set of tracked items are not "announced"
+   * by events and therefore the event which makes the item untracked could be
+   * delivered before we track the item.
+   *
+   * An item must not be in both the initial and adding lists at the same
+   * time. An item must be moved from the initial list to the adding list
+   * "atomically" before we begin tracking it.
+   *
+   * Since the LinkedList implementation is not synchronized, all access to
+   * this list must be protected by the same synchronized object for
+   * thread-safety.
+   *
+   * @GuardedBy this
+   */
+  QLinkedList<Item>	initial;
+
+  /**
+   * Common logic to add an item to the tracker used by track and
+   * trackInitial. The specified item must have been placed in the adding list
+   * before calling this method.
+   *
+   * @param item Item to be tracked.
+   * @param related Action related object.
+   */
+  void trackAdding(Item item, Related related);
+
+private:
+
+  QWaitCondition waitCond;
+
+  /**
+   * Map of tracked items to customized objects.
+   *
+   * @GuardedBy this
+   */
+  QHash<Item, QObject*> tracked;
+
+  /**
+   * Modification count. This field is initialized to zero and incremented by
+   * modified.
+   *
+   * @GuardedBy this
+   */
+  QAtomicInt trackingCount;
+
+  bool customizerAddingFinal(Item item, QObject* custom);
+
+};
+
+#include "ctkPluginAbstractTracked.cpp"
+
+#endif // CTKPLUGINABSTRACTTRACKED_P_H

+ 413 - 0
Libs/PluginFramework/ctkServiceTracker.cpp

@@ -0,0 +1,413 @@
+/*=============================================================================
+
+  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 "ctkServiceTracker.h"
+#include "ctkServiceTrackerPrivate.h"
+#include "ctkTrackedService_p.h"
+#include "ctkServiceException.h"
+#include "ctkPluginConstants.h"
+
+#include <QVarLengthArray>
+#include <QDebug>
+
+#include <stdexcept>
+#include <limits>
+
+
+ctkServiceTracker::~ctkServiceTracker()
+{
+}
+
+ctkServiceTracker::ctkServiceTracker(ctkPluginContext* context,
+                  const ctkServiceReference& reference,
+                  ctkServiceTrackerCustomizer* customizer)
+  : d_ptr(new ctkServiceTrackerPrivate(this, context, reference, customizer))
+{
+}
+
+ctkServiceTracker::ctkServiceTracker(ctkPluginContext* context, const QString& clazz,
+                      ctkServiceTrackerCustomizer* customizer)
+  : d_ptr(new ctkServiceTrackerPrivate(this, context, clazz, customizer))
+{
+}
+
+ctkServiceTracker::ctkServiceTracker(ctkPluginContext* context, const ctkLDAPSearchFilter& filter,
+                      ctkServiceTrackerCustomizer* customizer)
+  : d_ptr(new ctkServiceTrackerPrivate(this, context, filter, customizer))
+{
+}
+
+void ctkServiceTracker::open()
+{
+  Q_D(ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t;
+  {
+    QMutexLocker lock(&d->mutex);
+    if (d->trackedService)
+    {
+      return;
+    }
+
+    if (d->DEBUG)
+    {
+      qDebug() << "ctkServiceTracker::open: " << d->filter;
+    }
+
+    t = QSharedPointer<ctkTrackedService>(new ctkTrackedService(this, d->customizer));
+    {
+      QMutexLocker lockT(t.data());
+      try {
+        d->context->connectServiceListener(t.data(), "serviceChanged", d->listenerFilter);
+        QList<ctkServiceReference> references;
+        if (!d->trackClass.isEmpty())
+        {
+          references = d->getInitialReferences(d->trackClass, QString());
+        }
+        else
+        {
+          if (!d->trackReference.isNull())
+          {
+            if (d->trackReference.getPlugin())
+            {
+              references.push_back(d->trackReference);
+            }
+          }
+          else
+          { /* user supplied filter */
+            references = d->getInitialReferences(QString(),
+                                              (d->listenerFilter.isNull()) ? d->filter.toString() : d->listenerFilter);
+          }
+        }
+        /* set tracked with the initial references */
+        t->setInitial(references);
+      }
+      catch (const std::invalid_argument& e)
+      {
+        throw std::runtime_error(std::string("unexpected std::invalid_argument exception: ")
+            + e.what());
+      }
+    }
+    d->trackedService = t;
+  }
+  /* Call tracked outside of synchronized region */
+  t->trackInitial(); /* process the initial references */
+}
+
+void ctkServiceTracker::close()
+{
+  Q_D(ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> outgoing;
+  QList<ctkServiceReference> references;
+  {
+    QMutexLocker lock(&d->mutex);
+    outgoing = d->trackedService;
+    if (outgoing.isNull())
+    {
+      return;
+    }
+    if (d->DEBUG)
+    {
+      qDebug() << "ctkServiceTracker::close:" << d->filter;
+    }
+    outgoing->close();
+    references = getServiceReferences();
+    d->trackedService.clear();;
+    try
+    {
+      d->context->disconnectServiceListener(outgoing.data(), "serviceChanged");
+    }
+    catch (const std::logic_error& e)
+    {
+      /* In case the context was stopped. */
+    }
+  }
+  d->modified(); /* clear the cache */
+  {
+    QMutexLocker lockT(outgoing.data());
+    outgoing->wakeAll(); /* wake up any waiters */
+  }
+  foreach (ctkServiceReference ref, references)
+  {
+    outgoing->untrack(ref, ctkServiceEvent());
+  }
+
+  if (d->DEBUG)
+  {
+    QMutexLocker lock(&d->mutex);
+    if ((d->cachedReference.isNull()) && (d->cachedService == 0))
+    {
+      qDebug() << "ctkServiceTracker::close[cached cleared]:"
+          << d->filter;
+    }
+  }
+}
+
+QObject* ctkServiceTracker::waitForService(unsigned long timeout)
+{
+  Q_D(ctkServiceTracker);
+  QObject* object = getService();
+  while (object == 0)
+  {
+    QSharedPointer<ctkTrackedService> t = d->tracked();
+    if (t.isNull())
+    { /* if ServiceTracker is not open */
+      return 0;
+    }
+    {
+      QMutexLocker lockT(t.data());
+      if (t->size() == 0)
+      {
+        t->wait(timeout);
+      }
+    }
+    object = getService();
+    if (timeout > 0)
+    {
+      return object;
+    }
+  }
+  return object;
+}
+
+QList<ctkServiceReference> ctkServiceTracker::getServiceReferences() const
+{
+  Q_D(const ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return QList<ctkServiceReference>();
+  }
+  {
+    QMutexLocker lockT(t.data());
+    if (t->size() == 0)
+    {
+      return QList<ctkServiceReference>();
+    }
+    return t->getTracked();
+  }
+}
+
+ctkServiceReference ctkServiceTracker::getServiceReference() const
+{
+  Q_D(const ctkServiceTracker);
+  ctkServiceReference reference(0);
+  {
+    QMutexLocker lock(&d->mutex);
+    reference = d->cachedReference;
+  }
+  if (!reference.isNull())
+  {
+    if (d->DEBUG)
+    {
+      qDebug() << "ctkServiceTracker::getServiceReference[cached]:"
+                   << d->filter;
+    }
+    return reference;
+  }
+  if (d->DEBUG)
+  {
+    qDebug() << "ctkServiceTracker::getServiceReference:" << d->filter;
+  }
+  QList<ctkServiceReference> references = getServiceReferences();
+  int length = references.size();
+  if (length == 0)
+  { /* if no service is being tracked */
+    throw ctkServiceException("No service is being tracked");
+  }
+  int index = 0;
+  if (length > 1)
+  { /* if more than one service, select highest ranking */
+    QVarLengthArray<int, 10> rankings(length);
+    int count = 0;
+    int maxRanking = std::numeric_limits<int>::min();
+    for (int i = 0; i < length; i++)
+    {
+      bool ok = false;
+      int ranking = references[i].getProperty(ctkPluginConstants::SERVICE_RANKING).toInt(&ok);
+      if (!ok) ranking = 0;
+
+      rankings[i] = ranking;
+      if (ranking > maxRanking)
+      {
+        index = i;
+        maxRanking = ranking;
+        count = 1;
+      }
+      else
+      {
+        if (ranking == maxRanking)
+        {
+          count++;
+        }
+      }
+    }
+    if (count > 1)
+    { /* if still more than one service, select lowest id */
+      qlonglong minId = std::numeric_limits<qlonglong>::max();
+      for (int i = 0; i < length; i++)
+      {
+        if (rankings[i] == maxRanking)
+        {
+          qlonglong id = references[i].getProperty(ctkPluginConstants::SERVICE_ID).toLongLong();
+          if (id < minId)
+          {
+            index = i;
+            minId = id;
+          }
+        }
+      }
+    }
+  }
+
+  {
+    QMutexLocker lock(&d->mutex);
+    d->cachedReference = references[index];
+    return d->cachedReference;
+  }
+}
+
+QObject* ctkServiceTracker::getService(const ctkServiceReference& reference) const
+{
+  Q_D(const ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return 0;
+  }
+  {
+    QMutexLocker lockT(t.data());
+    return t->getCustomizedObject(reference);
+  }
+}
+
+QList<QObject*> ctkServiceTracker::getServices() const
+{
+  Q_D(const ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return QList<QObject*>();
+  }
+  {
+    QMutexLocker lockT(t.data());
+    QList<ctkServiceReference> references = getServiceReferences();
+    QList<QObject*> objects;
+    foreach (ctkServiceReference ref, references)
+    {
+      objects << getService(ref);
+    }
+    return objects;
+  }
+}
+
+QObject* ctkServiceTracker::getService() const
+{
+  Q_D(const ctkServiceTracker);
+  QObject* service = d->cachedService;
+  if (service != 0)
+  {
+    if (d->DEBUG)
+    {
+      qDebug() << "ctkServiceTracker::getService[cached]:"
+                   << d->filter;
+    }
+    return service;
+  }
+  if (d->DEBUG)
+  {
+    qDebug() << "ctkServiceTracker::getService:" << d->filter;
+  }
+
+  try
+  {
+    ctkServiceReference reference = getServiceReference();
+    if (reference.isNull())
+    {
+      return 0;
+    }
+    return d->cachedService = getService(reference);
+  }
+  catch (const ctkServiceException&)
+  {
+    return 0;
+  }
+}
+
+void ctkServiceTracker::remove(const ctkServiceReference& reference)
+{
+  Q_D(ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return;
+  }
+  t->untrack(reference, ctkServiceEvent());
+}
+
+int ctkServiceTracker::size() const
+{
+  Q_D(const ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return 0;
+  }
+  {
+    QMutexLocker lockT(t.data());
+    return t->size();
+  }
+}
+
+int ctkServiceTracker::getTrackingCount() const
+{
+  Q_D(const ctkServiceTracker);
+  QSharedPointer<ctkTrackedService> t = d->tracked();
+  if (t.isNull())
+  { /* if ServiceTracker is not open */
+    return -1;
+  }
+  {
+    QMutexLocker lockT(t.data());
+    return t->getTrackingCount();
+  }
+}
+
+QObject* ctkServiceTracker::addingService(const ctkServiceReference& reference)
+{
+  Q_D(ctkServiceTracker);
+  return d->context->getService(reference);
+}
+
+void ctkServiceTracker::modifiedService(const ctkServiceReference& reference, QObject* service)
+{
+  Q_UNUSED(reference)
+  Q_UNUSED(service)
+  /* do nothing */
+}
+
+void ctkServiceTracker::removedService(const ctkServiceReference& reference, QObject* service)
+{
+  Q_UNUSED(service)
+
+  Q_D(ctkServiceTracker);
+  d->context->ungetService(reference);
+}

+ 355 - 0
Libs/PluginFramework/ctkServiceTracker.h

@@ -0,0 +1,355 @@
+/*=============================================================================
+
+  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 CTKSERVICETRACKER_H
+#define CTKSERVICETRACKER_H
+
+#include <QScopedPointer>
+
+#include "CTKPluginFrameworkExport.h"
+
+#include "ctkServiceReference.h"
+#include "ctkServiceTrackerCustomizer.h"
+#include "ctkLDAPSearchFilter.h"
+
+class ctkPluginContext;
+class ctkServiceTrackerPrivate;
+
+class CTK_PLUGINFW_EXPORT ctkServiceTracker : protected ctkServiceTrackerCustomizer
+{
+public:
+
+  ~ctkServiceTracker();
+
+  /**
+   * Create a <code>ServiceTracker</code> on the specified
+   * <code>ServiceReference</code>.
+   *
+   * <p>
+   * The service referenced by the specified <code>ServiceReference</code>
+   * will be tracked by this <code>ServiceTracker</code>.
+   *
+   * @param context The <code>BundleContext</code> against which the tracking
+   *        is done.
+   * @param reference The <code>ServiceReference</code> for the service to be
+   *        tracked.
+   * @param customizer The customizer object to call when services are added,
+   *        modified, or removed in this <code>ServiceTracker</code>. If
+   *        customizer is <code>null</code>, then this
+   *        <code>ServiceTracker</code> will be used as the
+   *        <code>ServiceTrackerCustomizer</code> and this
+   *        <code>ServiceTracker</code> will call the
+   *        <code>ServiceTrackerCustomizer</code> methods on itself.
+   */
+  ctkServiceTracker(ctkPluginContext* context,
+                    const ctkServiceReference& reference,
+                    ctkServiceTrackerCustomizer* customizer = 0);
+
+  /**
+   * Create a <code>ServiceTracker</code> on the specified class name.
+   *
+   * <p>
+   * Services registered under the specified class name will be tracked by
+   * this <code>ServiceTracker</code>.
+   *
+   * @param context The <code>BundleContext</code> against which the tracking
+   *        is done.
+   * @param clazz The class name of the services to be tracked.
+   * @param customizer The customizer object to call when services are added,
+   *        modified, or removed in this <code>ServiceTracker</code>. If
+   *        customizer is <code>null</code>, then this
+   *        <code>ServiceTracker</code> will be used as the
+   *        <code>ServiceTrackerCustomizer</code> and this
+   *        <code>ServiceTracker</code> will call the
+   *        <code>ServiceTrackerCustomizer</code> methods on itself.
+   */
+  ctkServiceTracker(ctkPluginContext* context, const QString& clazz,
+                        ctkServiceTrackerCustomizer* customizer = 0);
+
+  /**
+   * Create a <code>ServiceTracker</code> on the specified <code>Filter</code>
+   * object.
+   *
+   * <p>
+   * Services which match the specified <code>Filter</code> object will be
+   * tracked by this <code>ServiceTracker</code>.
+   *
+   * @param context The <code>BundleContext</code> against which the tracking
+   *        is done.
+   * @param filter The <code>Filter</code> to select the services to be
+   *        tracked.
+   * @param customizer The customizer object to call when services are added,
+   *        modified, or removed in this <code>ServiceTracker</code>. If
+   *        customizer is null, then this <code>ServiceTracker</code> will be
+   *        used as the <code>ServiceTrackerCustomizer</code> and this
+   *        <code>ServiceTracker</code> will call the
+   *        <code>ServiceTrackerCustomizer</code> methods on itself.
+   * @since 1.1
+   */
+  ctkServiceTracker(ctkPluginContext* context, const ctkLDAPSearchFilter& filter,
+                        ctkServiceTrackerCustomizer* customizer = 0);
+
+  /**
+   * Open this <code>ServiceTracker</code> and begin tracking services.
+   *
+   * <p>
+   * Services which match the search criteria specified when this
+   * <code>ServiceTracker</code> was created are now tracked by this
+   * <code>ServiceTracker</code>.
+   *
+   * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+   *         with which this <code>ServiceTracker</code> was created is no
+   *         longer valid.
+   * @since 1.3
+   */
+  void open();
+
+  /**
+   * Close this <code>ServiceTracker</code>.
+   *
+   * <p>
+   * This method should be called when this <code>ServiceTracker</code> should
+   * end the tracking of services.
+   *
+   * <p>
+   * This implementation calls {@link #getServiceReferences()} to get the list
+   * of tracked services to remove.
+   */
+  void close();
+
+  /**
+   * Wait for at least one service to be tracked by this
+   * <code>ServiceTracker</code>. This method will also return when this
+   * <code>ServiceTracker</code> is closed.
+   *
+   * <p>
+   * It is strongly recommended that <code>waitForService</code> is not used
+   * during the calling of the <code>BundleActivator</code> methods.
+   * <code>BundleActivator</code> methods are expected to complete in a short
+   * period of time.
+   *
+   * <p>
+   * This implementation calls {@link #getService()} to determine if a service
+   * is being tracked.
+   *
+   * @param timeout The time interval in milliseconds to wait. If zero, the
+   *        method will wait indefinitely.
+   * @return Returns the result of {@link #getService()}.
+   * @throws InterruptedException If another thread has interrupted the
+   *         current thread.
+   * @throws IllegalArgumentException If the value of timeout is negative.
+   */
+  QObject* waitForService(unsigned long timeout);
+
+  /**
+   * Return an array of <code>ServiceReference</code>s for all services being
+   * tracked by this <code>ServiceTracker</code>.
+   *
+   * @return Array of <code>ServiceReference</code>s or <code>null</code> if
+   *         no services are being tracked.
+   */
+  QList<ctkServiceReference> getServiceReferences() const;
+
+  /**
+   * Returns a <code>ServiceReference</code> for one of the services being
+   * tracked by this <code>ServiceTracker</code>.
+   *
+   * <p>
+   * If multiple services are being tracked, the service with the highest
+   * ranking (as specified in its <code>service.ranking</code> property) is
+   * returned. If there is a tie in ranking, the service with the lowest
+   * service ID (as specified in its <code>service.id</code> property); that
+   * is, the service that was registered first is returned. This is the same
+   * algorithm used by <code>BundleContext.getServiceReference</code>.
+   *
+   * <p>
+   * This implementation calls {@link #getServiceReferences()} to get the list
+   * of references for the tracked services.
+   *
+   * @return A <code>ServiceReference</code> or <code>null</code> if no
+   *         services are being tracked.
+   * @since 1.1
+   */
+  ctkServiceReference getServiceReference() const;
+
+  /**
+   * Returns the service object for the specified
+   * <code>ServiceReference</code> if the specified referenced service is
+   * being tracked by this <code>ServiceTracker</code>.
+   *
+   * @param reference The reference to the desired service.
+   * @return A service object or <code>null</code> if the service referenced
+   *         by the specified <code>ServiceReference</code> is not being
+   *         tracked.
+   */
+  QObject* getService(const ctkServiceReference& reference) const;
+
+  /**
+   * Return an array of service objects for all services being tracked by this
+   * <code>ServiceTracker</code>.
+   *
+   * <p>
+   * This implementation calls {@link #getServiceReferences()} to get the list
+   * of references for the tracked services and then calls
+   * {@link #getService(ServiceReference)} for each reference to get the
+   * tracked service object.
+   *
+   * @return An array of service objects or <code>null</code> if no services
+   *         are being tracked.
+   */
+  QList<QObject*> getServices() const;
+
+  /**
+   * Returns a service object for one of the services being tracked by this
+   * <code>ServiceTracker</code>.
+   *
+   * <p>
+   * If any services are being tracked, this implementation returns the result
+   * of calling <code>getService(getServiceReference())</code>.
+   *
+   * @return A service object or <code>null</code> if no services are being
+   *         tracked.
+   */
+  QObject* getService() const;
+
+  /**
+   * Remove a service from this <code>ServiceTracker</code>.
+   *
+   * The specified service will be removed from this
+   * <code>ServiceTracker</code>. If the specified service was being tracked
+   * then the <code>ServiceTrackerCustomizer.removedService</code> method will
+   * be called for that service.
+   *
+   * @param reference The reference to the service to be removed.
+   */
+  void remove(const ctkServiceReference& reference);
+
+  /**
+   * Return the number of services being tracked by this
+   * <code>ServiceTracker</code>.
+   *
+   * @return The number of services being tracked.
+   */
+  int size() const;
+
+  /**
+   * Returns the tracking count for this <code>ServiceTracker</code>.
+   *
+   * The tracking count is initialized to 0 when this
+   * <code>ServiceTracker</code> is opened. Every time a service is added,
+   * modified or removed from this <code>ServiceTracker</code>, the tracking
+   * count is incremented.
+   *
+   * <p>
+   * The tracking count can be used to determine if this
+   * <code>ServiceTracker</code> has added, modified or removed a service by
+   * comparing a tracking count value previously collected with the current
+   * tracking count value. If the value has not changed, then no service has
+   * been added, modified or removed from this <code>ServiceTracker</code>
+   * since the previous tracking count was collected.
+   *
+   * @since 1.2
+   * @return The tracking count for this <code>ServiceTracker</code> or -1 if
+   *         this <code>ServiceTracker</code> is not open.
+   */
+  int getTrackingCount() const;
+
+protected:
+
+  /**
+   * Default implementation of the
+   * <code>ServiceTrackerCustomizer.addingService</code> method.
+   *
+   * <p>
+   * This method is only called when this <code>ServiceTracker</code> has been
+   * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+   *
+   * <p>
+   * This implementation returns the result of calling <code>getService</code>
+   * on the <code>BundleContext</code> with which this
+   * <code>ServiceTracker</code> was created passing the specified
+   * <code>ServiceReference</code>.
+   * <p>
+   * This method can be overridden in a subclass to customize the service
+   * object to be tracked for the service being added. In that case, take care
+   * not to rely on the default implementation of
+   * {@link #removedService(ServiceReference, Object) removedService} to unget
+   * the service.
+   *
+   * @param reference The reference to the service being added to this
+   *        <code>ServiceTracker</code>.
+   * @return The service object to be tracked for the service added to this
+   *         <code>ServiceTracker</code>.
+   * @see ServiceTrackerCustomizer#addingService(ServiceReference)
+   */
+  QObject* addingService(const ctkServiceReference& reference);
+
+  /**
+   * Default implementation of the
+   * <code>ServiceTrackerCustomizer.modifiedService</code> method.
+   *
+   * <p>
+   * This method is only called when this <code>ServiceTracker</code> has been
+   * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+   *
+   * <p>
+   * This implementation does nothing.
+   *
+   * @param reference The reference to modified service.
+   * @param service The service object for the modified service.
+   * @see ServiceTrackerCustomizer#modifiedService(ServiceReference, Object)
+   */
+  void modifiedService(const ctkServiceReference& reference, QObject* service);
+
+  /**
+   * Default implementation of the
+   * <code>ServiceTrackerCustomizer.removedService</code> method.
+   *
+   * <p>
+   * This method is only called when this <code>ServiceTracker</code> has been
+   * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+   *
+   * <p>
+   * This implementation calls <code>ungetService</code>, on the
+   * <code>BundleContext</code> with which this <code>ServiceTracker</code>
+   * was created, passing the specified <code>ServiceReference</code>.
+   * <p>
+   * This method can be overridden in a subclass. If the default
+   * implementation of {@link #addingService(ServiceReference) addingService}
+   * method was used, this method must unget the service.
+   *
+   * @param reference The reference to removed service.
+   * @param service The service object for the removed service.
+   * @see ServiceTrackerCustomizer#removedService(ServiceReference, Object)
+   */
+  void removedService(const ctkServiceReference& reference, QObject* service);
+
+private:
+
+  friend class ctkTrackedService;
+
+  Q_DECLARE_PRIVATE(ctkServiceTracker)
+
+  const QScopedPointer<ctkServiceTrackerPrivate> d_ptr;
+};
+
+#endif // CTKSERVICETRACKER_H

+ 106 - 0
Libs/PluginFramework/ctkServiceTrackerCustomizer.h

@@ -0,0 +1,106 @@
+/*=============================================================================
+
+  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 CTKSERVICETRACKERCUSTOMIZER_H
+#define CTKSERVICETRACKERCUSTOMIZER_H
+
+#include <ctkServiceReference.h>
+
+
+/**
+ * The <code>ctkServiceTrackerCustomizer</code> interface allows a
+ * <code>ctkServiceTracker</code> to customize the service objects that are
+ * tracked. A <code>ctkServiceTrackerCustomizer</code> is called when a service is
+ * being added to a <code>ctkServiceTracker</code>. The
+ * <code>ctkServiceTrackerCustomizer</code> can then return an object for the
+ * tracked service. A <code>ctkServiceTrackerCustomizer</code> is also called when
+ * a tracked service is modified or has been removed from a
+ * <code>ctkServiceTracker</code>.
+ *
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>ctkServiceEvent</code> being received by a <code>ctkServiceTracker</code>.
+ * Since <code>ctkServiceEvent</code>s are synchronously delivered by the
+ * Framework, it is highly recommended that implementations of these methods do
+ * not register (<code>ctkPluginContext::registerService</code>), modify (
+ * <code>ctkServiceRegistration::setProperties</code>) or unregister (
+ * <code>ctkServiceRegistration::unregister</code>) a service while being
+ * synchronized on any object.
+ *
+ * <p>
+ * The <code>ctkServiceTracker</code> class is thread-safe. It does not call a
+ * <code>ctkServiceTrackerCustomizer</code> while holding any locks.
+ * <code>ctkServiceTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ *
+ * @ThreadSafe
+ */
+struct ctkServiceTrackerCustomizer {
+
+  virtual ~ctkServiceTrackerCustomizer() {}
+
+  /**
+   * A service is being added to the <code>ctkServiceTracker</code>.
+   *
+   * <p>
+   * This method is called before a service which matched the search
+   * parameters of the <code>ctkServiceTracker</code> is added to the
+   * <code>ctkServiceTracker</code>. This method should return the service object
+   * to be tracked for the specified <code>ctkServiceReference</code>. The
+   * returned service object is stored in the <code>ctkServiceTracker</code> and
+   * is available from the <code>getService</code> and
+   * <code>getServices</code> methods.
+   *
+   * @param reference The reference to the service being added to the
+   *        <code>ctkServiceTracker</code>.
+   * @return The service object to be tracked for the specified referenced
+   *         service or <code>0</code> if the specified referenced service
+   *         should not be tracked.
+   */
+  virtual QObject* addingService(const ctkServiceReference& reference) = 0;
+
+  /**
+   * A service tracked by the <code>ctkServiceTracker</code> has been modified.
+   *
+   * <p>
+   * This method is called when a service being tracked by the
+   * <code>ctkServiceTracker</code> has had it properties modified.
+   *
+   * @param reference The reference to the service that has been modified.
+   * @param service The service object for the specified referenced service.
+   */
+  virtual void modifiedService(const ctkServiceReference& reference, QObject* service) = 0;
+
+  /**
+   * A service tracked by the <code>ctkServiceTracker</code> has been removed.
+   *
+   * <p>
+   * This method is called after a service is no longer being tracked by the
+   * <code>ctkServiceTracker</code>.
+   *
+   * @param reference The reference to the service that has been removed.
+   * @param service The service object for the specified referenced service.
+   */
+  virtual void removedService(const ctkServiceReference& reference, QObject* service) = 0;
+};
+
+#endif // CTKSERVICETRACKERCUSTOMIZER_H

+ 128 - 0
Libs/PluginFramework/ctkServiceTrackerPrivate.cpp

@@ -0,0 +1,128 @@
+/*=============================================================================
+
+  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 "ctkServiceTrackerPrivate.h"
+#include "ctkTrackedService_p.h"
+
+#include "ctkPluginConstants.h"
+#include "ctkLDAPSearchFilter.h"
+#include "ctkServiceTracker.h"
+
+#include <stdexcept>
+
+const bool ctkServiceTrackerPrivate::DEBUG	= true;
+
+ctkServiceTrackerPrivate::ctkServiceTrackerPrivate(
+    ctkServiceTracker* st, ctkPluginContext* context,
+    const ctkServiceReference& reference,
+    ctkServiceTrackerCustomizer* customizer)
+  : context(context), customizer(customizer), trackReference(reference),
+    trackedService(0), cachedReference(0), cachedService(0), q_ptr(st)
+{
+  this->customizer = customizer ? customizer : q_func();
+  this->listenerFilter = QString("(") + ctkPluginConstants::SERVICE_ID +
+                         "=" + reference.getProperty(ctkPluginConstants::SERVICE_ID).toString() + ")";
+  try
+  {
+    this->filter = ctkLDAPSearchFilter(listenerFilter);
+  }
+  catch (const std::invalid_argument& e)
+  {
+    /*
+     * we could only get this exception if the ServiceReference was
+     * invalid
+     */
+    std::invalid_argument ia(std::string("unexpected std::invalid_argument exception: ") + e.what());
+    throw ia;
+  }
+}
+
+ctkServiceTrackerPrivate::ctkServiceTrackerPrivate(
+    ctkServiceTracker* st,
+    ctkPluginContext* context, const QString& clazz,
+    ctkServiceTrackerCustomizer* customizer)
+      : context(context), customizer(customizer), trackClass(clazz),
+        trackReference(0), trackedService(0), cachedReference(0),
+        cachedService(0), q_ptr(st)
+{
+  this->customizer = customizer ? customizer : q_func();
+  this->listenerFilter = QString("(") + ctkPluginConstants::OBJECTCLASS + "="
+                        + clazz + ")";
+  try
+  {
+    this->filter = ctkLDAPSearchFilter(listenerFilter);
+  }
+  catch (const std::invalid_argument& e)
+  {
+    /*
+     * we could only get this exception if the clazz argument was
+     * malformed
+     */
+    std::invalid_argument ia(
+        std::string("unexpected std::invalid_argument exception: ") + e.what());
+    throw ia;
+  }
+}
+
+ctkServiceTrackerPrivate::ctkServiceTrackerPrivate(
+    ctkServiceTracker* st,
+    ctkPluginContext* context, const ctkLDAPSearchFilter& filter,
+    ctkServiceTrackerCustomizer* customizer)
+      : context(context), filter(filter), customizer(customizer),
+        listenerFilter(filter.toString()), trackReference(0),
+        trackedService(0), cachedReference(0), cachedService(0), q_ptr(st)
+{
+  this->customizer = customizer ? customizer : q_func();
+  if (context == 0)
+  {
+    throw std::invalid_argument("The plugin context cannot be null.");
+  }
+}
+
+ctkServiceTrackerPrivate::~ctkServiceTrackerPrivate()
+{
+  if (customizer != q_func())
+  {
+    delete customizer;
+  }
+}
+
+QList<ctkServiceReference> ctkServiceTrackerPrivate::getInitialReferences(const QString& className,
+                                                const QString& filterString)
+{
+  return context->getServiceReferences(className, filterString);
+}
+
+QSharedPointer<ctkTrackedService> ctkServiceTrackerPrivate::tracked() const
+{
+  return trackedService;
+}
+
+void ctkServiceTrackerPrivate::modified()
+{
+  cachedReference = 0; /* clear cached value */
+  cachedService = 0; /* clear cached value */
+  if (DEBUG)
+  {
+    qDebug() << "ctkServiceTracker::modified:" << filter;
+  }
+}

+ 157 - 0
Libs/PluginFramework/ctkServiceTrackerPrivate.h

@@ -0,0 +1,157 @@
+/*=============================================================================
+
+  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 CTKSERVICETRACKERPRIVATE_H
+#define CTKSERVICETRACKERPRIVATE_H
+
+#include "ctkServiceReference.h"
+#include "ctkLDAPSearchFilter.h"
+
+#include <QMutex>
+#include <QSharedPointer>
+
+class ctkServiceTracker;
+class ctkTrackedService;
+class ctkServiceTrackerCustomizer;
+
+class ctkServiceTrackerPrivate {
+
+public:
+
+  ctkServiceTrackerPrivate(ctkServiceTracker* st,
+                           ctkPluginContext* context,
+                           const ctkServiceReference& reference,
+                           ctkServiceTrackerCustomizer* customizer);
+
+  ctkServiceTrackerPrivate(ctkServiceTracker* st,
+      ctkPluginContext* context, const QString& clazz,
+      ctkServiceTrackerCustomizer* customizer);
+
+  ctkServiceTrackerPrivate(
+      ctkServiceTracker* st,
+      ctkPluginContext* context, const ctkLDAPSearchFilter& filter,
+      ctkServiceTrackerCustomizer* customizer);
+
+  ~ctkServiceTrackerPrivate();
+
+  /**
+   * Returns the list of initial <code>ctkServiceReference</code>s that will be
+   * tracked by this <code>ctkServiceTracker</code>.
+   *
+   * @param className The class name with which the service was registered, or
+   *        <code>null</code> for all services.
+   * @param filterString The filter criteria or <code>null</code> for all
+   *        services.
+   * @return The list of initial <code>ctkServiceReference</code>s.
+   * @throws std::invalid_argument If the specified filterString has an
+   *         invalid syntax.
+   */
+  QList<ctkServiceReference> getInitialReferences(const QString& className,
+                                                  const QString& filterString);
+
+  /* set this to true to compile in debug messages */
+  static const bool	DEBUG; //	= false;
+
+  /**
+   * The Plugin Context used by this <code>ctkServiceTracker</code>.
+   */
+  ctkPluginContext* const	context;
+
+  /**
+   * The filter used by this <code>ctkServiceTracker</code> which specifies the
+   * search criteria for the services to track.
+   */
+  ctkLDAPSearchFilter	filter;
+
+  /**
+   * The <code>ctkServiceTrackerCustomizer</code> for this tracker.
+   */
+  ctkServiceTrackerCustomizer* customizer;
+
+  /**
+   * Filter string for use when adding the ServiceListener. If this field is
+   * set, then certain optimizations can be taken since we don't have a user
+   * supplied filter.
+   */
+  QString listenerFilter;
+
+  /**
+   * Class name to be tracked. If this field is set, then we are tracking by
+   * class name.
+   */
+  QString trackClass;
+
+  /**
+   * Reference to be tracked. If this field is set, then we are tracking a
+   * single ctkServiceReference.
+   */
+  ctkServiceReference	trackReference;
+
+  /**
+   * Tracked services: <code>ctkServiceReference</code> -> customized Object and
+   * <code>ServiceListener</code> object
+   */
+  QSharedPointer<ctkTrackedService> trackedService;
+
+  /**
+   * Accessor method for the current Tracked object. This method is only
+   * intended to be used by the unsynchronized methods which do not modify the
+   * tracked field.
+   *
+   * @return The current Tracked object.
+   */
+  QSharedPointer<ctkTrackedService> tracked() const;
+
+  /**
+   * Called by the ctkTrackedService object whenever the set of tracked services is
+   * modified. Clears the cache.
+   */
+  /*
+   * This method must not be synchronized since it is called by ctkTrackedService while
+   * ctkTrackedService is synchronized. We don't want synchronization interactions
+   * between the listener thread and the user thread.
+   */
+  void modified();
+
+  /**
+   * Cached ctkServiceReference for getServiceReference.
+   */
+  mutable ctkServiceReference cachedReference;
+
+  /**
+   * Cached service object for getService.
+   *
+   * This field is volatile since it is accessed by multiple threads.
+   */
+  mutable QObject* volatile cachedService;
+
+  mutable QMutex mutex;
+
+private:
+
+  Q_DECLARE_PUBLIC(ctkServiceTracker)
+
+  ctkServiceTracker * const q_ptr;
+
+};
+
+#endif // CTKSERVICETRACKERPRIVATE_H

+ 122 - 0
Libs/PluginFramework/ctkTrackedService.cpp

@@ -0,0 +1,122 @@
+/*=============================================================================
+
+  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 "ctkTrackedService_p.h"
+
+#include "ctkServiceTracker.h"
+#include "ctkServiceTrackerPrivate.h"
+
+ctkTrackedService::ctkTrackedService(ctkServiceTracker* serviceTracker,
+                  ctkServiceTrackerCustomizer* customizer)
+  : serviceTracker(serviceTracker), customizer(customizer)
+{
+
+}
+
+void ctkTrackedService::serviceChanged(const ctkServiceEvent& event)
+{
+  /*
+   * Check if we had a delayed call (which could happen when we
+   * close).
+   */
+  if (closed)
+  {
+    return;
+  }
+
+  ctkServiceReference reference = event.getServiceReference();
+  if (serviceTracker->d_func()->DEBUG)
+  {
+    qDebug() << "ctkTrackedService::serviceChanged["
+        << event.getType() << "]: " << reference;
+  }
+
+  switch (event.getType())
+  {
+  case ctkServiceEvent::REGISTERED :
+  case ctkServiceEvent::MODIFIED :
+    {
+      if (!serviceTracker->d_func()->listenerFilter.isNull())
+      { // service listener added with filter
+        track(reference, event);
+        /*
+       * If the customizer throws an unchecked exception, it
+       * is safe to let it propagate
+       */
+      }
+      else
+      { // service listener added without filter
+        if (serviceTracker->d_func()->filter.match(reference))
+        {
+          track(reference, event);
+          /*
+         * If the customizer throws an unchecked exception,
+         * it is safe to let it propagate
+         */
+        }
+        else
+        {
+          untrack(reference, event);
+          /*
+         * If the customizer throws an unchecked exception,
+         * it is safe to let it propagate
+         */
+        }
+      }
+      break;
+    }
+  case ctkServiceEvent::MODIFIED_ENDMATCH :
+  case ctkServiceEvent::UNREGISTERING :
+    untrack(reference, event);
+    /*
+     * If the customizer throws an unchecked exception, it is
+     * safe to let it propagate
+     */
+    break;
+  }
+}
+
+void ctkTrackedService::modified()
+{
+  Superclass::modified(); /* increment the modification count */
+  serviceTracker->d_func()->modified();
+}
+
+QObject* ctkTrackedService::customizerAdding(ctkServiceReference item, ctkServiceEvent related)
+{
+  Q_UNUSED(related)
+  return customizer->addingService(item);
+}
+
+void ctkTrackedService::customizerModified(ctkServiceReference item,
+                        ctkServiceEvent related, QObject* object)
+{
+  Q_UNUSED(related)
+  customizer->modifiedService(item, object);
+}
+
+void ctkTrackedService::customizerRemoved(ctkServiceReference item,
+                       ctkServiceEvent related, QObject* object)
+{
+  Q_UNUSED(related)
+  customizer->removedService(item, object);
+}

+ 101 - 0
Libs/PluginFramework/ctkTrackedService_p.h

@@ -0,0 +1,101 @@
+/*=============================================================================
+
+  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 CTKTRACKEDSERVICE_P_H
+#define CTKTRACKEDSERVICE_P_H
+
+#include "ctkPluginAbstractTracked_p.h"
+#include "ctkServiceEvent.h"
+
+class ctkServiceTracker;
+class ctkServiceTrackerCustomizer;
+
+class ctkTrackedService : public QObject,
+    public ctkPluginAbstractTracked<ctkServiceReference, ctkServiceEvent>
+{
+  Q_OBJECT
+
+public:
+  ctkTrackedService(ctkServiceTracker* serviceTracker,
+                    ctkServiceTrackerCustomizer* customizer);
+
+public slots:
+
+  /**
+   * Slot connected to service events for the
+   * <code>ctkServiceTracker</code> class. This method must NOT be
+   * synchronized to avoid deadlock potential.
+   *
+   * @param event <code>ctkServiceEvent</code> object from the framework.
+   */
+  void serviceChanged(const ctkServiceEvent& event);
+
+private:
+
+  typedef ctkPluginAbstractTracked<ctkServiceReference, ctkServiceEvent> Superclass;
+
+  ctkServiceTracker* serviceTracker;
+  ctkServiceTrackerCustomizer* customizer;
+
+  /**
+   * Increment the tracking count and tell the tracker there was a
+   * modification.
+   *
+   * @GuardedBy this
+   */
+  void modified();
+
+  /**
+   * Call the specific customizer adding method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Item to be tracked.
+   * @param related Action related object.
+   * @return Customized object for the tracked item or <code>null</code>
+   *         if the item is not to be tracked.
+   */
+  QObject* customizerAdding(ctkServiceReference item, ctkServiceEvent related);
+
+  /**
+   * Call the specific customizer modified method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Tracked item.
+   * @param related Action related object.
+   * @param object Customized object for the tracked item.
+   */
+  void customizerModified(ctkServiceReference item,
+                          ctkServiceEvent related, QObject* object) ;
+
+  /**
+   * Call the specific customizer removed method. This method must not be
+   * called while synchronized on this object.
+   *
+   * @param item Tracked item.
+   * @param related Action related object.
+   * @param object Customized object for the tracked item.
+   */
+  void customizerRemoved(ctkServiceReference item,
+                         ctkServiceEvent related, QObject* object) ;
+};
+
+#endif // CTKTRACKEDSERVICE_P_H