/**
* 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 .
**/
#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
#include
#include
#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(params);
return OrthancPluginErrorCode_Success;
case _OrthancPluginService_LogWarning:
LOG(WARNING) << reinterpret_cast(params);
return OrthancPluginErrorCode_Success;
case _OrthancPluginService_LogInfo:
CLOG(INFO, PLUGINS) << reinterpret_cast(params);
return OrthancPluginErrorCode_Success;
case _OrthancPluginService_LogMessage:
{
const _OrthancPluginLogMessage& m = *reinterpret_cast(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(m.level);
Orthanc::Logging::LogCategory category = static_cast(m.category);
LOG_FROM_PLUGIN(level, category, m.plugin, m.file, m.line) << m.message;
return OrthancPluginErrorCode_Success;
}
default:
break;
}
Plugin* that = reinterpret_cast(context->pluginsManager);
for (std::list::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(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(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& 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);
}
}