Browse Source

ENH: PluginFramework: Implemented plugin resolution and start mechanism

- Dependencies of plugins are resolved (taking version ranges into
  account)
- Implemented start() method
- Added first draft for event handling in the framework
- Added a lot documentation
- Enhanced the PluginBrowser to listen to framework events and open
  plugin resources
- Enhanced the ctkMacroBuildPlugin macro to generate a MANIFEST.MF file
Sascha Zelzer 15 years ago
parent
commit
c95b9a82e8
41 changed files with 2310 additions and 156 deletions
  1. 2 0
      Applications/ctkPluginBrowser/CMakeLists.txt
  2. 46 4
      Applications/ctkPluginBrowser/ctkPluginBrowser.cxx
  3. 10 0
      Applications/ctkPluginBrowser/ctkPluginBrowser.h
  4. 65 0
      Applications/ctkPluginBrowser/ctkPluginBrowserEditors.cxx
  5. 53 0
      Applications/ctkPluginBrowser/ctkPluginBrowserEditors.h
  6. 0 12
      Applications/ctkPluginBrowser/ctkPluginBrowserMainWindow.ui
  7. 12 1
      Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.cxx
  8. 1 1
      Applications/ctkPluginBrowser/ctkPluginTableModel.cxx
  9. 62 3
      CMake/ctkMacroBuildPlugin.cmake
  10. 45 1
      CMake/ctkMacroGeneratePluginManifest.cmake
  11. 6 1
      Libs/PluginFramework/CMakeLists.txt
  12. 64 4
      Libs/PluginFramework/ctkPlugin.cxx
  13. 455 3
      Libs/PluginFramework/ctkPlugin.h
  14. 1 8
      Libs/PluginFramework/ctkPluginArchive.cxx
  15. 13 23
      Libs/PluginFramework/ctkPluginArchive_p.h
  16. 11 0
      Libs/PluginFramework/ctkPluginConstants.cxx
  17. 135 0
      Libs/PluginFramework/ctkPluginConstants.h
  18. 14 0
      Libs/PluginFramework/ctkPluginContext.cxx
  19. 5 17
      Libs/PluginFramework/ctkPluginContext.h
  20. 3 3
      Libs/PluginFramework/ctkPluginEvent.h
  21. 68 1
      Libs/PluginFramework/ctkPluginFramework.cxx
  22. 7 0
      Libs/PluginFramework/ctkPluginFramework.h
  23. 62 2
      Libs/PluginFramework/ctkPluginFrameworkContextPrivate.cxx
  24. 14 2
      Libs/PluginFramework/ctkPluginFrameworkContextPrivate_p.h
  25. 65 0
      Libs/PluginFramework/ctkPluginFrameworkEvent.cxx
  26. 221 0
      Libs/PluginFramework/ctkPluginFrameworkEvent.h
  27. 41 0
      Libs/PluginFramework/ctkPluginFrameworkListeners.cxx
  28. 55 0
      Libs/PluginFramework/ctkPluginFrameworkListeners_p.h
  29. 3 1
      Libs/PluginFramework/ctkPluginFrameworkPrivate.cxx
  30. 2 0
      Libs/PluginFramework/ctkPluginFrameworkPrivate_p.h
  31. 305 0
      Libs/PluginFramework/ctkPluginFrameworkUtil.cxx
  32. 51 0
      Libs/PluginFramework/ctkPluginFrameworkUtil_p.h
  33. 158 23
      Libs/PluginFramework/ctkPluginPrivate.cxx
  34. 42 6
      Libs/PluginFramework/ctkPluginPrivate_p.h
  35. 3 3
      Libs/PluginFramework/ctkPluginStorage.cxx
  36. 2 3
      Libs/PluginFramework/ctkPlugins.cxx
  37. 65 0
      Libs/PluginFramework/ctkRequirePlugin.cxx
  38. 50 0
      Libs/PluginFramework/ctkRequirePlugin_p.h
  39. 5 0
      Libs/PluginFramework/ctkServiceReference.cxx
  40. 8 0
      Libs/PluginFramework/ctkServiceReference.h
  41. 80 34
      Libs/PluginFramework/ctkServiceRegistration.cxx

+ 2 - 0
Applications/ctkPluginBrowser/CMakeLists.txt

