379 lines
11 KiB
C++
379 lines
11 KiB
C++
/**
|
|
* Orthanc - A Lightweight, RESTful DICOM Store
|
|
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
* Department, University Hospital of Liege, Belgium
|
|
* Copyright (C) 2017-2023 Osimis S.A., Belgium
|
|
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
|
|
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
|
|
#include "../../Sources/PrecompiledHeadersServer.h"
|
|
#include "PluginsManager.h"
|
|
|
|
#if ORTHANC_ENABLE_PLUGINS != 1
|
|
#error The plugin support is disabled
|
|
#endif
|
|
|
|
#include "../../../OrthancFramework/Sources/HttpServer/HttpOutput.h"
|
|
#include "../../../OrthancFramework/Sources/Logging.h"
|
|
#include "../../../OrthancFramework/Sources/OrthancException.h"
|
|
#include "../../../OrthancFramework/Sources/Toolbox.h"
|
|
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <boost/filesystem.hpp>
|
|
|
|
#ifdef _WIN32
|
|
#define PLUGIN_EXTENSION ".dll"
|
|
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
|
#define PLUGIN_EXTENSION ".so"
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
#define PLUGIN_EXTENSION ".dylib"
|
|
#else
|
|
#error Support your platform here
|
|
#endif
|
|
|
|
|
|
namespace Orthanc
|
|
{
|
|
PluginsManager::Plugin::Plugin(PluginsManager& pluginManager,
|
|
const std::string& path) :
|
|
library_(path),
|
|
pluginManager_(pluginManager)
|
|
{
|
|
memset(&context_, 0, sizeof(context_));
|
|
context_.pluginsManager = this;
|
|
context_.orthancVersion = ORTHANC_VERSION;
|
|
context_.Free = ::free;
|
|
context_.InvokeService = InvokeService;
|
|
}
|
|
|
|
|
|
static void CallInitialize(SharedLibrary& plugin,
|
|
const OrthancPluginContext& context)
|
|
{
|
|
typedef int32_t (*Initialize) (const OrthancPluginContext*);
|
|
|
|
#if defined(_WIN32)
|
|
Initialize initialize = (Initialize) plugin.GetFunction("OrthancPluginInitialize");
|
|
#else
|
|
/**
|
|
* gcc would complain about "ISO C++ forbids casting between
|
|
* pointer-to-function and pointer-to-object" without the trick
|
|
* below, that is known as "the POSIX.1-2003 (Technical Corrigendum
|
|
* 1) workaround". See the man page of "dlsym()".
|
|
* http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
|
|
* http://stackoverflow.com/a/14543811/881731
|
|
**/
|
|
|
|
Initialize initialize;
|
|
*(void **) (&initialize) = plugin.GetFunction("OrthancPluginInitialize");
|
|
#endif
|
|
|
|
assert(initialize != NULL);
|
|
int32_t error = initialize(&context);
|
|
|
|
if (error != 0)
|
|
{
|
|
LOG(ERROR) << "Error while initializing plugin " << plugin.GetPath()
|
|
<< " (code " << error << ")";
|
|
throw OrthancException(ErrorCode_SharedLibrary);
|
|
}
|
|
}
|
|
|
|
|
|
static void CallFinalize(SharedLibrary& plugin)
|
|
{
|
|
typedef void (*Finalize) ();
|
|
|
|
#if defined(_WIN32)
|
|
Finalize finalize = (Finalize) plugin.GetFunction("OrthancPluginFinalize");
|
|
#else
|
|
Finalize finalize;
|
|
*(void **) (&finalize) = plugin.GetFunction("OrthancPluginFinalize");
|
|
#endif
|
|
|
|
assert(finalize != NULL);
|
|
finalize();
|
|
}
|
|
|
|
|
|
static const char* CallGetName(SharedLibrary& plugin)
|
|
{
|
|
typedef const char* (*GetName) ();
|
|
|
|
#if defined(_WIN32)
|
|
GetName getName = (GetName) plugin.GetFunction("OrthancPluginGetName");
|
|
#else
|
|
GetName getName;
|
|
*(void **) (&getName) = plugin.GetFunction("OrthancPluginGetName");
|
|
#endif
|
|
|
|
assert(getName != NULL);
|
|
return getName();
|
|
}
|
|
|
|
|
|
static const char* CallGetVersion(SharedLibrary& plugin)
|
|
{
|
|
typedef const char* (*GetVersion) ();
|
|
|
|
#if defined(_WIN32)
|
|
GetVersion getVersion = (GetVersion) plugin.GetFunction("OrthancPluginGetVersion");
|
|
#else
|
|
GetVersion getVersion;
|
|
*(void **) (&getVersion) = plugin.GetFunction("OrthancPluginGetVersion");
|
|
#endif
|
|
|
|
assert(getVersion != NULL);
|
|
return getVersion();
|
|
}
|
|
|
|
|
|
OrthancPluginErrorCode PluginsManager::InvokeService(OrthancPluginContext* context,
|
|
_OrthancPluginService service,
|
|
const void* params)
|
|
{
|
|
switch (service)
|
|
{
|
|
case _OrthancPluginService_LogError:
|
|
LOG(ERROR) << reinterpret_cast<const char*>(params);
|
|
return OrthancPluginErrorCode_Success;
|
|
|
|
case _OrthancPluginService_LogWarning:
|
|
LOG(WARNING) << reinterpret_cast<const char*>(params);
|
|
return OrthancPluginErrorCode_Success;
|
|
|
|
case _OrthancPluginService_LogInfo:
|
|
CLOG(INFO, PLUGINS) << reinterpret_cast<const char*>(params);
|
|
return OrthancPluginErrorCode_Success;
|
|
|
|
case _OrthancPluginService_LogMessage:
|
|
{
|
|
const _OrthancPluginLogMessage& m = *reinterpret_cast<const _OrthancPluginLogMessage*>(params);
|
|
// We can convert directly from OrthancPluginLogLevel to LogLevel (and category) because the enum values must be identical
|
|
// for Orthanc::Logging to work both in the core and in the plugins
|
|
Orthanc::Logging::LogLevel level = static_cast<Orthanc::Logging::LogLevel>(m.level);
|
|
Orthanc::Logging::LogCategory category = static_cast<Orthanc::Logging::LogCategory>(m.category);
|
|
|
|
LOG_FROM_PLUGIN(level, category, m.plugin, m.file, m.line) << m.message;
|
|
return OrthancPluginErrorCode_Success;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Plugin* that = reinterpret_cast<Plugin*>(context->pluginsManager);
|
|
|
|
for (std::list<IPluginServiceProvider*>::iterator
|
|
it = that->GetPluginManager().serviceProviders_.begin();
|
|
it != that->GetPluginManager().serviceProviders_.end(); ++it)
|
|
{
|
|
try
|
|
{
|
|
if ((*it)->InvokeService(that->GetSharedLibrary(), service, params))
|
|
{
|
|
return OrthancPluginErrorCode_Success;
|
|
}
|
|
}
|
|
catch (OrthancException& e)
|
|
{
|
|
// This service provider has failed
|
|
if (e.GetErrorCode() != ErrorCode_UnknownResource) // This error code is valid in plugins
|
|
{
|
|
if (!e.HasBeenLogged())
|
|
{
|
|
LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What();
|
|
}
|
|
}
|
|
|
|
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
|
|
}
|
|
}
|
|
|
|
LOG(ERROR) << "Plugin invoking unknown service: " << service;
|
|
return OrthancPluginErrorCode_UnknownPluginService;
|
|
}
|
|
|
|
|
|
PluginsManager::PluginsManager()
|
|
{
|
|
}
|
|
|
|
PluginsManager::~PluginsManager()
|
|
{
|
|
for (Plugins::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
|
|
{
|
|
if (it->second != NULL)
|
|
{
|
|
LOG(WARNING) << "Unregistering plugin '" << it->first
|
|
<< "' (version " << it->second->GetVersion() << ")";
|
|
|
|
CallFinalize(it->second->GetSharedLibrary());
|
|
delete it->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static bool IsOrthancPlugin(SharedLibrary& library)
|
|
{
|
|
return (library.HasFunction("OrthancPluginInitialize") &&
|
|
library.HasFunction("OrthancPluginFinalize") &&
|
|
library.HasFunction("OrthancPluginGetName") &&
|
|
library.HasFunction("OrthancPluginGetVersion"));
|
|
}
|
|
|
|
|
|
void PluginsManager::RegisterPlugin(const std::string& path)
|
|
{
|
|
if (!boost::filesystem::exists(path))
|
|
{
|
|
boost::filesystem::path p(path);
|
|
std::string extension = p.extension().string();
|
|
Toolbox::ToLowerCase(extension);
|
|
|
|
if (extension == PLUGIN_EXTENSION)
|
|
{
|
|
// if this is a plugin path, fail to start
|
|
throw OrthancException(ErrorCode_SharedLibrary, "Inexistent path to plugin: " + path);
|
|
}
|
|
else
|
|
{
|
|
// it might be a directory -> just log a warning
|
|
LOG(WARNING) << "Inexistent path to plugins: " << path;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (boost::filesystem::is_directory(path))
|
|
{
|
|
ScanFolderForPlugins(path, false);
|
|
return;
|
|
}
|
|
|
|
std::unique_ptr<Plugin> plugin(new Plugin(*this, path));
|
|
|
|
if (!IsOrthancPlugin(plugin->GetSharedLibrary()))
|
|
{
|
|
LOG(ERROR) << "Plugin " << plugin->GetSharedLibrary().GetPath()
|
|
<< " does not declare the proper entry functions";
|
|
throw OrthancException(ErrorCode_SharedLibrary);
|
|
}
|
|
|
|
std::string name(CallGetName(plugin->GetSharedLibrary()));
|
|
if (plugins_.find(name) != plugins_.end())
|
|
{
|
|
LOG(ERROR) << "Plugin '" << name << "' already registered";
|
|
throw OrthancException(ErrorCode_SharedLibrary);
|
|
}
|
|
|
|
plugin->SetVersion(CallGetVersion(plugin->GetSharedLibrary()));
|
|
LOG(WARNING) << "Registering plugin '" << name
|
|
<< "' (version " << plugin->GetVersion() << ")";
|
|
|
|
CallInitialize(plugin->GetSharedLibrary(), plugin->GetContext());
|
|
|
|
plugins_[name] = plugin.release();
|
|
}
|
|
|
|
|
|
void PluginsManager::ScanFolderForPlugins(const std::string& folder,
|
|
bool isRecursive)
|
|
{
|
|
using namespace boost::filesystem;
|
|
|
|
if (!exists(folder))
|
|
{
|
|
return;
|
|
}
|
|
|
|
CLOG(INFO, PLUGINS) << "Scanning folder " << folder << " for plugins";
|
|
|
|
directory_iterator end_it; // default construction yields past-the-end
|
|
for (directory_iterator it(folder);
|
|
it != end_it;
|
|
++it)
|
|
{
|
|
std::string path = it->path().string();
|
|
|
|
if (is_directory(it->status()))
|
|
{
|
|
if (isRecursive)
|
|
{
|
|
ScanFolderForPlugins(path, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::string extension = it->path().extension().string();
|
|
Toolbox::ToLowerCase(extension);
|
|
|
|
if (extension == PLUGIN_EXTENSION)
|
|
{
|
|
CLOG(INFO, PLUGINS) << "Found a shared library: " << it->path();
|
|
|
|
SharedLibrary plugin(path);
|
|
if (IsOrthancPlugin(plugin))
|
|
{
|
|
RegisterPlugin(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PluginsManager::ListPlugins(std::list<std::string>& result) const
|
|
{
|
|
result.clear();
|
|
|
|
for (Plugins::const_iterator it = plugins_.begin();
|
|
it != plugins_.end(); ++it)
|
|
{
|
|
result.push_back(it->first);
|
|
}
|
|
}
|
|
|
|
|
|
bool PluginsManager::HasPlugin(const std::string& name) const
|
|
{
|
|
return plugins_.find(name) != plugins_.end();
|
|
}
|
|
|
|
|
|
const std::string& PluginsManager::GetPluginVersion(const std::string& name) const
|
|
{
|
|
Plugins::const_iterator it = plugins_.find(name);
|
|
if (it == plugins_.end())
|
|
{
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
else
|
|
{
|
|
return it->second->GetVersion();
|
|
}
|
|
}
|
|
|
|
|
|
std::string PluginsManager::GetPluginName(SharedLibrary& library)
|
|
{
|
|
return CallGetName(library);
|
|
}
|
|
}
|