Fraxinus  17.12-rc1
An IGT application
cxPluginFramework.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include "cxPluginFramework.h"
34 
35 #include <QApplication>
36 #include <QStringList>
37 #include <QDirIterator>
38 #include <QFileInfo>
39 #include <QDebug>
40 
41 #include "ctkPluginFrameworkFactory.h"
42 #include "ctkPluginFramework.h"
43 #include "ctkPluginContext.h"
44 #include "ctkPluginException.h"
45 
46 #include <ctkConfig.h>
47 
48 #include "cxSettings.h"
49 #include "cxDataLocations.h"
51 
52 #include "cxFileHelpers.h"
53 #include "cxLogger.h"
54 #include <iostream>
55 #include "cxTypeConversions.h"
56 #include "cxProfile.h"
57 
58 namespace cx
59 {
60 
62 {
63  mSettingsBase = "pluginFramework";
64  mSettingsSearchPaths = mSettingsBase + "/searchPaths";
65 
66  ctkProperties fwProps;
67  QString storagePath = ProfileManager::getInstance()->getSettingsPath() + "/pluginFramework";
68 
69  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storagePath;
70 
71  // remove settings as stored by CTK, because full paths are stored here, causing
72  // problems when running both debug and release on the same machine (and similar).
73  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT;
74 
75  mFrameworkFactory.reset(new ctkPluginFrameworkFactory(fwProps));
76  mPluginLibFilter << "*.dll" << "*.so" << "*.dylib";
77 }
78 
80 {
81  if(mFramework->getState() == ctkPlugin::ACTIVE)//LogicManager calls stop() before the destructor is called
82  {
83  CX_LOG_CHANNEL_WARNING("plugin") << "This should not happen: PluginFrameworkManager destructor stopping plugin framework";
84  this->stop();
85  }
86 }
87 
88 QString PluginFrameworkManager::convertToRelativePath(QString path) const
89 {
90  QDir base = qApp->applicationDirPath();
91  return base.relativeFilePath(path);
92 }
93 
94 QString PluginFrameworkManager::convertToAbsolutePath(QString path) const
95 {
96  if (QDir(path).isAbsolute())
97  return QDir(path).absolutePath();
98 
99  QDir base = qApp->applicationDirPath();
100  return QDir(base.path() + "/" + path).absolutePath();
101 }
102 
103 
104 
105 std::vector<PluginFrameworkManager::PluginLoadInfo> PluginFrameworkManager::getPluginLoadInfo(QStringList symbolicNames)
106 {
107  std::vector<PluginLoadInfo> retval;
108 
109  for (unsigned i=0; i<symbolicNames.size(); ++i)
110  {
111  PluginLoadInfo info;
112  info.symbolicName = symbolicNames[i];
113  info.storedState = settings()->value(mSettingsBase+"/"+info.symbolicName).toString();
114 
115  if (info.storedState.isEmpty())
116  {
117  info.isNew = true;
118  info.storedState = getStringForctkPluginState(ctkPlugin::ACTIVE);
119  }
120  else
121  {
122  info.isNew = false;
123  }
124 
125  info.targetState = getctkPluginStateForString(info.storedState);
126 
127  retval.push_back(info);
128  }
129 
130  return retval;
131 }
132 
134 {
135  QStringList paths = settings()->value(mSettingsSearchPaths, QStringList()).toStringList();
136  this->setSearchPaths(paths);
137 
138  QStringList names = this->getPluginSymbolicNames();
139  std::vector<PluginLoadInfo> info = this->getPluginLoadInfo(names);
140 
141  // install all plugins, must do this first in order to let FW handle dependencies.
142  CX_LOG_CHANNEL_INFO("plugin") << "Installing all plugins...";
143  for (unsigned i=0; i< info.size(); ++i)
144  {
145  if (info[i].targetState != ctkPlugin::UNINSTALLED)
146  {
147  this->install(info[i].symbolicName);
148  }
149  }
150 
151  // start all plugins
152  for (unsigned i=0; i< info.size(); ++i)
153  {
154  if (info[i].targetState == ctkPlugin::ACTIVE)
155  {
156  if (info[i].isNew)
157  CX_LOG_CHANNEL_INFO("plugin") << QString("Autostarting plugin %1").arg(info[i].symbolicName);
158  else
159  CX_LOG_CHANNEL_INFO("plugin") << QString("Starting plugin %1").arg(info[i].symbolicName);
160 
161  this->start(info[i].symbolicName, ctkPlugin::START_TRANSIENT);
162  }
163  else
164  {
165  CX_LOG_CHANNEL_INFO("plugin") << QString("Set plugin to state [%2]: %1")
166  .arg(info[i].symbolicName)
167  .arg(info[i].storedState);
168  }
169  }
170 
171 }
172 
173 void PluginFrameworkManager::saveState()
174 {
175  QStringList relativePaths;
176  for (int i=0; i<mPluginSearchPaths.size(); ++i)
177  relativePaths << this->convertToRelativePath(mPluginSearchPaths[i]);
178  settings()->setValue(mSettingsSearchPaths, relativePaths);
179 
180  QStringList names = this->getPluginSymbolicNames();
181  for (unsigned i=0; i<names.size(); ++i)
182  {
183  QString name = names[i];
184  ctkPlugin::State state = this->getStateFromSymbolicName(name);
185  settings()->setValue(mSettingsBase+"/"+name, getStringForctkPluginState(state));
186  }
187 }
188 
190 {
191  ctkPlugin::State state = ctkPlugin::UNINSTALLED;
192  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(name);
193  if (plugin)
194  state = plugin->getState();
195  return state;
196 }
197 
198 void PluginFrameworkManager::setSearchPaths(const QStringList& searchPath)
199 {
200  mPluginSearchPaths.clear();
201 
202  for (int i=0; i<searchPath.size(); ++i)
203  mPluginSearchPaths << this->convertToAbsolutePath(searchPath[i]);
204 
205  QStringList defPaths = DataLocations::getDefaultPluginsPath();
206  for (unsigned i=0; i<defPaths.size(); ++i)
207  {
208  QString defPath = this->convertToAbsolutePath(defPaths[i]);
209  if (!mPluginSearchPaths.count(defPath))
210  mPluginSearchPaths << defPath;
211  }
212 
213  mPluginSearchPaths.removeDuplicates();
214 
215  for (int i=0; i<searchPath.size(); ++i)
216  {
217  QApplication::addLibraryPath(searchPath[i]);
218  }
219  emit pluginPoolChanged();
220 }
221 
222 
224 {
225  return mPluginSearchPaths;
226 }
227 
229 {
230  return mFramework->getPluginContext();
231 }
232 
233 QSharedPointer<ctkPluginFramework> PluginFrameworkManager::getPluginFramework()
234 {
235  return mFramework;
236 }
237 
238 void PluginFrameworkManager::initializeFramework()
239 {
240  if (this->frameworkInitialized())
241  {
242  return;
243  }
244 
245  QSharedPointer<ctkPluginFramework> framework = mFrameworkFactory->getFramework();
246 
247  try
248  {
249  framework->init();
250  } catch (const ctkException& exc)
251  {
252  this->handlePluginException("Failed to initialize the plug-in framework", exc);
253  }
254  mFramework = framework;
255 }
256 
257 bool PluginFrameworkManager::frameworkInitialized() const
258 {
259  return mFramework != 0;
260 }
261 
262 bool PluginFrameworkManager::frameworkStarted() const
263 {
264  return mFramework && (mFramework->getState() == ctkPlugin::ACTIVE);
265 }
266 
267 void PluginFrameworkManager::startFramework()
268 {
269  if (!this->frameworkInitialized())
270  this->initializeFramework();
271 
272  if (this->frameworkStarted())
273  return;
274 
275  try
276  {
277  mFramework->start();
278  }
279  catch (const ctkException& exc)
280  {
281  this->handlePluginException("Failed to start the plug-in framework", exc);
282  }
283 }
284 
285 void PluginFrameworkManager::install(const QString& symbolicName)
286 {
287  this->initializeFramework();
288  if (!this->frameworkInitialized())
289  return;
290 
291  QString pluginPath = this->getPluginPath(symbolicName);
292  if (pluginPath.isEmpty())
293  return;
294 
295  try
296  {
297  ctkPluginContext* pc = this->getPluginContext();
298  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->getPluginId();
299  }
300  catch (const ctkException& exc)
301  {
302  this->handlePluginException(QString("Failed to install plugin %1").arg(symbolicName), exc);
303  }
304 }
305 
307 {
308  this->startFramework();
309  return this->frameworkStarted();
310 }
311 
313 {
314  this->saveState();
315 
316  // give plugins time to clean up internal resources before different thread deletes them
317  // (obsolete because we have disabled the other-thread shutdown)
318  emit aboutToStop();
319 
320  // Bypass CTK internal 'shutdown in another thread'-mechanism, activated if we
321  // call framework::stop(). It causes too much trouble regarding Qt objects created
322  // in main thread and deleted in another thread. openCV also has trouble.
323  QStringList plugins = getPluginSymbolicNames();
324  for (int i=0; i<plugins.size(); ++i)
325  {
326  this->stop(plugins[i]);
327  }
328 
329  // stop the framework
330  try
331  {
332  mFramework->stop(); //will start a thread that destructs plugins
333  ctkPluginFrameworkEvent fe = mFramework->waitForStop(5000);
334 // int timeout = 5000;
335 // int interval = 50;
336 // ctkPluginFrameworkEvent fe;
337 // for(int i=0; i<timeout/interval; ++i)
338 // {
339 // fe = mFramework->waitForStop(interval);
340 // qApp->processEvents();
341 // }
342  if (fe.getType() == ctkPluginFrameworkEvent::FRAMEWORK_WAIT_TIMEDOUT)
343  {
344  CX_LOG_CHANNEL_WARNING("plugin") << "Stopping the plugin framework timed out";
345  return false;
346  }
347  }
348  catch (const ctkException& exc)
349  {
350  this->handlePluginException("Failed to stop the plug-in framework", exc);
351  return false;
352  }
353  return !this->frameworkStarted();
354 }
355 
356 void PluginFrameworkManager::uninstall(const QString& symbolicName)
357 {
358  QString pluginPath = getPluginPath(symbolicName);
359  if (pluginPath.isEmpty())
360  return;
361 
362  try
363  {
364  ctkPluginContext* pc = this->getPluginContext();
365  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->uninstall();
366  }
367  catch (const ctkException& exc)
368  {
369  this->handlePluginException(QString("Failed to uninstall plugin %1.").arg(symbolicName), exc);
370  return;
371  }
372 
373  return;
374 }
375 
376 bool PluginFrameworkManager::start(const QString& symbolicName, ctkPlugin::StartOptions options)
377 {
378  this->startFramework();
379 
380  QString pluginPath = getPluginPath(symbolicName);
381  if (pluginPath.isEmpty())
382  {
383  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to find plugin %1 in search path.").arg(symbolicName);
384  return false;
385  }
386 
387  try
388  {
389  ctkPluginContext* pc = this->getPluginContext();
390  QSharedPointer<ctkPlugin> plugin = pc->installPlugin(QUrl::fromLocalFile(pluginPath));
391  plugin->start(options);
392  }
393  catch (ctkException& exc)
394  {
395  this->handlePluginException(QString("Failed to stop plugin %1.").arg(symbolicName), exc);
396  return false;
397  }
398 
399  return true;
400 }
401 
402 void PluginFrameworkManager::handlePluginException(const QString& message, const ctkException& exc)
403 {
404  CX_LOG_CHANNEL_ERROR("plugin") << message;
405  const ctkException* nest = &exc;
406  while (nest)
407  {
408  CX_LOG_CHANNEL_ERROR("plugin") << QString(" cause: %1").arg(nest->what());
409  nest = nest->cause();
410  }
411 }
412 
413 bool PluginFrameworkManager::stop(const QString& symbolicName, ctkPlugin::StopOptions options)
414 {
415  if (!this->frameworkStarted())
416  return false;
417  QString pluginPath = this->getPluginPath(symbolicName);
418  if (pluginPath.isEmpty())
419  return false;
420 
421  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(symbolicName);
422 
423  if (!plugin)
424  {
425  CX_LOG_CHANNEL_WARNING("plugin") << QString("Plugin: %1 not found").arg(symbolicName);
426  return false;
427  }
428 
429  try
430  {
431  plugin->stop(options);
432  }
433  catch (const ctkException& exc)
434  {
435  this->handlePluginException(QString("Failed to stop plugin %1").arg(symbolicName), exc);
436  return false;
437  }
438 
439  return true;
440 }
441 
442 QSharedPointer<ctkPlugin> PluginFrameworkManager::getInstalledPluginFromSymbolicName(QString symbolicName)
443 {
444  QSharedPointer<ctkPlugin> empty;
445 
446  if (!this->frameworkInitialized())
447  return empty;
448 
449  QString pluginPath = this->getPluginPath(symbolicName);
450  if (pluginPath.isEmpty())
451  return empty;
452 
453  ctkPluginContext* pc = this->getPluginContext();
454  QList < QSharedPointer<ctkPlugin> > plugins = pc->getPlugins();
455  foreach(QSharedPointer<ctkPlugin> plugin, plugins)
456  {
457  if (plugin->getSymbolicName() == symbolicName)
458  {
459  return plugin;
460  }
461  }
462 
463  return empty;
464 }
465 
466 
467 QString PluginFrameworkManager::getPluginPath(const QString& symbolicName)
468 {
469  QString pluginFileName(symbolicName);
470  pluginFileName.replace(".", "_");
471  foreach(QString searchPath, mPluginSearchPaths)
472  {
473  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
474  while(dirIter.hasNext())
475  {
476  dirIter.next();
477  QFileInfo fileInfo = dirIter.fileInfo();
478  QString fileBaseName = fileInfo.baseName();
479  if (fileBaseName.startsWith("lib")) fileBaseName = fileBaseName.mid(3);
480 
481  if (fileBaseName == pluginFileName)
482  {
483  return fileInfo.canonicalFilePath();
484  }
485  }
486  }
487 
488  return QString();
489 }
490 
492 {
493  QStringList result;
494  foreach(QString searchPath, mPluginSearchPaths)
495  {
496  result.append(this->getPluginSymbolicNames(searchPath));
497  }
498  result.removeDuplicates();
499  return result;
500 }
501 
502 QStringList PluginFrameworkManager::getPluginSymbolicNames(const QString& searchPath)
503 {
504  QStringList result;
505  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
506  while (dirIter.hasNext())
507  {
508  dirIter.next();
509  QFileInfo fileInfo = dirIter.fileInfo();
510  QString fileBaseName = fileInfo.baseName();
511  if (fileBaseName.startsWith("lib"))
512  fileBaseName = fileBaseName.mid(3);
513  QString name = fileBaseName.replace("_", ".");
514  if (this->nameIsProbablyPlugin(name))
515  result << name;
516  }
517 
518  return result;
519 }
520 
521 bool PluginFrameworkManager::nameIsProbablyPlugin(QString name) const
522 {
523  // heuristic check for plugin-ish name
524  return name.startsWith("org.custusx");
525 }
526 
527 } /* namespace cx */
#define CX_LOG_CHANNEL_INFO(channel)
Definition: cxLogger.h:129
ctkPlugin::State getStateFromSymbolicName(QString name)
#define CX_LOG_CHANNEL_WARNING(channel)
Definition: cxLogger.h:131
QStringList getSearchPaths() const
static ProfileManager * getInstance(QString defaultProfile=QString("Laboratory"))
returns the only instance of this class
Definition: cxProfile.cpp:188
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Definition: cxSettings.cpp:87
QSharedPointer< ctkPluginFramework > getPluginFramework()
void uninstall(const QString &symbolicName)
void install(const QString &symbolicName)
QSharedPointer< ctkPlugin > getInstalledPluginFromSymbolicName(QString symbolicName)
void setValue(const QString &key, const QVariant &value)
Definition: cxSettings.cpp:79
ctkPluginContext * getPluginContext()
Settings * settings()
Shortcut for accessing the settings instance.
Definition: cxSettings.cpp:42
static QStringList getDefaultPluginsPath()
return the folder where plugins should be located, by default.
QString getStringForctkPluginState(const ctkPlugin::State state)
QString getSettingsPath()
Definition: cxProfile.cpp:240
ctkPlugin::State getctkPluginStateForString(QString text)
#define CX_LOG_CHANNEL_ERROR(channel)
Definition: cxLogger.h:132
void setSearchPaths(const QStringList &searchPath)
Namespace for all CustusX production code.