@@ -6,6 +6,7 @@ PROJECT(ctkPluginBrowser)
 
 SET(KIT_SRCS
   ctkPluginBrowser.cxx
+  ctkPluginBrowserEditors.cxx
   ctkPluginBrowserMain.cxx
   ctkPluginTableModel.cxx
   ctkPluginResourcesTreeModel.cxx
@@ -15,6 +16,7 @@ SET(KIT_SRCS
 # Headers that should run through moc
 SET(KIT_MOC_SRCS
   ctkPluginBrowser.h
+  ctkPluginBrowserEditors.h
   )
 
 # UI files

+ 46 - 4
Applications/ctkPluginBrowser/ctkPluginBrowser.cxx

@@ -41,6 +41,7 @@ namespace ctk {
   PluginBrowser::PluginBrowser(PluginFramework* framework)
     : framework(framework)
   {
+    framework->getPluginContext()->connectFrameworkListener(this, SLOT(frameworkEvent(PluginFrameworkEvent)));
 
     QStringList pluginDirs;
     pluginDirs << qApp->applicationDirPath() + "/Plugins";
@@ -50,7 +51,8 @@ namespace ctk {
     {
       try
       {
-        framework->getPluginContext()->installPlugin(QUrl::fromLocalFile(dirIter.next()).toString());
+        Plugin* plugin = framework->getPluginContext()->installPlugin(QUrl::fromLocalFile(dirIter.next()).toString());
+        plugin->start(Plugin::START_ACTIVATION_POLICY);
       }
       catch (const PluginException& e)
       {
@@ -62,6 +64,8 @@ namespace ctk {
 
     ui.setupUi(this);
 
+    editors = new PluginBrowserEditors(ui.centralwidget);
+
     QAbstractItemModel* pluginTableModel = new PluginTableModel(framework->getPluginContext(), this);
     ui.pluginsTableView->setModel(pluginTableModel);
 
@@ -69,15 +73,16 @@ namespace ctk {
     ui.qtResourcesTreeView->setModel(qtresourcesTreeModel);
 
     connect(ui.pluginsTableView, SIGNAL(clicked(QModelIndex)), this, SLOT(pluginSelected(QModelIndex)));
-
+    connect(ui.pluginsTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(pluginDoubleClicked(QModelIndex)));
+    connect(ui.pluginResourcesTreeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(dbResourceDoubleClicked(QModelIndex)));
+    connect(ui.qtResourcesTreeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(qtResourceDoubleClicked(QModelIndex)));
   }
 
   void PluginBrowser::pluginSelected(const QModelIndex &index)
   {
     QVariant v = index.data(Qt::UserRole);
-    qDebug() << "Selected plugin:" << v;
 
-    Plugin* plugin = framework->getPluginContext()->getPlugin(v.toInt());
+    Plugin* plugin = framework->getPluginContext()->getPlugin(v.toLongLong());
 
     if (!plugin) return;
 
@@ -86,5 +91,42 @@ namespace ctk {
     if (oldModel) oldModel->deleteLater();;
   }
 
+  void PluginBrowser::pluginDoubleClicked(const QModelIndex& index)
+  {
+    long pluginId = index.data(Qt::UserRole).toLongLong();
+    Plugin* plugin = framework->getPluginContext()->getPlugin(pluginId);
+
+    QByteArray mfContent = plugin->getResource("/META-INF/MANIFEST.MF");
+    QString location = QString("/") + plugin->getSymbolicName() + "/META-INF/MANIFEST.MF";
+    editors->openEditor(location, mfContent, location + " [cached]");
+  }
+
+  void PluginBrowser::qtResourceDoubleClicked(const QModelIndex& index)
+  {
+
+  }
+
+  void PluginBrowser::dbResourceDoubleClicked(const QModelIndex& index)
+  {
+    QString resPath = index.data(Qt::UserRole).toString();
+    if (resPath.isEmpty() || resPath.endsWith('/')) return;
+
+    qDebug() << "Trying to open: " << resPath;
+
+    QModelIndex pluginIndex = ui.pluginsTableView->selectionModel()->selectedIndexes().first();
+    long pluginId = pluginIndex.data(Qt::UserRole).toLongLong();
+
+    Plugin* plugin = framework->getPluginContext()->getPlugin(pluginId);
+
+    QByteArray resContent = plugin->getResource(resPath);
+    QString location = QString("/") + plugin->getSymbolicName() + resPath;
+    editors->openEditor(location, resContent, location + " [cached]");
+  }
+
+  void PluginBrowser::frameworkEvent(const PluginFrameworkEvent& event)
+  {
+    qDebug() << "FrameworkEvent: [" << event.getPlugin()->getSymbolicName() << "]" << event.getErrorString();
+  }
+
 
 }

+ 10 - 0
Applications/ctkPluginBrowser/ctkPluginBrowser.h

@@ -26,6 +26,10 @@
 
 #include <ui_ctkPluginBrowserMainWindow.h>
 
+#include "ctkPluginBrowserEditors.h"
+
+#include <ctkPluginFrameworkEvent.h>
+
 namespace ctk {
 
   class PluginFramework;
@@ -41,12 +45,18 @@ namespace ctk {
   private slots:
 
     void pluginSelected(const QModelIndex& index);
+    void pluginDoubleClicked(const QModelIndex& index);
+    void qtResourceDoubleClicked(const QModelIndex& index);
+    void dbResourceDoubleClicked(const QModelIndex& index);
+
+    void frameworkEvent(const PluginFrameworkEvent& event);
 
   private:
 
     PluginFramework* framework;
 
     Ui::PluginBrowserWindow ui;
+    PluginBrowserEditors* editors;
   };
 
 }

+ 65 - 0
Applications/ctkPluginBrowser/ctkPluginBrowserEditors.cxx

@@ -0,0 +1,65 @@
+/*=============================================================================
+
+  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 "ctkPluginBrowserEditors.h"
+
+#include <QTextEdit>
+#include <QVBoxLayout>
+
+namespace ctk {
+
+  PluginBrowserEditors::PluginBrowserEditors(QWidget* editorArea)
+    : QObject(editorArea), tabWidget(editorArea)
+  {
+    editorArea->setLayout(new QVBoxLayout());
+    editorArea->layout()->addWidget(&tabWidget);
+
+    tabWidget.setTabsClosable(true);
+
+    connect(&tabWidget, SIGNAL(tabCloseRequested(int)), SLOT(tabCloseRequested(int)));
+  }
+
+  void PluginBrowserEditors::openEditor(const QString &location, const QByteArray& content,
+                                   const QString& title, const QString& tooltip)
+  {
+    if (editorLocations.contains(location))
+    {
+      int index = editorLocations.indexOf(location);
+      tabWidget.setCurrentIndex(index);
+    }
+    else
+    {
+      QTextEdit* textEdit = new QTextEdit();
+      textEdit->setReadOnly(true);
+      textEdit->setPlainText(QString(content));
+      int index = tabWidget.addTab(textEdit, title);
+      editorLocations.insert(index, location);
+      tabWidget.setCurrentIndex(index);
+    }
+  }
+
+  void PluginBrowserEditors::tabCloseRequested(int index)
+  {
+    editorLocations.removeAt(index);
+    tabWidget.removeTab(index);
+  }
+
+}

+ 53 - 0
Applications/ctkPluginBrowser/ctkPluginBrowserEditors.h

@@ -0,0 +1,53 @@
+/*=============================================================================
+
+  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 CTKPLUGINBROWSEREDITORS_H
+#define CTKPLUGINBROWSEREDITORS_H
+
+#include <QObject>
+#include <QTabWidget>
+
+namespace ctk {
+
+  class PluginBrowserEditors : public QObject
+  {
+    Q_OBJECT
+
+  public:
+
+    PluginBrowserEditors(QWidget* editorArea);
+
+    void openEditor(const QString& location, const QByteArray& content, const QString& title, const QString& tooltip = QString());
+
+  private:
+
+    QStringList editorLocations;
+
+    QTabWidget tabWidget;
+
+  private slots:
+
+    void tabCloseRequested(int index);
+  };
+
+}
+
+#endif // CTKPLUGINBROWSEREDITORS_H

+ 0 - 12
Applications/ctkPluginBrowser/ctkPluginBrowserMainWindow.ui

@@ -21,18 +21,6 @@
     <property name="margin">
      <number>0</number>
     </property>
-    <item>
-     <widget class="QTabWidget" name="detailsTabWidget">
-      <property name="currentIndex">
-       <number>0</number>
-      </property>
-      <widget class="QWidget" name="tab">
-       <attribute name="title">
-        <string>MANIFEST.MF</string>
-       </attribute>
-      </widget>
-     </widget>
-    </item>
    </layout>
   </widget>
   <widget class="QMenuBar" name="menubar">

+ 12 - 1
Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.cxx

@@ -115,6 +115,17 @@ namespace ctk {
 
       return p.mid(i+1);
     }
+    else if (role == Qt::UserRole)
+    {
+      if (this->parent())
+      {
+        return this->parent()->data(role).toString() + path;
+      }
+      else
+      {
+        return path;
+      }
+    }
 
     return QVariant();
   }
@@ -181,7 +192,7 @@ namespace ctk {
     if (!index.isValid())
       return QVariant();
 
-    if (role == Qt::DisplayRole)
+    if (role == Qt::DisplayRole | role == Qt::UserRole)
     {
       PluginResourceTreeItem* item = static_cast<PluginResourceTreeItem*>(index.internalPointer());
       return item->data(role);

+ 1 - 1
Applications/ctkPluginBrowser/ctkPluginTableModel.cxx

@@ -55,7 +55,7 @@ namespace ctk {
     }
     else if (role == Qt::UserRole)
     {
-      return plugin->getPluginId();
+      return QVariant::fromValue<qlonglong>(plugin->getPluginId());
     }
 
     return QVariant();

+ 62 - 3
CMake/ctkMacroBuildPlugin.cmake

@@ -2,8 +2,31 @@
 #
 # Depends on:
 #  CTK/CMake/ctkMacroParseArguments.cmake
+#  CTK/CMake/ctkMacroGeneratePluginManifest.cmake
+#
+# This macro takes the usual arguments for building
+# a shared library using Qt. Additionally, it generates
+# plugin meta-data by creating a MANIFEST.MF text file
+# which is embedded in the share library as a Qt resource.
+#
+# The following variables can be set in a file named
+# manifest_headers.cmake, which will then be read by
+# this macro:
+#
+# Plugin-ActivationPolicy
+# Plugin-Category
+# Plugin-ContactAddress
+# Plugin-Copyright
+# Plugin-Description
+# Plugin-DocURL
+# Plugin-Icon
+# Plugin-License
+# Plugin-Name
+# Require-Plugin
+# Plugin-SymbolicName
+# Plugin-Vendor
+# Plugin-Version
 #
-
 MACRO(ctkMacroBuildPlugin)
   CtkMacroParseArguments(MY
     "NAME;EXPORT_DIRECTIVE;SRCS;MOC_SRCS;UI_FORMS;INCLUDE_DIRECTORIES;TARGET_LIBRARIES;RESOURCES;LIBRARY_TYPE"
@@ -60,10 +83,46 @@ MACRO(ctkMacroBuildPlugin)
     QT4_ADD_RESOURCES(MY_QRC_SRCS ${MY_RESOURCES})
   ENDIF()
 
+  # Clear the variables for the manifest headers
+  SET(Plugin-ActivationPolicy )
+  SET(Plugin-Category )
+  SET(Plugin-ContactAddress )
+  SET(Plugin-Copyright )
+  SET(Plugin-Description )
+  SET(Plugin-DocURL )
+  SET(Plugin-Icon )
+  SET(Plugin-License )
+  SET(Plugin-Name )
+  SET(Require-Plugin )
+  SET(Plugin-SymbolicName )
+  SET(Plugin-Vendor )
+  SET(Plugin-Version )
+
+  # If a file named manifest_headers.cmake exists, read it
+  IF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/manifest_headers.cmake")
+    INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/manifest_headers.cmake)
+  ENDIF()
+
+  # Set the plugin_symbolicname to the library name if it is not set
+  IF(NOT Plugin-SymbolicName)
+    STRING(REPLACE "_" "." Plugin-SymbolicName ${lib_name})
+  ENDIF()
+
   # Add the generated manifest qrc file
-  STRING(REPLACE "_" "." plugin_symbolic_name ${lib_name})
   ctkMacroGeneratePluginManifest(MY_QRC_SRCS
-    SYMBOLIC_NAME ${plugin_symbolic_name}
+    ACTIVATIONPOLICY ${Plugin-ActivationPolicy}
+    CATEGORY ${Plugin-Category}
+    CONTACT_ADDRESS ${Plugin-ContactAddress}
+    COPYRIGHT ${Plugin-Copyright}
+    DESCRIPTION ${Plugin-Description}
+    DOC_URL ${Plugin-DocURL}
+    ICON ${Plugin-Icon}
+    LICENSE ${Plugin-License}
+    NAME ${Plugin-Name}
+    REQUIRE_PLUGIN ${Require-Plugin}
+    SYMBOLIC_NAME ${Plugin-SymbolicName}
+    VENDOR ${Plugin-Vendor}
+    VERSION ${Plugin-Version}
     )
 
   SOURCE_GROUP("Resources" FILES

+ 45 - 1
CMake/ctkMacroGeneratePluginManifest.cmake

@@ -21,12 +21,56 @@ MACRO(ctkMacroGeneratePluginManifest QRC_SRCS)
   IF(DEFINED MY_ACTIVATIONPOLICY)
     STRING(TOLOWER "${MY_ACTIVATIONPOLICY}" _activation_policy)
     IF(_activation_policy STREQUAL "eager")
-      SET(manifest_content "Plugin-ActivationPolicy: eager")
+      SET(_manifest_content "${_manifest_content}\nPlugin-ActivationPolicy: eager")
     ELSE()
       MESSAGE(SEND_ERROR "ACTIVATIONPOLICY is set to '${MY_ACTIVATIONPOLICY}', which is not supported")
     ENDIF()
   ENDIF()
 
+  IF(DEFINED MY_CATEGORY)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Category: ${MY_CATEGORY}")
+  ENDIF()
+
+  IF(DEFINED MY_CONTACT_ADDRESS)
+    SET(_manifest_content "${_manifest_content}\nPlugin-ContactAddress: ${MY_CONTACT_ADDRESS}")
+  ENDIF()
+
+  IF(DEFINED MY_COPYRIGHT)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Copyright: ${MY_COPYRIGHT}")
+  ENDIF()
+
+  IF(DEFINED MY_DESCRIPTION)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Description: ${MY_DESCRIPTION}")
+  ENDIF()
+
+  IF(DEFINED MY_DOC_URL)
+    SET(_manifest_content "${_manifest_content}\nPlugin-DocURL: ${MY_DOC_URL}")
+  ENDIF()
+
+  IF(DEFINED MY_ICON)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Icon: ${MY_ICON}")
+  ENDIF()
+
+  IF(DEFINED MY_LICENSE)
+    SET(_manifest_content "${_manifest_content}\nPlugin-License: ${MY_LICENSE}")
+  ENDIF()
+
+  IF(DEFINED MY_NAME)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Name: ${MY_NAME}")
+  ENDIF()
+
+  IF(DEFINED MY_REQUIRE_PLUGIN)
+    SET(_manifest_content "${_manifest_content}\nRequire-Plugin: ${MY_REQUIRE_PLUGIN}")
+  ENDIF()
+
+  IF(DEFINED MY_VENDOR)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Vendor: ${MY_VENDOR}")
+  ENDIF()
+
+  IF(DEFINED MY_VERSION)
+    SET(_manifest_content "${_manifest_content}\nPlugin-Version: ${MY_VERSION}")
+  ENDIF()
+
   SET(_manifest_filename "MANIFEST.MF")
   SET(_manifest_filepath "${CMAKE_CURRENT_BINARY_DIR}/${_manifest_filename}")
   SET(_manifest_qrc_filepath "${CMAKE_CURRENT_BINARY_DIR}/${MY_SYMBOLIC_NAME}_manifest.qrc")

+ 6 - 1
Libs/PluginFramework/CMakeLists.txt

@@ -53,11 +53,15 @@ SET(KIT_SRCS
   ctkPluginFramework.cxx
   ctkPluginFrameworkContext.cxx
   ctkPluginFrameworkContextPrivate.cxx
+  ctkPluginFrameworkEvent.cxx
+  ctkPluginFrameworkListeners.cxx
   ctkPluginFrameworkPrivate.cxx
+  ctkPluginFrameworkUtil.cxx
   ctkPluginManager.cxx
   ctkPluginManifest.cxx
   ctkPluginPrivate.cxx
   ctkPlugins.cxx
+  ctkRequirePlugin.cxx
   ctkServiceReference.cxx
   ctkServiceRegistration.cxx
   #PluginFramework/ctkServiceRegistry.cxx
@@ -72,8 +76,9 @@ SET(KIT_SRCS
 
 # Headers that should run through moc
 SET(KIT_MOC_SRCS
-  ctkPluginContext.h
   ctkPluginEvent.h
+  ctkPluginFrameworkEvent.h
+  ctkPluginFrameworkListeners_p.h
 )
 
 # UI files

+ 64 - 4
Libs/PluginFramework/ctkPlugin.cxx

@@ -23,6 +23,7 @@
 
 #include "ctkPluginPrivate_p.h"
 #include "ctkPluginArchive_p.h"
+#include "ctkPluginFrameworkContextPrivate_p.h"
 
 #include <QStringList>
 
@@ -52,12 +53,53 @@ namespace ctk {
     return d->state;
   }
 
-  void Plugin::start()
+  void Plugin::start(const StartOptions& options)
   {
-
+    Q_D(Plugin);
+
+    if (d->state == UNINSTALLED)
+    {
+      throw std::logic_error("Plugin is uninstalled");
+    }
+
+    // Initialize the activation; checks initialization of lazy
+    // activation.
+
+    //TODO 1: If activating or deactivating, wait a litle
+    // we don't use mutliple threads to start plugins for now
+    //waitOnActivation(lock, "Plugin::start", false);
+
+    //2: start() is idempotent, i.e., nothing to do when already started
+    if (d->state == ACTIVE)
+    {
+      return;
+    }
+
+    //3: Record non-transient start requests.
+    if ((options & START_TRANSIENT) == 0)
+    {
+      d->setAutostartSetting(options);
+    }
+
+    //4: Resolve plugin (if needed)
+    d->getUpdatedState();
+
+    //5: Eager?
+    if ((options & START_ACTIVATION_POLICY) && d->eagerActivation )
+    {
+      d->finalizeActivation();
+    }
+    else
+    {
+      if (STARTING == d->state) return;
+      d->state = STARTING;
+      d->pluginContext = new PluginContext(this->d_func());
+      PluginEvent pluginEvent(PluginEvent::LAZY_ACTIVATION, this);
+      d->fwCtx->listeners.emitPluginChanged(pluginEvent);
+    }
   }
 
-  void Plugin::stop()
+  void Plugin::stop(const StopOptions& options)
   {
 
   }
@@ -69,7 +111,7 @@ namespace ctk {
     return d->pluginContext;
   }
 
-  int Plugin::getPluginId() const
+  long Plugin::getPluginId() const
   {
     Q_D(const Plugin);
     return d->id;
@@ -82,6 +124,24 @@ namespace ctk {
     return d->location;
   }
 
+  QHash<QString, QString> Plugin::getHeaders()
+  {
+    //TODO security
+    Q_D(Plugin);
+    if (d->cachedRawHeaders.empty())
+    {
+      d->cachedRawHeaders = d->archive->getUnlocalizedAttributes();
+    }
+
+    if (d->state == UNINSTALLED)
+    {
+      return d->cachedHeaders;
+    }
+
+    //TODO use the embedded .qm files to localize header values
+    return d->cachedRawHeaders;
+  }
+
   QString Plugin::getSymbolicName() const
   {
     Q_D(const Plugin);

+ 455 - 3
Libs/PluginFramework/ctkPlugin.h

@@ -32,6 +32,47 @@ namespace ctk {
   class PluginFrameworkContextPrivate;
   class PluginPrivate;
 
+  /**
+   * An installed plugin in the Framework.
+   *
+   * <p>
+   * A <code>Plugin</code> object is the access point to define the lifecycle of
+   * an installed plugin. Each plugin installed in the plugin environment must have
+   * an associated <code>Plugin</code> object.
+   *
+   * <p>
+   * A plugin must have a unique identity, a <code>long</code>, chosen by the
+   * Framework. This identity must not change during the lifecycle of a plugin,
+   * even when the plugin is updated. Uninstalling and then reinstalling the
+   * plugin must create a new unique identity.
+   *
+   * <p>
+   * A plugin can be in one of six states:
+   * <ul>
+   * <li>{@link #UNINSTALLED}
+   * <li>{@link #INSTALLED}
+   * <li>{@link #RESOLVED}
+   * <li>{@link #STARTING}
+   * <li>{@link #STOPPING}
+   * <li>{@link #ACTIVE}
+   * </ul>
+   * <p>
+   * They can be ORed together using the <code>States</code> type to
+   * determine if a plugin is in one of the valid states.
+   *
+   * <p>
+   * A plugin should only execute code when its state is one of
+   * <code>STARTING</code>,<code>ACTIVE</code>, or <code>STOPPING</code>.
+   * An <code>UNINSTALLED</code> plugin can not be set to another state; it is a
+   * zombie and can only be reached because references are kept somewhere.
+   *
+   * <p>
+   * The Framework is the only entity that is allowed to create
+   * <code>Plugin</code> objects, and these objects are only valid within the
+   * Framework that created them.
+   *
+   * @threadsafe
+   */
   class CTK_PLUGINFW_EXPORT Plugin {
 
     Q_DECLARE_PRIVATE(Plugin)
@@ -39,16 +80,159 @@ namespace ctk {
   public:
 
     enum State {
+      /**
+       * The plugin is uninstalled and may not be used.
+       *
+       * <p>
+       * The <code>UNINSTALLED</code> state is only visible after a plugin is
+       * uninstalled; the plugin is in an unusable state but references to the
+       * <code>Plugin</code> object may still be available and used for
+       * introspection.
+       */
       UNINSTALLED,
+
+      /**
+       * The plugin is installed but not yet resolved.
+       *
+       * <p>
+       * A plugin is in the <code>INSTALLED</code> state when it has been
+       * installed in the Framework but is not or cannot be resolved.
+       * <p>
+       * This state is visible if the plugin's code dependencies are not resolved.
+       * The Framework may attempt to resolve an <code>INSTALLED</code> plugin's
+       * code dependencies and move the plugin to the <code>RESOLVED</code>
+       * state.
+       */
       INSTALLED,
+
+      /**
+       * The plugin is resolved and is able to be started.
+       *
+       * <p>
+       * A plugin is in the <code>RESOLVED</code> state when the Framework has
+       * successfully resolved the plugin's code dependencies. These dependencies
+       * include:
+       * <ul>
+       * <li>The plugin's required plugin dependencies from its
+       * {@link PluginConstants#REQUIRE_PLUGIN} Manifest header.
+       * </ul>
+       * <p>
+       * Note that the plugin is not active yet. A plugin must be put in the
+       * <code>RESOLVED</code> state before it can be started. The Framework may
+       * attempt to resolve a plugin at any time.
+       */
       RESOLVED,
+
+      /**
+       * The plugin is in the process of starting.
+       *
+       * <p>
+       * A plugin is in the <code>STARTING</code> state when its
+       * {@link #start(const Options&) start} method is active. A plugin must be in this
+       * state when the plugin's {@link PluginActivator#start} method is called. If the
+       * <code>PluginActivator::start</code> method completes without exception,
+       * then the plugin has successfully started and must move to the
+       * <code>ACTIVE</code> state.
+       * <p>
+       * If the plugin does not have a
+       * {@link PluginConstants#ACTIVATION_EAGER eager activation policy}, then the
+       * plugin may remain in this state for some time until the activation is
+       * triggered.
+       */
       STARTING,
+
+      /**
+       * The plugin is in the process of stopping.
+       *
+       * <p>
+       * A plugin is in the <code>STOPPING</code> state when its
+       * {@link #stop(const Option&) stop} method is active. A plugin must be in this state
+       * when the plugin's {@link PluginActivator#stop} method is called. When the
+       * <code>PluginActivator::stop</code> method completes the plugin is
+       * stopped and must move to the <code>RESOLVED</code> state.
+       */
       STOPPING,
+
+      /**
+       * The plugin is now running.
+       *
+       * <p>
+       * A plugin is in the <code>ACTIVE</code> state when it has been
+       * successfully started and activated.
+       */
       ACTIVE
     };
 
+    /**
+     * Represents an ORed combination of plugin states.
+     *
+     * @see #State
+     */
     Q_DECLARE_FLAGS(States, State)
 
+    enum StartOption {
+
+      /**
+       * The plugin start operation is transient and the persistent autostart
+       * setting of the plugin is not modified.
+       *
+       * <p>
+       * This option may be set when calling {@link #start(const StartOptions&)} to notify the
+       * framework that the autostart setting of the plugin must not be modified.
+       * If this option is not set, then the autostart setting of the plugin is
+       * modified.
+       *
+       * @see #start(const StartOptions&)
+       */
+      START_TRANSIENT,
+
+      /**
+       * The plugin start operation must activate the plugin according to the
+       * plugin's declared
+       * {@link PluginConstants#PLUGIN_ACTIVATIONPOLICY activation policy}.
+       *
+       * <p>
+       * This bit may be set when calling {@link #start(const StartOptions&)} to notify the
+       * framework that the plugin must be activated using the plugin's declared
+       * activation policy.
+       *
+       * @see PluginConstants#PLUGIN_ACTIVATIONPOLICY
+       * @see #start(const StartOptions&)
+       */
+      START_ACTIVATION_POLICY
+
+    };
+
+    /**
+     * Represents an ORed combination of start options.
+     *
+     * @see #StartOption
+     */
+    Q_DECLARE_FLAGS(StartOptions, StartOption)
+
+    enum StopOption {
+      /**
+       * The plugin stop is transient and the persistent autostart setting of the
+       * plugin is not modified.
+       *
+       * <p>
+       * This option may be set when calling {@link #stop(const StopOptions&)} to notify the
+       * framework that the autostart setting of the plugin must not be modified.
+       * If this option is not set, then the autostart setting of the plugin is
+       * modified.
+       *
+       * @see #stop(const StopOptions&)
+       */
+      STOP_TRANSIENT
+    };
+
+    /**
+     * Represents an ORed combination of stop options.
+     *
+     * @see #StopOption
+     */
+    Q_DECLARE_FLAGS(StopOptions, StopOption)
+
     virtual ~Plugin();
 
     /**
@@ -63,13 +247,226 @@ namespace ctk {
      */
     State getState() const;
 
-    virtual void start();
+    /**
+     * Starts this plugin.
+     *
+     * <p>
+     * If this plugin's state is <code>UNINSTALLED</code> then an
+     * <code>std::logic_error</code> is thrown.
+     * <p>
+     * The following steps are required to start this bundle:
+     * <ol>
+     * <li>If this plugin is in the process of being activated or deactivated
+     * then this method must wait for activation or deactivation to complete
+     * before continuing. If this does not occur in a reasonable time, a
+     * <code>PluginException</code> is thrown to indicate this plugin was unable
+     * to be started.
+     *
+     * <li>If this plugin's state is <code>ACTIVE</code> then this method
+     * returns immediately.
+     *
+     * <li>If the {@link #START_TRANSIENT} option is not set then set this
+     * plugin's autostart setting to <em>Started with declared activation</em>
+     * if the {@link #START_ACTIVATION_POLICY} option is set or
+     * <em>Started with lazy activation</em> if not set. When the Framework is
+     * restarted and this plugin's autostart setting is not <em>Stopped</em>,
+     * this plugin must be automatically started.
+     *
+     * <li>If this plugin's state is not <code>RESOLVED</code>, an attempt is
+     * made to resolve this plugin. If the Framework cannot resolve this plugin,
+     * a <code>PluginException</code> is thrown.
+     *
+     * <li>If the {@link #START_ACTIVATION_POLICY} option is not set then:
+     * <ul>
+     * <li>If this plugin's state is <code>STARTING</code> then this method
+     * returns immediately.
+     * <li>This plugin's state is set to <code>STARTING</code>.
+     * <li>A plugin event of type {@link PluginEvent#LAZY_ACTIVATION} is fired.
+     * <li>This method returns immediately and the remaining steps will be
+     * followed when this plugin's activation is later triggered.
+     * </ul>
+     * If the {@link #START_ACTIVATION_POLICY} option is set and this
+     * plugin's declared activation policy is {@link PluginConstants#ACTIVATION_EAGER
+     * eager} then:
+     * <i></i>
+     * <li>This plugin's state is set to <code>STARTING</code>.
+     *
+     * <li>A plugin event of type {@link PluginEvent#STARTING} is fired.
+     *
+     * <li>The {@link PluginActivator#start} method of this plugin's
+     * <code>PluginActivator</code>, is called. If the
+     * <code>PluginActivator</code> throws an exception then:
+     * <ul>
+     * <li>This plugin's state is set to <code>STOPPING</code>.
+     * <li>A plugin event of type {@link PluginEvent#STOPPING} is fired.
+     * <li>Any services registered by this plugin must be unregistered.
+     * <li>Any services used by this plugin must be released.
+     * <li>Any listeners registered by this plugin must be removed.
+     * <li>This plugin's state is set to <code>RESOLVED</code>.
+     * <li>A plugin event of type {@link BundleEvent#STOPPED} is fired.
+     * <li>A <code>PluginException</code> is then thrown.
+     * </ul>
+     * <i></i>
+     * <li>If this plugin's state is <code>UNINSTALLED</code>, because this
+     * plugin was uninstalled while the <code>PluginActivator#start</code>
+     * method was running, a <code>PluginException</code> is thrown.
+     *
+     * <li>This plugin's state is set to <code>ACTIVE</code>.
+     *
+     * <li>A plugin event of type {@link PluginEvent#STARTED} is fired.
+     * </ol>
+     *
+     * <b>Preconditions </b>
+     * <ul>
+     * <li><code>getState()</code> in &#x007B; <code>INSTALLED</code>,
+     * <code>RESOLVED</code>, <code>STARTING</code> &#x007D;
+     * or &#x007B; <code>INSTALLED</code>, <code>RESOLVED</code> &#x007D;
+     * if this plugin has a eager activation policy.
+     * </ul>
+     * <b>Postconditions, no exceptions thrown </b>
+     * <ul>
+     * <li>Plugin autostart setting is modified unless the
+     * {@link #START_TRANSIENT} option was set.
+     * <li><code>getState()</code> in &#x007B; <code>ACTIVE</code> &#x007D;
+     * if the eager activation policy was used.
+     * <li><code>PluginActivator::start()</code> has been called and did not
+     * throw an exception if the eager policy was used.
+     * </ul>
+     * <b>Postconditions, when an exception is thrown </b>
+     * <ul>
+     * <li>Depending on when the exception occurred, plugin autostart setting is
+     * modified unless the {@link #START_TRANSIENT} option was set.
+     * <li><code>getState()</code> not in &#x007B; <code>STARTING</code>,
+     * <code>ACTIVE</code> &#x007D;.
+     * </ul>
+     *
+     * @param options The options for starting this plugin. See
+     *        {@link #START_TRANSIENT} and {@link #START_ACTIVATION_POLICY}. The
+     *        Framework must ignore unrecognized options.
+     * @throws PluginException If this plugin could not be started. This could
+     *         be because a code dependency could not be resolved or the
+     *         <code>PluginActivator</code> could not be loaded or
+     *         threw an exception.
+     * @throws std::logic_error If this plugin has been uninstalled or this
+     *         plugin tries to change its own state.
+     */
+    virtual void start(const StartOptions& options = 0);
 
-    virtual void stop();
+    /**
+     * Stops this plugin.
+     *
+     * <p>
+     * The following steps are required to stop a plugin:
+     * <ol>
+     * <li>If this plugin's state is <code>UNINSTALLED</code> then an
+     * <code>std::logic_error</code> is thrown.
+     *
+     * <li>If this plugin is in the process of being activated or deactivated
+     * then this method must wait for activation or deactivation to complete
+     * before continuing. If this does not occur in a reasonable time, a
+     * <code>PluginException</code> is thrown to indicate this plugin was unable
+     * to be stopped.
+     * <li>If the {@link #STOP_TRANSIENT} option is not set then then set this
+     * plugin's persistent autostart setting to <em>Stopped</em>. When the
+     * Framework is restarted and this plugin's autostart setting is
+     * <em>Stopped</em>, this bundle must not be automatically started.
+     *
+     * <li>If this plugin's state is not <code>STARTING</code> or
+     * <code>ACTIVE</code> then this method returns immediately.
+     *
+     * <li>This plugin's state is set to <code>STOPPING</code>.
+     *
+     * <li>A plugin event of type {@link PluginEvent#STOPPING} is fired.
+     *
+     * <li>If this plugin's state was <code>ACTIVE</code> prior to setting the
+     * state to <code>STOPPING</code>, the {@link PluginActivator#stop} method
+     * of this plugin's <code>PluginActivator</code> is
+     * called. If that method throws an exception, this method must continue to
+     * stop this plugin and a <code>PluginException</code> must be thrown after
+     * completion of the remaining steps.
+     *
+     * <li>Any services registered by this plugin must be unregistered.
+     * <li>Any services used by this plugin must be released.
+     * <li>Any listeners registered by this plugin must be removed.
+     *
+     * <li>If this plugin's state is <code>UNINSTALLED</code>, because this
+     * plugin was uninstalled while the <code>PluginActivator::stop</code> method
+     * was running, a <code>PluginException</code> must be thrown.
+     *
+     * <li>This plugin's state is set to <code>RESOLVED</code>.
+     *
+     * <li>A plugin event of type {@link PluginEvent#STOPPED} is fired.
+     * </ol>
+     *
+     * <b>Preconditions </b>
+     * <ul>
+     * <li><code>getState()</code> in &#x007B; <code>ACTIVE</code> &#x007D;.
+     * </ul>
+     * <b>Postconditions, no exceptions thrown </b>
+     * <ul>
+     * <li>Plugin autostart setting is modified unless the
+     * {@link #STOP_TRANSIENT} option was set.
+     * <li><code>getState()</code> not in &#x007B; <code>ACTIVE</code>,
+     * <code>STOPPING</code> &#x007D;.
+     * <li><code>PluginActivator::stop</code> has been called and did not throw
+     * an exception.
+     * </ul>
+     * <b>Postconditions, when an exception is thrown </b>
+     * <ul>
+     * <li>Plugin autostart setting is modified unless the
+     * {@link #STOP_TRANSIENT} option was set.
+     * </ul>
+     *
+     * @param options The options for stoping this bundle. See
+     *        {@link #STOP_TRANSIENT}.
+     * @throws PluginException If this plugin's <code>PluginActivator</code>
+     *         threw an exception.
+     * @throws std::logic_error If this plugin has been uninstalled or this
+     *         plugin tries to change its own state.
+     */
+    virtual void stop(const StopOptions& options = 0);
 
+    /**
+     * Returns this plugin's {@link PluginContext}. The returned
+     * <code>PluginContext</code> can be used by the caller to act on behalf
+     * of this plugin.
+     *
+     * <p>
+     * If this plugin is not in the {@link #STARTING}, {@link #ACTIVE}, or
+     * {@link #STOPPING} states, then this
+     * plugin has no valid <code>PluginContext</code>. This method will
+     * return <code>0</code> if this plugin has no valid
+     * <code>PluginContext</code>.
+     *
+     * @return A <code>PluginContext</code> for this plugin or
+     *         <code>0</code> if this plugin has no valid
+     *         <code>PluginContext</code>.
+     */
     PluginContext* getPluginContext() const;
 
-    int getPluginId() const;
+    /**
+     * Returns this plugin's unique identifier. This plugin is assigned a unique
+     * identifier by the Framework when it was installed in the plugin
+     * environment.
+     *
+     * <p>
+     * A plugin's unique identifier has the following attributes:
+     * <ul>
+     * <li>Is unique and persistent.
+     * <li>Is a <code>long</code>.
+     * <li>Its value is not reused for another plugin, even after a plugin is
+     * uninstalled.
+     * <li>Does not change while a plugin remains installed.
+     * <li>Does not change when a plugin is updated.
+     * </ul>
+     *
+     * <p>
+     * This method must continue to return this plugin's unique identifier while
+     * this plugin is in the <code>UNINSTALLED</code> state.
+     *
+     * @return The unique identifier of this plugin.
+     */
+    long getPluginId() const;
 
     /**
      * Returns this plugin's location identifier.
@@ -88,6 +485,49 @@ namespace ctk {
      */
     QString getLocation() const;
 
+    /**
+     * Returns this plugin's Manifest headers and values. This method returns
+     * all the Manifest headers and values from the main section of this
+     * bundle's Manifest file; that is, all lines prior to the first named section.
+     *
+     * TODO: documentation about manifest value internationalization
+     *
+     * <p>
+     * For example, the following Manifest headers and values are included if
+     * they are present in the Manifest file:
+     *
+     * <pre>
+     *     Plugin-Name
+     *     Plugin-Vendor
+     *     Plugin-Version
+     *     Plugin-Description
+     *     Plugin-DocURL
+     *     Plugin-ContactAddress
+     * </pre>
+     *
+     * <p>
+     * This method must continue to return Manifest header information while
+     * this plugin is in the <code>UNINSTALLED</code> state.
+     *
+     * @return A <code>QHash<Qstring, QString></code> object containing this plugin's
+     *         Manifest headers and values.
+     */
+    virtual QHash<QString, QString> getHeaders();
+
+    /**
+     * Returns the symbolic name of this plugin as specified by its
+     * <code>Plugin-SymbolicName</code> manifest header. The plugin symbolic
+     * name together with a version must identify a unique plugin. The plugin
+     * symbolic name should be based on the reverse domain name naming
+     * convention like that used for java packages.
+     *
+     * <p>
+     * This method must continue to return this plugin's symbolic name while
+     * this plugin is in the <code>UNINSTALLED</code> state.
+     *
+     * @return The symbolic name of this plugin or a null QString if this
+     *         plugin does not have a symbolic name.
+     */
     QString getSymbolicName() const;
 
     /**
@@ -131,10 +571,22 @@ namespace ctk {
      */
     virtual QByteArray getResource(const QString& path) const;
 
+    /**
+     * Returns the version of this plugin as specified by its
+     * <code>Plugin-Version</code> manifest header. If this plugin does not have a
+     * specified version then {@link Version#emptyVersion} is returned.
+     *
+     * <p>
+     * This method must continue to return this plugin's version while
+     * this plugin is in the <code>UNINSTALLED</code> state.
+     *
+     * @return The version of this plugin.
+     */
     Version getVersion() const;
 
   protected:
 
+    friend class PluginFramework;
     friend class PluginFrameworkContextPrivate;
     friend class Plugins;
 

+ 1 - 8
Libs/PluginFramework/ctkPluginArchive.cxx

@@ -53,16 +53,9 @@ namespace ctk {
     return manifest.getAttribute(key);
   }
 
-  QHash<QString,QString> PluginArchive::getLocalizationEntries(const QString& localeFile) const
-  {
-    //TODO
-    return QHash<QString,QString>();
-  }
-
   QHash<QString,QString> PluginArchive::getUnlocalizedAttributes() const
   {
-    //TODO
-    return QHash<QString,QString>();
+    return manifest.getMainAttributes();
   }
 
   int PluginArchive::getPluginId() const

+ 13 - 23
Libs/PluginFramework/ctkPluginArchive_p.h

@@ -91,22 +91,12 @@ public:
    * @param key Name of attribute to get.
    * @return A string with result or null if the entry doesn't exists.
    */
-   QString getAttribute(const QString& key) const;
-
-  /**
-   * Gets all localization entries from this plugin. Will typically
-   * read the file OSGI-INF/plugin_&lt;locale&gt;.properties.
-   *
-   * @param localeFile Filename within archive for localization properties.
-   * @return null or a mapping of the entries.
-   */
-   QHash<QString,QString> getLocalizationEntries(const QString& localeFile) const;
-
+  QString getAttribute(const QString& key) const;
 
   /**
    * @returns the (raw/unlocalized) attributes
    */
-   QHash<QString,QString> getUnlocalizedAttributes() const;
+  QHash<QString,QString> getUnlocalizedAttributes() const;
 
 
   /**
@@ -114,7 +104,7 @@ public:
    *
    * @return Plugin identifier.
    */
-   int getPluginId() const;
+  int getPluginId() const;
 
 
   /**
@@ -122,7 +112,7 @@ public:
    *
    * @return Bundle location.
    */
-   QUrl getPluginLocation() const;
+  QUrl getPluginLocation() const;
 
    /**
     * Get the path to the plugin library on the local
@@ -140,7 +130,7 @@ public:
    * @param component Resource to get the byte array from.
    * @return QByteArray to the entry (empty if it doesn't exist).
    */
-   QByteArray getPluginResource(const QString& component) const;
+  QByteArray getPluginResource(const QString& component) const;
 
 
   /**
@@ -151,31 +141,31 @@ public:
    * @param name
    * @return
    */
-   QStringList findResourcesPath(const QString& path) const;
+  QStringList findResourcesPath(const QString& path) const;
 
 
   /**
    * Get stored plugin start level.
    */
-   int getStartLevel() const;
+  int getStartLevel() const;
 
 
   /**
    * Set stored plugin start level.
    */
-   void setStartLevel(int level);
+  void setStartLevel(int level);
 
 
   /**
    * Get last modified timestamp.
    */
-   qtimestamp getLastModified() const;
+  qtimestamp getLastModified() const;
 
 
   /**
    * Set stored last modified timestamp.
    */
-   void setLastModified(qtimestamp timemillisecs);
+  void setLastModified(qtimestamp timemillisecs);
 
 
   /**
@@ -183,7 +173,7 @@ public:
    *
    * @return the autostart setting. "-1" if the plugin is not started.
    */
-   int getAutostartSetting() const;
+  int getAutostartSetting() const;
 
 
   /**
@@ -191,7 +181,7 @@ public:
    *
    * @param setting the autostart setting to use.
    */
-   void setAutostartSetting(int setting);
+  void setAutostartSetting(int setting);
 
 
   /**
@@ -214,7 +204,7 @@ public:
    * Remove plugin from persistent storage.
    * This will delete the current PluginArchive instance.
    */
-   void purge();
+  void purge();
 
 };
 

+ 11 - 0
Libs/PluginFramework/ctkPluginConstants.cxx

@@ -29,9 +29,20 @@ namespace ctk {
   const QString PluginConstants::FRAMEWORK_STORAGE = "org.commontk.pluginfw.storage";
 
   const QString	PluginConstants::PLUGIN_SYMBOLICNAME = "Plugin-SymbolicName";
+  const QString PluginConstants::PLUGIN_COPYRIGHT = "Plugin-Copyright";
+  const QString PluginConstants::PLUGIN_DESCRIPTION = "Plugin-Description";
+  const QString PluginConstants::PLUGIN_NAME = "Plugin-Name";
+  const QString PluginConstants::PLUGIN_LOCALIZATION = "Plugin-Localization";
+  const QString PluginConstants::PLUGIN_LOCALIZATION_DEFAULT_BASENAME = "CTK-INF/l10n/plugin";
+  const QString PluginConstants::REQUIRE_PLUGIN = "Require-Plugin";
+  const QString PluginConstants::PLUGIN_VERSION_ATTRIBUTE = "plugin-version";
   const QString	PluginConstants::PLUGIN_VERSION = "Plugin-Version";
   const QString	PluginConstants::PLUGIN_ACTIVATIONPOLICY = "Plugin-ActivationPolicy";
 
   const QString	PluginConstants::ACTIVATION_EAGER = "eager";
 
+  const QString PluginConstants::RESOLUTION_DIRECTIVE = "resolution";
+  const QString PluginConstants::RESOLUTION_MANDATORY = "mandatory";
+  const QString PluginConstants::RESOLUTION_OPTIONAL = "optional";
+
 }

+ 135 - 0
Libs/PluginFramework/ctkPluginConstants.h

@@ -69,6 +69,87 @@ namespace ctk {
     static const QString	PLUGIN_SYMBOLICNAME; // = "Plugin-SymbolicName"
 
     /**
+     * Manifest header identifying the plugin's copyright information.
+     * <p>
+     * The attribute value may be retrieved from the <code>QHash</code>
+     * object returned by the <code>Plugin::getHeaders</code> method.
+     */
+    static const QString PLUGIN_COPYRIGHT; // = "Plugin-Copyright"
+
+    /**
+     * Manifest header containing a brief description of the plugin's
+     * functionality.
+     * <p>
+     * The attribute value may be retrieved from the <code>QHash</code>
+     * object returned by the <code>Plugin::getHeaders</code> method.
+     */
+    static const QString PLUGIN_DESCRIPTION; // = "Plugin-Description"
+
+    /**
+     * Manifest header identifying the plugin's name.
+     * <p>
+     * The attribute value may be retrieved from the <code>QHash</code>
+     * object returned by the <code>Plugin::getHeaders</code> method.
+     */
+    static const QString PLUGIN_NAME; // = "Plugin-Name"
+
+
+    /**
+     * Manifest header identifying the base name of the plugin's Qt .qm
+     * files.
+     *
+     * <p>
+     * The attribute value may be retrieved from the <code>QHash</code>
+     * object returned by the <code>Plugin::getHeaders</code> method.
+     *
+     * @see #PLUGIN_LOCALIZATION_DEFAULT_BASENAME
+     */
+    static const QString PLUGIN_LOCALIZATION; // = "Plugin-Localization"
+
+    /**
+     * Default value for the <code>Plugin-Localization</code> manifest header.
+     *
+     * @see #PLUGIN_LOCALIZATION
+     */
+    static const QString PLUGIN_LOCALIZATION_DEFAULT_BASENAME; // = "CTK-INF/l10n/plugin"
+
+    /**
+     * Manifest header identifying the symbolic names of other plugins required
+     * by the plugin.
+     *
+     * <p>
+     * The attribute value may be retrieved from the <code>Dictionary</code>
+     * object returned by the <code>Plugin::getHeaders</code> method.
+     *
+     */
+    static const QString REQUIRE_PLUGIN; // = "Require-Plugin"
+
+    /**
+     * Manifest header attribute identifying a range of versions for a plugin
+     * specified in the <code>Require-Plugin</code> manifest headers.
+     * The default value is <code>0.0.0</code>.
+     *
+     * <p>
+     * The attribute value is encoded in the Require-Plugin manifest header
+     * like:
+     *
+     * <pre>
+     *     Require-Plugin: com.acme.module.test; plugin-version=&quot;1.1&quot;
+     *     Require-Plugin: com.acme.module.test; plugin-version=&quot;[1.0,2.0)&quot;
+     * </pre>
+     *
+     * <p>
+     * The plugin-version attribute value uses a mathematical interval notation
+     * to specify a range of plugin versions. A plugin-version attribute value
+     * specified as a single version means a version range that includes any
+     * plugin version greater than or equal to the specified version.
+     *
+     * @see #REQUIRE_PLUGIN
+     */
+    static const QString PLUGIN_VERSION_ATTRIBUTE; // = "plugin-version"
+
+
+    /**
      * Manifest header identifying the plugin's version.
      *
      * <p>
@@ -113,8 +194,62 @@ namespace ctk {
      */
     static const QString	ACTIVATION_EAGER; // = "eager"
 
+    /**
+     * Manifest header directive identifying the resolution type in the
+     * Require-Plugin manifest header. The default value is
+     * {@link #RESOLUTION_MANDATORY mandatory}.
+     *
+     * <p>
+     * The directive value is encoded in the Require-Plugin
+     * manifest header like:
+     *
+     * <pre>
+     *     Require-Plugin: com.acme.module.test; resolution:=&quot;optional&quot;
+     * </pre>
+     *
+     * @see #REQUIRE_PLUGIN
+     * @see #RESOLUTION_MANDATORY
+     * @see #RESOLUTION_OPTIONAL
+     */
+    static const QString RESOLUTION_DIRECTIVE; // = "resolution"
 
+    /**
+     * Manifest header directive value identifying a mandatory resolution type.
+     * A mandatory resolution type indicates that the required plugin
+     * must be resolved when the plugin is resolved. If such a
+     * require plugin cannot be resolved, the module fails to resolve.
+     *
+     * <p>
+     * The directive value is encoded in the Require-Plugin
+     * manifest header like:
+     *
+     * <pre>
+     *     Require-Plugin: com.acme.module.test; resolution:=&quot;manditory&quot;
+     * </pre>
+     *
+     * @see #RESOLUTION_DIRECTIVE
+     */
+    static const QString RESOLUTION_MANDATORY; // = "mandatory"
 
+    /**
+     * Manifest header directive value identifying an optional resolution type.
+     * An optional resolution type indicates that the require plugin
+     * is optional and the plugin may be resolved without the require
+     * plugin being resolved. If the require plugin is not resolved
+     * when the plugin is resolved, therequire plugin may not be
+     * resolved before the plugin is refreshed.
+     *
+     * <p>
+     * The directive value is encoded in the Require-Plugin
+     * manifest header like:
+     *
+     * <pre>
+     *     Require-Plugin: com.acme.module.test; resolution:=&quot;optional&quot;
+     * </pre>
+     *
+     * @see #RESOLUTION_DIRECTIVE
+     */
+    static const QString RESOLUTION_OPTIONAL; // = "optional"
 
   };
 

+ 14 - 0
Libs/PluginFramework/ctkPluginContext.cxx

@@ -110,5 +110,19 @@ namespace ctk {
 
   }
 
+  bool PluginContext::connectPluginListener(const QObject* receiver, const char* method,
+                                            Qt::ConnectionType type)
+  {
+    Q_D(PluginContext);
+    // TODO check permissions for a direct connection
+    receiver->connect(&(d->plugin->fwCtx->listeners), SIGNAL(pluginChanged(PluginEvent)), method, type);
+  }
+
+  bool PluginContext::connectFrameworkListener(const QObject* receiver, const char* method, Qt::ConnectionType type)
+  {
+    Q_D(PluginContext);
+    receiver->connect(&(d->plugin->fwCtx->listeners), SIGNAL(frameworkEvent(PluginFrameworkEvent)), method, type);
+  }
+
 
 }

+ 5 - 17
Libs/PluginFramework/ctkPluginContext.h

@@ -90,9 +90,8 @@ namespace ctk {
    *
    * @threadsafe
    */
-  class CTK_PLUGINFW_EXPORT PluginContext : public QObject
+  class CTK_PLUGINFW_EXPORT PluginContext
   {
-    Q_OBJECT
 	  Q_DECLARE_PRIVATE(PluginContext)
 
   public:
@@ -136,26 +135,15 @@ namespace ctk {
     Plugin* installPlugin(const QUrl& location, QIODevice* in = 0);
 
 
-  signals:
+    bool connectPluginListener(const QObject* receiver, const char* method, Qt::ConnectionType type = Qt::QueuedConnection);
 
-    /**
-     *
-     */
-    void pluginChanged(const PluginEvent& event);
-
-    /**
-     *
-     */
-    //void frameworkEvent(const FrameworkEvent& event);
-
-    /**
-     *
-     */
-    //void serviceChanged(const ServiceEvent& event);
+    bool connectFrameworkListener(const QObject* receiver, const char* method, Qt::ConnectionType type = Qt::QueuedConnection);
 
   protected:
 
     friend class PluginFrameworkPrivate;
+    friend class Plugin;
+    friend class PluginPrivate;
 
     PluginContext(PluginPrivate* plugin);
 

+ 3 - 3
Libs/PluginFramework/ctkPluginEvent.h

@@ -41,7 +41,7 @@ namespace ctk {
    * when a change occurs in a plugins's lifecycle. A type code is used to identify
    * the event type for future extendability.
    *
-   * @see PluginContext#pluginChanged
+   * @see PluginContext#connectPluginListener
    * @see EventBus
    */
   class CTK_PLUGINFW_EXPORT PluginEvent : public QObject
@@ -123,8 +123,8 @@ namespace ctk {
 
     }
 
-    PluginEvent::Type type;
-    Plugin* plugin;
+    const PluginEvent::Type type;
+    Plugin* const plugin;
   };
 
 }

+ 68 - 1
Libs/PluginFramework/ctkPluginFramework.cxx

@@ -25,6 +25,7 @@
 #include "ctkPluginPrivate_p.h"
 #include "ctkPluginFrameworkContextPrivate_p.h"
 #include "ctkPluginConstants.h"
+#include "ctkPluginArchive_p.h"
 
 
 namespace ctk {
@@ -32,7 +33,7 @@ namespace ctk {
   PluginFramework::PluginFramework(PluginFrameworkContextPrivate* fw)
     : Plugin(*new PluginFrameworkPrivate(*this, fw))
   {
-
+    qRegisterMetaType<PluginFrameworkEvent>("PluginFrameworkEvent");
   }
 
   void PluginFramework::init()
@@ -55,6 +56,65 @@ namespace ctk {
     d->init();
   }
 
+  void PluginFramework::start(const Plugin::StartOptions& options)
+  {
+    Q_UNUSED(options);
+    Q_D(PluginFramework);
+
+    QStringList pluginsToStart;
+    {
+      QMutexLocker sync(&d->lock);
+      // TODO: parallel start
+      //waitOnActivation(lock, "PluginFramework::start", true);
+
+      switch (d->state)
+      {
+      case INSTALLED:
+      case RESOLVED:
+        d->init();
+      case STARTING:
+        d->activating = true;
+        break;
+      case ACTIVE:
+        return;
+      default:
+        throw std::logic_error("INTERNAL ERROR, Illegal state");
+      }
+
+      pluginsToStart = d->fwCtx->storage.getStartOnLaunchPlugins();
+    }
+
+    // Start plugins according to their autostart setting.
+    QStringListIterator i(pluginsToStart);
+    while (i.hasNext())
+    {
+      Plugin* p = d->fwCtx->plugins->getPlugin(i.next());
+      try {
+        const int autostartSetting = p->d_func()->archive->getAutostartSetting();
+        // Launch must not change the autostart setting of a plugin
+        StartOptions option = Plugin::START_TRANSIENT;
+        if (Plugin::START_ACTIVATION_POLICY == autostartSetting)
+        {
+          // Transient start according to the plugins activation policy.
+          option |= Plugin::START_ACTIVATION_POLICY;
+        }
+        p->start(option);
+      }
+      catch (const PluginException& pe)
+      {
+        d->fwCtx->listeners.frameworkError(p, pe);
+      }
+    }
+
+    {
+      QMutexLocker sync(&d->lock);
+      d->state = ACTIVE;
+      d->activating = false;
+      d->fwCtx->listeners.emitFrameworkEvent(
+          PluginFrameworkEvent(PluginFrameworkEvent::STARTED, this));
+    }
+  }
+
   QStringList PluginFramework::getResourceList(const QString& path) const
   {
     QString resourcePath = QString(":/") + PluginConstants::SYSTEM_PLUGIN_SYMBOLICNAME;
@@ -97,4 +157,11 @@ namespace ctk {
     // TODO implement
   }
 
+  QHash<QString, QString> PluginFramework::getHeaders()
+  {
+    //TODO security
+    Q_D(PluginFramework);
+    return d->systemHeaders;
+  }
+
 }

+ 7 - 0
Libs/PluginFramework/ctkPluginFramework.h

@@ -44,6 +44,13 @@ namespace ctk {
      */
     void init();
 
+    /**
+     * Start this framework.
+     */
+    void start(const Plugin::StartOptions& options = 0);
+
+    QHash<QString, QString> getHeaders();
+
     // TODO return info about the reason why this
     // method returned
     void waitForStop(int timeout);

+ 62 - 2
Libs/PluginFramework/ctkPluginFrameworkContextPrivate.cxx

@@ -23,6 +23,7 @@
 
 #include "ctkPluginFrameworkPrivate_p.h"
 #include "ctkPluginArchive_p.h"
+#include "ctkPluginConstants.h"
 
 namespace ctk {
 
@@ -114,10 +115,69 @@ namespace ctk {
     return dbg;
   }
 
-  void PluginFrameworkContextPrivate::resolvePlugin(PluginPrivate* plugin) const
+  void PluginFrameworkContextPrivate::resolvePlugin(PluginPrivate* plugin)
   {
-    // TODO
+    qDebug() << "resolve:" << plugin->symbolicName << "[" << plugin->id << "]";
 
+    // If we enter with tempResolved set, it means that we already have
+    // resolved plugins. Check that it is true!
+    if (tempResolved.size() > 0 && !tempResolved.contains(plugin))
+    {
+      PluginException pe("resolve: InternalError1!", PluginException::RESOLVE_ERROR);
+      listeners.frameworkError(plugin->q_func(), pe);
+      throw pe;
+    }
+
+    tempResolved.clear();
+    tempResolved.insert(plugin);
+
+    checkRequirePlugin(plugin);
+
+    tempResolved.clear();
+
+    qDebug() << "resolve: Done for" << plugin->symbolicName << "[" << plugin->id << "]";
+  }
+
+  void PluginFrameworkContextPrivate::checkRequirePlugin(PluginPrivate *plugin)
+  {
+    if (!plugin->require.isEmpty())
+    {
+      qDebug() << "checkRequirePlugin: check requiring plugin" << plugin->id;
+
+      QListIterator<RequirePlugin*> i(plugin->require);
+      while (i.hasNext())
+      {
+        RequirePlugin* pr = i.next();
+        QList<Plugin*> pl = plugins->getPlugins(pr->name, pr->pluginRange);
+        PluginPrivate* ok = 0;
+        for (QListIterator<Plugin*> pci(pl); pci.hasNext() && ok == 0; )
+        {
+          PluginPrivate* p2 = pci.next()->d_func();
+          if (tempResolved.contains(p2))
+          {
+            ok = p2;
+          }
+          else if (PluginPrivate::RESOLVED_FLAGS & p2->state)
+          {
+            ok = p2;
+          }
+          else if (p2->state == Plugin::INSTALLED) {
+            QSet<PluginPrivate*> oldTempResolved = tempResolved;
+            tempResolved.insert(p2);
+            checkRequirePlugin(p2);
+            tempResolved = oldTempResolved;
+            ok = p2;
+          }
+        }
+
+        if (!ok && pr->resolution == PluginConstants::RESOLUTION_MANDATORY)
+        {
+          tempResolved.clear();
+          qDebug() << "checkRequirePlugin: failed to satisfy:" << pr->name;
+          throw PluginException(QString("Failed to resolve required plugin: %1").arg(pr->name));
+        }
+      }
+    }
   }
 
 

+ 14 - 2
Libs/PluginFramework/ctkPluginFrameworkContextPrivate_p.h

@@ -29,6 +29,7 @@
 #include "ctkPluginFramework.h"
 #include "ctkPluginStorage_p.h"
 #include "ctkPlugins_p.h"
+#include "ctkPluginFrameworkListeners_p.h"
 
 namespace ctk {
 
@@ -44,6 +45,11 @@ namespace ctk {
       Plugins* plugins;
 
       /**
+       * All listeners in this framework.
+       */
+      PluginFrameworkListeners listeners;
+
+      /**
        * All registered services in this framework.
        */
       //Services services;
@@ -108,14 +114,16 @@ namespace ctk {
        */
       void checkOurPlugin(Plugin* plugin) const;
 
+
       /**
        * Check that the plugin specified can resolve all its
        * Require-Plugin constraints.
        *
        * @param plugin Plugin to check, must be in INSTALLED state
-       * @return Symbolic name of plugin blocking resolve, otherwise null.
+       *
+       * @throws PluginException
        */
-      void resolvePlugin(PluginPrivate* plugin) const;
+      void resolvePlugin(PluginPrivate* plugin);
 
 
       /**
@@ -124,7 +132,11 @@ namespace ctk {
        */
       QDebug log() const;
 
+  private:
+
+      QSet<PluginPrivate*> tempResolved;
 
+      void checkRequirePlugin(PluginPrivate* plugin);
   };
 
 }

+ 65 - 0
Libs/PluginFramework/ctkPluginFrameworkEvent.cxx

@@ -0,0 +1,65 @@
+/*=============================================================================
+
+  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 "ctkPluginFrameworkEvent.h"
+
+namespace ctk {
+
+  PluginFrameworkEvent::PluginFrameworkEvent()
+    : d(0)
+  {
+
+  }
+
+  PluginFrameworkEvent::PluginFrameworkEvent(Type type, Plugin* plugin, const std::exception& fwException)
+    : d(new PluginFrameworkEventData(type, plugin, fwException.what()))
+  {
+
+  }
+
+  PluginFrameworkEvent::PluginFrameworkEvent(Type type, Plugin* plugin)
+    : d(new PluginFrameworkEventData(type, plugin, QString()))
+  {
+
+  }
+
+  PluginFrameworkEvent::PluginFrameworkEvent(const PluginFrameworkEvent& other)
+    : d(other.d)
+  {
+
+  }
+
+  QString PluginFrameworkEvent::getErrorString() const
+  {
+    return d->errorString;
+  }
+
+  Plugin* PluginFrameworkEvent::getPlugin() const
+  {
+    return d->plugin;
+  }
+
+  PluginFrameworkEvent::Type PluginFrameworkEvent::getType() const
+  {
+    return d->type;
+  }
+
+}

+ 221 - 0
Libs/PluginFramework/ctkPluginFrameworkEvent.h

@@ -0,0 +1,221 @@
+/*=============================================================================
+
+  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 CTKPLUGINFRAMEWORKEVENT_H
+#define CTKPLUGINFRAMEWORKEVENT_H
+
+#include <QObject>
+#include <QSharedDataPointer>
+
+#include "CTKPluginFrameworkExport.h"
+
+namespace ctk {
+
+  class Plugin;
+  class PluginFrameworkEventData;
+
+  /**
+   * A general event from the Framework.
+   *
+   * <p>
+   * <code>PluginFrameworkEvent</code> objects are delivered to slots connected
+   * <code>FrameworkListener</code>s when a general event occurs within the plugin
+   * environment. A type code is used to identify the event type for future
+   * extendability.
+   *
+   * @see PluginContext#connectFrameworkListener
+   * @see EventBus
+   */
+  class CTK_PLUGINFW_EXPORT PluginFrameworkEvent : public QObject
+  {
+    Q_OBJECT
+    Q_PROPERTY(Type type READ getType CONSTANT)
+    Q_PROPERTY(Plugin* plugin READ getPlugin CONSTANT)
+    Q_PROPERTY(QString errorString READ getErrorString CONSTANT)
+    Q_ENUMS(Type)
+
+    QSharedDataPointer<PluginFrameworkEventData> d;
+
+  public:
+
+    enum Type {
+      /**
+       * The Framework has started.
+       *
+       * <p>
+       * This event is fired when the Framework has started after all installed
+       * plugins that are marked to be started have been started and the Framework
+       * has reached the initial start level. The source of this event is the
+       * System Plugin.
+       */
+      STARTED,
+
+      /**
+       * An error has occurred.
+       *
+       * <p>
+       * There was an error associated with a plugin.
+       */
+      ERROR,
+
+      /**
+       * A warning has occurred.
+       *
+       * <p>
+       * There was a warning associated with a plugin.
+       */
+      WARNING,
+
+      /**
+       * An informational event has occurred.
+       *
+       * <p>
+       * There was an informational event associated with a plugin.
+       */
+      INFO,
+
+      /**
+       * The Framework has stopped.
+       *
+       * <p>
+       * This event is fired when the Framework has been stopped because of a stop
+       * operation on the system plugin. The source of this event is the System
+       * Plugin.
+       */
+      STOPPED,
+
+      /**
+       * The Framework has stopped during update.
+       *
+       * <p>
+       * This event is fired when the Framework has been stopped because of an
+       * update operation on the system plugin. The Framework will be restarted
+       * after this event is fired. The source of this event is the System Plugin.
+       */
+      STOPPED_UPDATE,
+
+      /**
+       * The Framework did not stop before the wait timeout expired.
+       *
+       * <p>
+       * This event is fired when the Framework did not stop before the wait
+       * timeout expired. The source of this event is the System Plugin.
+       */
+      WAIT_TIMEDOUT
+
+    };
+
+    /**
+     * Default constructor for use with the Qt meta object system.
+     */
+    PluginFrameworkEvent();
+
+    /**
+     * Creates a Framework event regarding the specified plugin and exception.
+     *
+     * @param type The event type.
+     * @param plugin The event source.
+     * @param fwException The related exception.
+     */
+    PluginFrameworkEvent(Type type, Plugin* plugin, const std::exception& fwException);
+
+    /**
+     * Creates a Framework event regarding the specified plugin.
+     *
+     * @param type The event type.
+     * @param plugin The event source.
+     */
+    PluginFrameworkEvent(Type type, Plugin* plugin);
+
+    PluginFrameworkEvent(const PluginFrameworkEvent& other);
+
+    /**
+     * Returns the exception error string related to this event.
+     *
+     * @return The related error string.
+     */
+    QString getErrorString() const;
+
+    /**
+     * Returns the plugin associated with the event. This plugin is also the
+     * source of the event.
+     *
+     * @return The plugin associated with the event.
+     */
+    Plugin* getPlugin() const;
+
+    /**
+     * Returns the type of framework event.
+     * <p>
+     * The type values are:
+     * <ul>
+     * <li>{@link #STARTED}
+     * <li>{@link #ERROR}
+     * <li>{@link #WARNING}
+     * <li>{@link #INFO}
+     * <li>{@link #STARTLEVEL_CHANGED}
+     * <li>{@link #STOPPED}
+     * <li>{@link #STOPPED_UPDATE}
+     * <li>{@link #WAIT_TIMEDOUT}
+     * </ul>
+     *
+     * @return The type of state change.
+     */
+    Type getType() const;
+  };
+
+
+  class PluginFrameworkEventData : public QSharedData
+  {
+  public:
+
+    PluginFrameworkEventData(PluginFrameworkEvent::Type type, Plugin* plugin, const QString& exc)
+      : plugin(plugin), errorString(exc), type(type)
+    {
+
+    }
+
+    PluginFrameworkEventData(const PluginFrameworkEventData& other)
+      : QSharedData(other), plugin(other.plugin), errorString(other.errorString),
+        type(other.type)
+    {
+
+    }
+
+    /**
+     * Plugin related to the event.
+     */
+    Plugin* const	plugin;
+
+    /**
+     * Exception related to the event.
+     */
+    const QString errorString;
+
+    /**
+     * Type of event.
+     */
+    const PluginFrameworkEvent::Type type;
+  };
+
+}
+
+#endif // CTKPLUGINFRAMEWORKEVENT_H

+ 41 - 0
Libs/PluginFramework/ctkPluginFrameworkListeners.cxx

@@ -0,0 +1,41 @@
+/*=============================================================================
+
+  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 "ctkPluginFrameworkListeners_p.h"
+
+namespace ctk {
+
+  void PluginFrameworkListeners::frameworkError(Plugin* p, const std::exception& e)
+  {
+    emit frameworkEvent(PluginFrameworkEvent(PluginFrameworkEvent::ERROR, p, e));
+  }
+
+  void PluginFrameworkListeners::emitFrameworkEvent(const PluginFrameworkEvent& event)
+  {
+    emit frameworkEvent(event);
+  }
+
+  void PluginFrameworkListeners::emitPluginChanged(const PluginEvent& event)
+  {
+    emit pluginChanged(event);
+  }
+
+}

+ 55 - 0
Libs/PluginFramework/ctkPluginFrameworkListeners_p.h

@@ -0,0 +1,55 @@
+/*=============================================================================
+
+  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 CTKPLUGINFRAMEWORKLISTENERS_H
+#define CTKPLUGINFRAMEWORKLISTENERS_H
+
+#include <QObject>
+
+#include <ctkPluginEvent.h>
+#include <ctkPluginFrameworkEvent.h>
+
+namespace ctk {
+
+  class PluginFrameworkListeners : public QObject
+  {
+
+    Q_OBJECT
+
+  public:
+
+    void frameworkError(Plugin* p, const std::exception& e);
+
+    void emitPluginChanged(const PluginEvent& event);
+
+    void emitFrameworkEvent(const PluginFrameworkEvent& event);
+
+  signals:
+
+    void pluginChanged(const PluginEvent& event);
+
+    void frameworkEvent(const PluginFrameworkEvent& event);
+
+  };
+
+}
+
+#endif // CTKPLUGINFRAMEWORKLISTENERS_H

+ 3 - 1
Libs/PluginFramework/ctkPluginFrameworkPrivate.cxx

@@ -34,7 +34,9 @@ namespace ctk {
                     // TODO: read version from the manifest resource
                     Version(0, 9, 0))
   {
-
+    systemHeaders.insert(PluginConstants::PLUGIN_SYMBOLICNAME, symbolicName);
+    systemHeaders.insert(PluginConstants::PLUGIN_NAME, location);
+    systemHeaders.insert(PluginConstants::PLUGIN_VERSION, version.toString());
   }
 
   void PluginFrameworkPrivate::init()

+ 2 - 0
Libs/PluginFramework/ctkPluginFrameworkPrivate_p.h

@@ -42,6 +42,8 @@ namespace ctk {
 
     void initSystemPlugin();
 
+    QHash<QString, QString> systemHeaders;
+
   };
 
 }

+ 305 - 0
Libs/PluginFramework/ctkPluginFrameworkUtil.cxx

@@ -0,0 +1,305 @@
+/*=============================================================================
+
+  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 "ctkPluginFrameworkUtil_p.h"
+
+#include <QString>
+
+#include <stdexcept>
+
+namespace ctk {
+
+  /**
+   * Class for tokenize an attribute string.
+   */
+  class AttributeTokenizer {
+
+  public:
+
+    QString s;
+    int length;
+    int pos;
+
+    AttributeTokenizer(const QString& input)
+      : s(input), length(input.size()), pos(0)
+    {
+
+    }
+
+    QString getWord()
+    {
+      skipWhite();
+      bool backslash = false;
+      bool quote = false;
+      QString val;
+      int end = 0;
+      for (; pos < length; pos++)
+      {
+        bool breakLoop = false;
+        if (backslash)
+        {
+          backslash = false;
+          val.append(s.at(pos));
+        }
+        else
+        {
+          QChar c = s.at(pos);
+          switch (c.toAscii())
+          {
+          case '"':
+            quote = !quote;
+            end = val.length();
+            break;
+          case '\\':
+            backslash = true;
+            break;
+          case ',': case ':': case ';': case '=':
+            if (!quote)
+            {
+              breakLoop = true;
+              break;
+            }
+            // Fall through
+          default:
+            val.append(c);
+            if (!c.isSpace())
+            {
+              end = val.length();
+            }
+            break;
+          }
+          if (breakLoop) break;
+        }
+      }
+      if (quote || backslash || end == 0)
+      {
+        return QString();
+      }
+      return val.left(end);
+    }
+
+    QString getKey()
+    {
+      if (pos >= length) {
+        return QString();
+      }
+      int save = pos;
+      if (s.at(pos) == ';') {
+        pos++;
+      }
+      QString res = getWord();
+      if (!res.isNull()) {
+        if (pos == length) {
+          return res;
+        }
+        QChar c = s.at(pos);
+        if (c == ';' || c == ',') {
+          return res;
+        }
+      }
+      pos = save;
+      return QString();
+    }
+
+    QString getParam()
+    {
+      if (pos == length || s.at(pos) != ';') {
+        return QString();
+      }
+      int save = pos++;
+      QString res = getWord();
+      if (!res.isNull()) {
+        if (pos < length && s.at(pos) == '=') {
+          return res;
+        } if (pos + 1 < length && s.at(pos) == ':' && s.at(pos+1) == '=') {
+          return res;
+        }
+      }
+      pos = save;
+      return QString();
+    }
+
+    bool isDirective()
+    {
+      if (pos + 1 < length && s.at(pos) == ':')
+      {
+        pos++;
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    QString getValue()
+    {
+      if (s.at(pos) != '=')
+      {
+        return QString();
+      }
+      int save = pos++;
+      skipWhite();
+      QString val = getWord();
+      if (val.isNull())
+      {
+        pos = save;
+        return QString();
+      }
+      return val;
+    }
+
+    bool getEntryEnd()
+    {
+      int save = pos;
+      skipWhite();
+      if (pos == length) {
+        return true;
+      } else if (s.at(pos) == ',') {
+        pos++;
+        return true;
+      } else {
+        pos = save;
+        return false;
+      }
+    }
+
+    bool getEnd()
+    {
+      int save = pos;
+      skipWhite();
+      if (pos == length) {
+        return true;
+      } else {
+        pos = save;
+        return false;
+      }
+    }
+
+    QString getRest()
+    {
+      QString res = s.mid(pos).trimmed();
+      return res.length() == 0 ? "<END OF LINE>" : res;
+    }
+
+  private:
+
+    void skipWhite()
+    {
+      for (; pos < length; pos++) {
+        if (!s.at(pos).isSpace()) {
+          break;
+        }
+      }
+    }
+  };
+
+  QList<QMap<QString, QStringList> > PluginFrameworkUtil::parseEntries(const QString& a, const QString& s,
+                                             bool single, bool unique, bool single_entry)
+  {
+    QList<QMap<QString, QStringList> > result;
+    if (!s.isNull())
+    {
+      AttributeTokenizer at(s);
+      do {
+        QList<QString> keys;
+        QMap<QString, QStringList > params;
+        QStringList directives;
+
+        QString key = at.getKey();
+        if (key.isNull())
+        {
+          QString what = QString("Definition, ") + a + ", expected key at: " + at.getRest()
+                         + ". Key values are terminated by a ';' or a ',' and may not "
+                         + "contain ':', '='.";
+          throw std::invalid_argument(what.toStdString());
+        }
+        if (!single)
+        {
+          keys.push_back(key);
+          while (!(key = at.getKey()).isNull())
+          {
+            keys.push_back(key);
+          }
+        }
+        QString param;
+        while (!(param = at.getParam()).isNull())
+        {
+          QStringList& old = params[param];
+          bool is_directive = at.isDirective();
+          if (!old.isEmpty() && unique)
+          {
+            QString what = QString("Definition, ") + a + ", duplicate " +
+                           (is_directive ? "directive" : "attribute") +
+                           ": " + param;
+            throw std::invalid_argument(what.toStdString());
+          }
+          QString value = at.getValue();
+          if (value.isNull())
+          {
+            QString what = QString("Definition, ") + a + ", expected value at: " + at.getRest();
+            throw std::invalid_argument(what.toStdString());
+          }
+          if (is_directive)
+          {
+            // NYI Handle directives and check them
+            directives.push_back(param);
+          }
+          if (unique)
+          {
+            params.insert(param, QStringList(value));
+          } else {
+            old.push_back(value);
+          }
+        }
+
+        if (at.getEntryEnd())
+        {
+          if (single)
+          {
+            params.insert("$key", QStringList(key));
+          }
+          else
+          {
+            params.insert("$keys", keys);
+          }
+          result.push_back(params);
+        }
+        else
+        {
+          QString what = QString("Definition, ") + a + ", expected end of entry at: " + at.getRest();
+          throw std::invalid_argument(what.toStdString());
+        }
+
+        if (single_entry && !at.getEnd())
+        {
+          QString what = QString("Definition, ") + a + ", expected end of single entry at: " + at.getRest();
+          throw std::invalid_argument(what.toStdString());
+        }
+
+        params.insert("$directives", directives); // $ is not allowed in
+                                               // param names...
+      } while (!at.getEnd());
+    }
+    return result;
+  }
+
+}

+ 51 - 0
Libs/PluginFramework/ctkPluginFrameworkUtil_p.h

@@ -0,0 +1,51 @@
+/*=============================================================================
+
+  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 <QMap>
+#include <QStringList>
+
+namespace ctk {
+
+  class PluginFrameworkUtil
+  {
+  public:
+
+    /**
+       * Parse strings of format:
+       *
+       *   ENTRY (, ENTRY)*
+       *   ENTRY = key (; key)* (; PARAM)*
+       *   PARAM = attribute '=' value
+       *   PARAM = directive ':=' value
+       *
+       * @param a Attribute being parsed
+       * @param s String to parse
+       * @param single If true, only allow one key per ENTRY
+       * @param unique Only allow unique parameters for each ENTRY.
+       * @param single_entry If true, only allow one ENTRY is allowed.
+       * @return QMap<QString, QString> mapping attributes to values.
+       * @exception std::invalid_argument If syntax error in input string.
+       */
+      static QList<QMap<QString, QStringList> > parseEntries(const QString& a, const QString& s,
+                                                 bool single, bool unique, bool single_entry);
+  };
+
+}

+ 158 - 23
Libs/PluginFramework/ctkPluginPrivate.cxx

@@ -22,8 +22,11 @@
 #include "ctkPluginPrivate_p.h"
 
 #include "ctkPluginConstants.h"
+#include "ctkPluginDatabaseException.h"
 #include "ctkPluginArchive_p.h"
 #include "ctkPluginFrameworkContextPrivate_p.h"
+#include "ctkPluginFrameworkUtil_p.h"
+#include "ctkPluginActivator.h"
 
 namespace ctk {
 
@@ -36,67 +39,85 @@ namespace ctk {
       PluginArchive* pa)
     : q_ptr(&qq), fwCtx(fw), id(pa->getPluginId()),
     location(pa->getPluginLocation().toString()), state(Plugin::INSTALLED),
-    archive(pa), pluginContext(0), pluginActivator(0), lastModified(0),
-    lazyActivation(true), activating(false), deactivating(false),
-    resolveFailException("")
+    archive(pa), pluginContext(0), pluginActivator(0), pluginLoader(pa->getLibLocation()),
+    lastModified(0), eagerActivation(false), activating(false), deactivating(false)
   {
     //TODO
     //checkCertificates(pa);
 
     checkManifestHeaders();
+
+    // fill require list
+    QString requireString = archive->getAttribute(PluginConstants::REQUIRE_PLUGIN);
+    QList<QMap<QString, QStringList> > requireList = PluginFrameworkUtil::parseEntries(PluginConstants::REQUIRE_PLUGIN,
+                                                                         requireString, true, true, false);
+    QListIterator<QMap<QString, QStringList> > i(requireList);
+    while (i.hasNext())
+    {
+      const QMap<QString, QStringList>& e = i.next();
+      const QStringList& res = e.value(PluginConstants::RESOLUTION_DIRECTIVE);
+      const QStringList& version = e.value(PluginConstants::PLUGIN_VERSION_ATTRIBUTE);
+      RequirePlugin* rp = new RequirePlugin(this, e.value("$key").front(),
+                                            res.empty() ? QString() : res.front(),
+                                            version.empty() ? QString() : version.front());
+      require.push_back(rp);
+    }
   }
 
   PluginPrivate::PluginPrivate(Plugin& qq,
     PluginFrameworkContextPrivate* fw,
-    int id, const QString& loc, const QString& sym, const Version& ver)
+    long id, const QString& loc, const QString& sym, const Version& ver)
       : q_ptr(&qq), fwCtx(fw), id(id), location(loc), symbolicName(sym), version(ver),
       state(Plugin::INSTALLED), archive(0), pluginContext(0),
       pluginActivator(0), lastModified(0),
-      lazyActivation(true), activating(false), deactivating(false),
-      resolveFailException("")
+      eagerActivation(false), activating(false), deactivating(false)
   {
 
   }
 
   PluginPrivate::~PluginPrivate()
   {
-
-  }
-
-  QHash<QString, QString> PluginPrivate::getHeaders(const QString& locale)
-  {
-    return QHash<QString, QString>();
+    qDeleteAll(require);
   }
 
-  Plugin::States PluginPrivate::getUpdatedState()
+  Plugin::State PluginPrivate::getUpdatedState()
   {
     if (state & Plugin::INSTALLED)
     {
-      bool wasResolved = false;
       try
       {
         if (state == Plugin::INSTALLED)
         {
           fwCtx->resolvePlugin(this);
-          wasResolved = true;
           state = Plugin::RESOLVED;
+          fwCtx->listeners.emitPluginChanged(PluginEvent(PluginEvent::RESOLVED, this->q_func()));
         }
 
       }
       catch (const PluginException& pe)
       {
-        // TODO
-        //fwCtx.listeners.frameworkError(this, be);
+        Q_Q(Plugin);
+        this->fwCtx->listeners.frameworkError(q, pe);
+        throw;
       }
+    }
+
+    return state;
+  }
 
-      if (wasResolved)
+  void PluginPrivate::setAutostartSetting(const Plugin::StartOptions& setting) {
+    try
+    {
+      if (archive)
       {
-        // TODO
-        // fwCtx.listeners.bundleChanged(new BundleEvent(BundleEvent.RESOLVED, this));
+        archive->setAutostartSetting(setting);
       }
     }
-
-    return state;
+    catch (const PluginDatabaseException& e)
+    {
+      Q_Q(Plugin);
+      this->fwCtx->listeners.frameworkError(q, e);
+    }
   }
 
   void PluginPrivate::checkManifestHeaders()
@@ -126,10 +147,124 @@ namespace ctk {
     QString ap = archive->getAttribute(PluginConstants::PLUGIN_ACTIVATIONPOLICY);
     if (PluginConstants::ACTIVATION_EAGER == ap)
     {
-      lazyActivation = false;
+      eagerActivation = true;
+    }
+
+  }
+
+  void PluginPrivate::finalizeActivation()
+  {
+    switch (getUpdatedState())
+    {
+      case Plugin::INSTALLED:
+        // we shouldn't be here, getUpdatedState should have thrown
+        // an exception during resolving the plugin
+        throw PluginException("Internal error: expected exception on plugin resolve not thrown!");
+      case Plugin::STARTING:
+        if (activating) return; // finalization already in progress.
+        // Lazy activation; fall through to RESOLVED.
+      case Plugin::RESOLVED:
+        //6:
+        state = Plugin::STARTING;
+        activating = true;
+        qDebug() << "activating #" << this->id;
+        //7:
+        if (!pluginContext)
+        {
+          pluginContext = new PluginContext(this);
+        }
+        try
+        {
+          //TODO maybe call this in its own thread
+          start0();
+        }
+        catch (...)
+        {
+          //8:
+          state = Plugin::STOPPING;
+          // NYI, call outside lock
+          fwCtx->listeners.emitPluginChanged(PluginEvent(PluginEvent::STOPPING, this->q_func()));
+          removePluginResources();
+          delete pluginContext;
+
+          state = Plugin::RESOLVED;
+          // NYI, call outside lock
+          fwCtx->listeners.emitPluginChanged(PluginEvent(PluginEvent::STOPPED, this->q_func()));
+          activating = false;
+          throw;
+        }
+        activating = false;
+        break;
+      case Plugin::ACTIVE:
+        break;
+      case Plugin::STOPPING:
+        // This happens if start is called from inside the PluginActivator::stop method.
+        // Don't allow it.
+        throw PluginException("start called from PluginActivator::stop",
+                              PluginException::ACTIVATOR_ERROR);
+      case Plugin::UNINSTALLED:
+        throw std::logic_error("Plugin is in UNINSTALLED state");
+      }
+  }
+
+  void PluginPrivate::start0()
+  {
+    fwCtx->listeners.emitPluginChanged(PluginEvent(PluginEvent::STARTING, this->q_func()));
+
+    try {
+      pluginLoader.load();
+      if (!pluginLoader.isLoaded())
+      {
+        throw PluginException(QString("Loading plugin %1 failed: %2").arg(pluginLoader.fileName()).arg(pluginLoader.errorString()),
+                              PluginException::ACTIVATOR_ERROR);
+      }
+
+      pluginActivator = qobject_cast<PluginActivator*>(pluginLoader.instance());
+      if (!pluginActivator)
+      {
+        throw PluginException(QString("Creating PluginActivator instance from %1 failed: %2").arg(pluginLoader.fileName()).arg(pluginLoader.errorString()),
+                              PluginException::ACTIVATOR_ERROR);
+      }
+
+      pluginActivator->start(pluginContext);
+
+      if (Plugin::UNINSTALLED == state)
+      {
+        throw PluginException("Plugin uninstalled during start()", PluginException::STATECHANGE_ERROR);
+      }
+      state = Plugin::ACTIVE;
+    }
+    catch (const std::exception& e)
+    {
+      throw PluginException("Plugin start failed", PluginException::ACTIVATOR_ERROR, e);
     }
 
+    qDebug() << "activating #" << id << "completed.";
+
+    //10:
+    fwCtx->listeners.emitPluginChanged(PluginEvent(PluginEvent::STARTED, this->q_func()));
   }
 
+  void PluginPrivate::removePluginResources()
+  {
+    // automatic disconnect due to Qt signal slot
+    //fwCtx->listeners.removeAllListeners(this);
+
+    // TODO
+//    Set srs = fwCtx.services.getRegisteredByBundle(this);
+//    for (Iterator i = srs.iterator(); i.hasNext();) {
+//      try {
+//        ((ServiceRegistration)i.next()).unregister();
+//      } catch (IllegalStateException ignore) {
+//        // Someone has unregistered the service after stop completed.
+//        // This should not occur, but we don't want get stuck in
+//        // an illegal state so we catch it.
+//      }
+//    }
+//    Set s = fwCtx.services.getUsedByBundle(this);
+//    for (Iterator i = s.iterator(); i.hasNext(); ) {
+//      ((ServiceRegistrationImpl) i.next()).reference.ungetService(this, false);
+//    }
+  }
 
 }

+ 42 - 6
Libs/PluginFramework/ctkPluginPrivate_p.h

@@ -24,8 +24,10 @@
 
 #include "ctkPlugin.h"
 #include "ctkPluginException.h"
+#include "ctkRequirePlugin_p.h"
 
 #include <QHash>
+#include <QPluginLoader>
 
 namespace ctk {
 
@@ -63,7 +65,7 @@ namespace ctk {
      */
     PluginPrivate(Plugin& qq,
                   PluginFrameworkContextPrivate* fw,
-                  int id,
+                  long id,
                   const QString& loc,
                   const QString& sym,
                   const Version& ver);
@@ -76,10 +78,19 @@ namespace ctk {
      *
      * @return Plugin state
      */
-    Plugin::States getUpdatedState();
+    Plugin::State getUpdatedState();
 
-    QHash<QString, QString> getHeaders(const QString& locale);
+    /**
+     * Save the autostart setting to the persistent plugin storage.
+     *
+     * @param setting The autostart options to save.
+     */
+    void setAutostartSetting(const Plugin::StartOptions& setting);
 
+    /**
+     * Performs the actual activation.
+     */
+    void finalizeActivation();
 
     /**
      * Union of flags allowing plugin class access
@@ -91,7 +102,7 @@ namespace ctk {
     /**
      * Plugin identifier
      */
-    const int id;
+    const long id;
 
     /**
      * Plugin location identifier
@@ -129,6 +140,11 @@ namespace ctk {
     PluginActivator* pluginActivator;
 
     /**
+     * The Qt plugin loader for the plugin
+     */
+    QPluginLoader pluginLoader;
+
+    /**
      * Time when the plugin was last modified
      */
     long lastModified;
@@ -143,7 +159,11 @@ namespace ctk {
      */
     QHash<QString, QString> cachedRawHeaders;
 
-    bool lazyActivation;
+    /**
+     * True when this plugin has its activation policy
+     * set to "eager"
+     */
+    bool eagerActivation;
 
     /** True during the finalization of an activation. */
     bool activating;
@@ -152,15 +172,31 @@ namespace ctk {
     bool deactivating;
 
     /** Saved exception of resolve failure */
-    PluginException resolveFailException;
+    //PluginException resolveFailException;
+
+    /** List of RequirePlugin entries. */
+    QList<RequirePlugin*> require;
 
   private:
 
+    friend class PluginFrameworkContextPrivate;
+
     /**
      * Check manifest and cache certain manifest headers as variables.
      */
     void checkManifestHeaders();
 
+    // This could potentially be run in its own thread,
+    // parallelizing plugin activations
+    void start0();
+
+    /**
+     * Remove a plugins registered listeners, registered services and
+     * used services.
+     *
+     */
+    void removePluginResources();
+
 
   };
 

+ 3 - 3
Libs/PluginFramework/ctkPluginStorage.cxx

@@ -83,10 +83,10 @@ namespace ctk {
     QListIterator<PluginArchive*> i(archives);
     while(i.hasNext())
     {
-      PluginArchive* ba = i.next();
-      if (ba->getAutostartSetting() != -1)
+      PluginArchive* pa = i.next();
+      if (pa->getAutostartSetting() != -1)
       {
-        res.push_back(ba->getPluginLocation().toString());
+        res.push_back(pa->getPluginLocation().toString());
       }
     }
     return res;

+ 2 - 3
Libs/PluginFramework/ctkPlugins.cxx

@@ -330,12 +330,11 @@ namespace ctk {
       {
         try
         {
-          plugin->start();
+          plugin->start(0);
         }
         catch (const PluginException& pe)
         {
-          // TODO
-          // rb.fwCtx.listeners.frameworkError(rb, be);
+          pp->fwCtx->listeners.frameworkError(plugin, pe);
         }
       }
     }

+ 65 - 0
Libs/PluginFramework/ctkRequirePlugin.cxx

@@ -0,0 +1,65 @@
+/*=============================================================================
+
+  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 "ctkRequirePlugin_p.h"
+
+#include "ctkPluginConstants.h"
+#include "ctkPluginPrivate_p.h"
+
+namespace ctk {
+
+  RequirePlugin::RequirePlugin(PluginPrivate* requestor,
+                const QString& name, const QString& res,
+                const QString& range)
+                  : name(name),
+                  resolution(res.isEmpty() ? PluginConstants::RESOLUTION_MANDATORY : res),
+                  pluginRange(range.isEmpty() ? VersionRange::defaultVersionRange() : range)
+  {
+
+    if (resolution != PluginConstants::RESOLUTION_MANDATORY &&
+        resolution != PluginConstants::RESOLUTION_OPTIONAL )
+    {
+      QString what = QString("Invalid directive : '")
+                     + PluginConstants::RESOLUTION_DIRECTIVE + ":=" + this->resolution
+                     + "' in manifest header '"
+                     + PluginConstants::REQUIRE_PLUGIN + ": " + this->name
+                     + "' of plugin with id " + requestor->id
+                     + " (" + requestor->symbolicName + ")"
+                     + ". The value must be either '"
+                     + PluginConstants::RESOLUTION_MANDATORY + "' or '"
+                     + PluginConstants::RESOLUTION_OPTIONAL  + "'.";
+      throw std::invalid_argument(what.toStdString());
+      }
+
+
+  }
+
+  bool RequirePlugin::overlap(const RequirePlugin& rp) const
+  {
+    if (resolution == PluginConstants::RESOLUTION_MANDATORY &&
+        rp.resolution != PluginConstants::RESOLUTION_MANDATORY)
+    {
+      return false;
+    }
+    return pluginRange.withinRange(rp.pluginRange);
+  }
+
+}

+ 50 - 0
Libs/PluginFramework/ctkRequirePlugin_p.h

@@ -0,0 +1,50 @@
+/*=============================================================================
+
+  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 CTKREQUIREPLUGIN_P_H
+#define CTKREQUIREPLUGIN_P_H
+
+#include <ctkVersionRange_p.h>
+
+namespace ctk {
+
+  class PluginPrivate;
+
+  class RequirePlugin
+  {
+
+  public:
+
+    const QString name;
+    const QString resolution;
+    const VersionRange pluginRange;
+
+    RequirePlugin(PluginPrivate* requestor,
+                  const QString& name, const QString& res,
+                  const QString& range);
+
+    bool overlap(const RequirePlugin& rp) const;
+
+  };
+
+}
+
+#endif // CTKREQUIREPLUGIN_P_H

+ 5 - 0
Libs/PluginFramework/ctkServiceReference.cxx

@@ -25,6 +25,11 @@
 
 namespace ctk {
 
+  ServiceReference::ServiceReference(ServiceRegistrationPrivate* reg)
+  {
+
+  }
+
   QVariant ServiceReference::getProperty(const QString& key) const
   {
     return QVariant();

+ 8 - 0
Libs/PluginFramework/ctkServiceReference.h

@@ -28,6 +28,8 @@
 
 namespace ctk {
 
+  class ServiceRegistrationPrivate;
+
   class ServiceReference {
 
   public:
@@ -42,6 +44,12 @@ namespace ctk {
 
     bool operator<(const ServiceReference& reference) const;
 
+  protected:
+
+    friend class ServiceRegistrationPrivate;
+
+    ServiceReference(ServiceRegistrationPrivate* reg);
+
   };
 
 }

+ 80 - 34
Libs/PluginFramework/ctkServiceRegistration.cxx

@@ -23,6 +23,8 @@
 
 #include <QMutex>
 
+#include <stdexcept>
+
 namespace ctk {
 
   class ServiceRegistrationPrivate
@@ -86,7 +88,7 @@ namespace ctk {
 
     ServiceRegistrationPrivate(Plugin* plugin, QObject* service,
                                const PluginContext::ServiceProperties& props)
-                                 : plugin(plugint), service(service), reference(this),
+                                 : plugin(plugin), service(service), reference(this),
                                  properties(props), available(true), unregistering(false)
     {
 
@@ -108,49 +110,93 @@ namespace ctk {
 
   ServiceReference ServiceRegistration::getReference() const
   {
-    if (!available) throw std::logic_error("Service is unregistered");
+    Q_D(const ServiceRegistration);
+
+    if (!d->available) throw std::logic_error("Service is unregistered");
 
-    return reference;
+    return d->reference;
   }
 
   void ServiceRegistration::setProperties(const PluginContext::ServiceProperties& properties)
   {
-    QMutexLocker lock(eventLock);
-          Set before;
-          // TBD, optimize the locking of services
-          synchronized (bundle.fwCtx.services) {
-            synchronized (properties) {
-              if (available) {
-                // NYI! Optimize the MODIFIED_ENDMATCH code
-                Object old_rank = properties.get(Constants.SERVICE_RANKING);
-                before = bundle.fwCtx.listeners.getMatchingServiceListeners(reference);
-                String[] classes = (String[])properties.get(Constants.OBJECTCLASS);
-                Long sid = (Long)properties.get(Constants.SERVICE_ID);
-                properties = new PropertiesDictionary(props, classes, sid);
-                Object new_rank = properties.get(Constants.SERVICE_RANKING);
-                if (old_rank != new_rank && new_rank instanceof Integer &&
-                    !((Integer)new_rank).equals(old_rank)) {
-                  bundle.fwCtx.services.updateServiceRegistrationOrder(this, classes);
-                }
-              } else {
-                throw new IllegalStateException("Service is unregistered");
-              }
-            }
-          }
-          bundle.fwCtx.listeners
-            .serviceChanged(bundle.fwCtx.listeners.getMatchingServiceListeners(reference),
-                            new ServiceEvent(ServiceEvent.MODIFIED, reference),
-                            before);
-          bundle.fwCtx.listeners
-            .serviceChanged(before,
-                            new ServiceEvent(ServiceEvent.MODIFIED_ENDMATCH, reference),
-                            null);
+//    QMutexLocker lock(eventLock);
+//          Set before;
+//          // TBD, optimize the locking of services
+//          synchronized (bundle.fwCtx.services) {
+//
+//            synchronized (properties) {
+//              if (available) {
+//                // NYI! Optimize the MODIFIED_ENDMATCH code
+//                Object old_rank = properties.get(Constants.SERVICE_RANKING);
+//                before = bundle.fwCtx.listeners.getMatchingServiceListeners(reference);
+//                String[] classes = (String[])properties.get(Constants.OBJECTCLASS);
+//                Long sid = (Long)properties.get(Constants.SERVICE_ID);
+//                properties = new PropertiesDictionary(props, classes, sid);
+//                Object new_rank = properties.get(Constants.SERVICE_RANKING);
+//                if (old_rank != new_rank && new_rank instanceof Integer &&
+//                    !((Integer)new_rank).equals(old_rank)) {
+//                  bundle.fwCtx.services.updateServiceRegistrationOrder(this, classes);
+//                }
+//              } else {
+//                throw new IllegalStateException("Service is unregistered");
+//              }
+//            }
+//          }
+//          bundle.fwCtx.listeners
+//            .serviceChanged(bundle.fwCtx.listeners.getMatchingServiceListeners(reference),
+//                            new ServiceEvent(ServiceEvent.MODIFIED, reference),
+//                            before);
+//          bundle.fwCtx.listeners
+//            .serviceChanged(before,
+//                            new ServiceEvent(ServiceEvent.MODIFIED_ENDMATCH, reference),
+//                            null);
 
   }
 
   void ServiceRegistration::unregister() const
   {
-
+//    Q_D(ServiceRegistration);
+//
+//    if (d->unregistering) return; // Silently ignore redundant unregistration.
+//
+//        {
+//          QMutexLocker lock(eventLock);
+//          if (d->unregistering) return;
+//          d->unregistering = true;
+//
+//          if (d->available)
+//          {
+//            if (d->plugin)
+//            {
+//              d->plugin->fwCtx.services.removeServiceRegistration(this);
+//            }
+//          }
+//          else
+//          {
+//            throw std::logic_error("Service is unregistered");
+//          }
+//        }
+//
+//        if (d->plugin)
+//    {
+//          bundle.fwCtx.listeners
+//            .serviceChanged(bundle.fwCtx.listeners.getMatchingServiceListeners(reference),
+//                            new ServiceEvent(ServiceEvent.UNREGISTERING, reference),
+//                            null);
+//        }
+//        synchronized (eventLock) {
+//          synchronized (properties) {
+//            available = false;
+//            if (null!=bundle)
+//              bundle.fwCtx.perm.callUnregister0(this);
+//            bundle = null;
+//            dependents = null;
+//            reference = null;
+//            service = null;
+//            serviceInstances = null;
+//            unregistering = false;
+//          }
+//        }
   }
 
 }