Orthanc/OrthancServer/Plugins/Engine/OrthancPlugins.cpp
2025-06-23 19:07:37 +05:30

6795 lines
208 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 "OrthancPlugins.h"
#if ORTHANC_ENABLE_PLUGINS != 1
#error The plugin support is disabled
#endif
#if !defined(DCMTK_VERSION_NUMBER)
# error The macro DCMTK_VERSION_NUMBER must be defined
#endif
#include "../../../OrthancFramework/Sources/Compression/GzipCompressor.h"
#include "../../../OrthancFramework/Sources/Compression/ZlibCompressor.h"
#include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
#include "../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h"
#include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
#include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h"
#include "../../../OrthancFramework/Sources/FileStorage/PluginStorageAreaAdapter.h"
#include "../../../OrthancFramework/Sources/HttpServer/HttpServer.h"
#include "../../../OrthancFramework/Sources/HttpServer/HttpToolbox.h"
#include "../../../OrthancFramework/Sources/Images/Image.h"
#include "../../../OrthancFramework/Sources/Images/ImageProcessing.h"
#include "../../../OrthancFramework/Sources/Images/JpegReader.h"
#include "../../../OrthancFramework/Sources/Images/JpegWriter.h"
#include "../../../OrthancFramework/Sources/Images/PngReader.h"
#include "../../../OrthancFramework/Sources/Images/PngWriter.h"
#include "../../../OrthancFramework/Sources/Logging.h"
#include "../../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
#include "../../../OrthancFramework/Sources/MallocMemoryBuffer.h"
#include "../../../OrthancFramework/Sources/MetricsRegistry.h"
#include "../../../OrthancFramework/Sources/OrthancException.h"
#include "../../../OrthancFramework/Sources/SerializationToolbox.h"
#include "../../../OrthancFramework/Sources/Toolbox.h"
#include "../../Sources/Database/VoidDatabaseListener.h"
#include "../../Sources/OrthancConfiguration.h"
#include "../../Sources/OrthancFindRequestHandler.h"
#include "../../Sources/Search/HierarchicalMatcher.h"
#include "../../Sources/ServerContext.h"
#include "../../Sources/ServerToolbox.h"
#include "OrthancPluginDatabase.h"
#include "OrthancPluginDatabaseV3.h"
#include "OrthancPluginDatabaseV4.h"
#include "PluginMemoryBuffer32.h"
#include "PluginsEnumerations.h"
#include "PluginsJob.h"
#include <boost/math/special_functions/round.hpp>
#include <boost/regex.hpp>
#include <dcmtk/dcmdata/dcdicent.h>
#include <dcmtk/dcmnet/dimse.h>
#define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc SDK is necessary to use buffers > 4GB, but is currently not available"
namespace Orthanc
{
class OrthancPlugins::IDicomInstance : public boost::noncopyable
{
public:
virtual ~IDicomInstance()
{
}
virtual bool CanBeFreed() const = 0;
virtual const DicomInstanceToStore& GetInstance() const = 0;
};
class OrthancPlugins::DicomInstanceFromCallback : public IDicomInstance
{
private:
const DicomInstanceToStore& instance_;
public:
explicit DicomInstanceFromCallback(const DicomInstanceToStore& instance) :
instance_(instance)
{
}
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
{
return false;
}
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
{
return instance_;
};
};
class OrthancPlugins::DicomInstanceFromBuffer : public IDicomInstance
{
private:
std::string buffer_;
std::unique_ptr<DicomInstanceToStore> instance_;
void Setup(const void* buffer,
size_t size)
{
buffer_.assign(reinterpret_cast<const char*>(buffer), size);
instance_.reset(DicomInstanceToStore::CreateFromBuffer(buffer_));
instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
}
public:
DicomInstanceFromBuffer(const void* buffer,
size_t size)
{
Setup(buffer, size);
}
explicit DicomInstanceFromBuffer(const std::string& buffer)
{
Setup(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
}
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
{
return true;
}
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
{
return *instance_;
};
};
class OrthancPlugins::DicomInstanceFromParsed : public IDicomInstance
{
private:
std::unique_ptr<ParsedDicomFile> parsed_;
std::unique_ptr<DicomInstanceToStore> instance_;
void Setup(ParsedDicomFile* parsed)
{
parsed_.reset(parsed);
if (parsed_.get() == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
instance_.reset(DicomInstanceToStore::CreateFromParsedDicomFile(*parsed_));
instance_->SetOrigin(DicomInstanceOrigin::FromPlugins());
}
}
public:
explicit DicomInstanceFromParsed(IDicomTranscoder::DicomImage& transcoded)
{
Setup(transcoded.ReleaseAsParsedDicomFile());
}
explicit DicomInstanceFromParsed(ParsedDicomFile* parsed /* takes ownership */)
{
Setup(parsed);
}
virtual bool CanBeFreed() const ORTHANC_OVERRIDE
{
return true;
}
virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
{
return *instance_;
};
};
class OrthancPlugins::WebDavCollection : public IWebDavBucket
{
private:
PluginsErrorDictionary& errorDictionary_;
std::string uri_;
OrthancPluginWebDavIsExistingFolderCallback isExistingFolder_;
OrthancPluginWebDavListFolderCallback listFolder_;
OrthancPluginWebDavRetrieveFileCallback retrieveFile_;
OrthancPluginWebDavStoreFileCallback storeFile_;
OrthancPluginWebDavCreateFolderCallback createFolder_;
OrthancPluginWebDavDeleteItemCallback deleteItem_;
void* payload_;
class PathHelper : public boost::noncopyable
{
private:
std::vector<const char*> items_;
public:
explicit PathHelper(const std::vector<std::string>& path)
{
items_.resize(path.size());
for (size_t i = 0; i < path.size(); i++)
{
items_[i] = path[i].c_str();
}
}
uint32_t GetSize() const
{
return static_cast<uint32_t>(items_.size());
}
const char* const* GetItems() const
{
return (items_.empty() ? NULL : &items_[0]);
}
};
static MimeType ParseMimeType(const char* mimeType)
{
MimeType mime;
if (LookupMimeType(mime, mimeType))
{
return mime;
}
else
{
LOG(WARNING) << "Unknown MIME type in plugin: " << mimeType;
return MimeType_Binary;
}
}
static OrthancPluginErrorCode AddFile(
OrthancPluginWebDavCollection* collection,
const char* displayName,
uint64_t contentSize,
const char* mimeType,
const char* creationTime)
{
try
{
std::unique_ptr<File> f(new File(displayName));
f->SetCreationTime(boost::posix_time::from_iso_string(creationTime));
f->SetContentLength(contentSize);
if (mimeType == NULL ||
std::string(mimeType).empty())
{
f->SetMimeType(SystemToolbox::AutodetectMimeType(displayName));
}
else
{
f->SetMimeType(ParseMimeType(mimeType));
}
reinterpret_cast<Collection*>(collection)->AddResource(f.release());
return OrthancPluginErrorCode_Success;
}
catch (OrthancException& e)
{
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
}
catch (...)
{
return OrthancPluginErrorCode_InternalError;
}
}
static OrthancPluginErrorCode AddFolder(
OrthancPluginWebDavCollection* collection,
const char* displayName,
const char* creationTime)
{
try
{
std::unique_ptr<Folder> f(new Folder(displayName));
f->SetCreationTime(boost::posix_time::from_iso_string(creationTime));
reinterpret_cast<Collection*>(collection)->AddResource(f.release());
return OrthancPluginErrorCode_Success;
}
catch (OrthancException& e)
{
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
}
catch (boost::bad_lexical_cast&)
{
LOG(ERROR) << "Presumably ill-formed date in the plugin";
return OrthancPluginErrorCode_ParameterOutOfRange;
}
catch (...)
{
return OrthancPluginErrorCode_InternalError;
}
}
class ContentTarget : public boost::noncopyable
{
private:
bool isSent_;
MimeType& mime_;
std::string& content_;
boost::posix_time::ptime& modificationTime_;
public:
ContentTarget(const std::string& displayName,
MimeType& mime,
std::string& content,
boost::posix_time::ptime& modificationTime) :
isSent_(false),
mime_(mime),
content_(content),
modificationTime_(modificationTime)
{
mime = SystemToolbox::AutodetectMimeType(displayName);
}
bool IsSent() const
{
return isSent_;
}
static OrthancPluginErrorCode RetrieveFile(
OrthancPluginWebDavCollection* collection,
const void* data,
uint64_t size,
const char* mimeType,
const char* creationTime)
{
ContentTarget& target = *reinterpret_cast<ContentTarget*>(collection);
if (target.isSent_)
{
return OrthancPluginErrorCode_BadSequenceOfCalls;
}
else
{
try
{
target.isSent_ = true;
if (mimeType != NULL &&
!std::string(mimeType).empty())
{
target.mime_ = ParseMimeType(mimeType);
}
target.content_.assign(reinterpret_cast<const char*>(data), size);
target.modificationTime_ = boost::posix_time::from_iso_string(creationTime);
return OrthancPluginErrorCode_Success;
}
catch (Orthanc::OrthancException& e)
{
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
}
catch (boost::bad_lexical_cast&)
{
LOG(ERROR) << "Presumably ill-formed date in the plugin";
return OrthancPluginErrorCode_ParameterOutOfRange;
}
catch (...)
{
return OrthancPluginErrorCode_InternalError;
}
}
}
};
public:
WebDavCollection(PluginsErrorDictionary& errorDictionary,
const _OrthancPluginRegisterWebDavCollection& p) :
errorDictionary_(errorDictionary),
uri_(p.uri),
isExistingFolder_(p.isExistingFolder),
listFolder_(p.listFolder),
retrieveFile_(p.retrieveFile),
storeFile_(p.storeFile),
createFolder_(p.createFolder),
deleteItem_(p.deleteItem),
payload_(p.payload)
{
}
const std::string& GetUri() const
{
return uri_;
}
virtual bool IsExistingFolder(const std::vector<std::string>& path)
{
PathHelper helper(path);
uint8_t isExisting;
OrthancPluginErrorCode code = isExistingFolder_(&isExisting, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isExisting != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual bool ListCollection(Collection& collection,
const std::vector<std::string>& path)
{
PathHelper helper(path);
uint8_t isExisting;
OrthancPluginErrorCode code = listFolder_(&isExisting, reinterpret_cast<OrthancPluginWebDavCollection*>(&collection),
AddFile, AddFolder, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isExisting != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual bool GetFileContent(MimeType& mime,
std::string& content,
boost::posix_time::ptime& modificationTime,
const std::vector<std::string>& path)
{
PathHelper helper(path);
ContentTarget target(path.back(), mime, content, modificationTime);
OrthancPluginErrorCode code = retrieveFile_(
reinterpret_cast<OrthancPluginWebDavCollection*>(&target),
ContentTarget::RetrieveFile, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return target.IsSent();
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual bool StoreFile(const std::string& content,
const std::vector<std::string>& path)
{
PathHelper helper(path);
uint8_t isReadOnly;
OrthancPluginErrorCode code = storeFile_(&isReadOnly, helper.GetSize(), helper.GetItems(),
content.empty() ? NULL : content.c_str(), content.size(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isReadOnly != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual bool CreateFolder(const std::vector<std::string>& path)
{
PathHelper helper(path);
uint8_t isReadOnly;
OrthancPluginErrorCode code = createFolder_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isReadOnly != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual bool DeleteItem(const std::vector<std::string>& path)
{
PathHelper helper(path);
uint8_t isReadOnly;
OrthancPluginErrorCode code = deleteItem_(&isReadOnly, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isReadOnly != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
virtual void Start()
{
}
virtual void Stop()
{
}
};
static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer* target,
const void* data,
size_t size)
{
PluginMemoryBuffer32 buffer;
buffer.Assign(data, size);
buffer.Release(target);
}
static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer* target,
const std::string& str)
{
PluginMemoryBuffer32 buffer;
buffer.Assign(str);
buffer.Release(target);
}
static char* CopyString(const std::string& str)
{
char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
if (result == NULL)
{
throw OrthancException(ErrorCode_NotEnoughMemory);
}
if (!str.empty())
{
memcpy(result, str.c_str(), str.size());
}
result[str.size()] = '\0'; // Add the null terminator of the string
return result;
}
static void CopyDictionary(PluginMemoryBuffer32& target,
const std::map<std::string, std::string>& dictionary)
{
Json::Value json = Json::objectValue;
for (HttpClient::HttpHeaders::const_iterator
it = dictionary.begin(); it != dictionary.end(); ++it)
{
json[it->first] = it->second;
}
target.Assign(json.toStyledString());
}
namespace
{
static IMemoryBuffer* GetRangeFromWhole(std::unique_ptr<IMemoryBuffer>& whole,
uint64_t start /* inclusive */,
uint64_t end /* exclusive */)
{
if (start > end)
{
throw OrthancException(ErrorCode_BadRange);
}
else if (start == end)
{
return new PluginMemoryBuffer64; // Empty
}
else
{
if (start == 0 &&
end == whole->GetSize())
{
return whole.release();
}
else if (end > whole->GetSize())
{
throw OrthancException(ErrorCode_BadRange);
}
else
{
std::unique_ptr<PluginMemoryBuffer64> range(new PluginMemoryBuffer64);
range->Assign(reinterpret_cast<const char*>(whole->GetData()) + start, end - start);
assert(range->GetSize() > 0);
return range.release();
}
}
}
// "legacy" storage plugins don't store customData -> derive from IStorageArea
class StorageAreaWithoutCustomData : public IStorageArea
{
private:
OrthancPluginStorageCreate create_;
OrthancPluginStorageRemove remove_;
PluginsErrorDictionary& errorDictionary_;
protected:
PluginsErrorDictionary& GetErrorDictionary() const
{
return errorDictionary_;
}
public:
StorageAreaWithoutCustomData(OrthancPluginStorageCreate create,
OrthancPluginStorageRemove remove,
PluginsErrorDictionary& errorDictionary) :
create_(create),
remove_(remove),
errorDictionary_(errorDictionary)
{
if (create_ == NULL ||
remove_ == NULL)
{
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement all the required primitives");
}
}
virtual void Create(const std::string& uuid,
const void* content,
size_t size,
FileContentType type) ORTHANC_OVERRIDE
{
OrthancPluginErrorCode error = create_
(uuid.c_str(), content, size, Plugins::Convert(type));
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
virtual void Remove(const std::string& uuid,
FileContentType type) ORTHANC_OVERRIDE
{
OrthancPluginErrorCode error = remove_
(uuid.c_str(), Plugins::Convert(type));
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
};
class PluginStorageAreaV1 : public StorageAreaWithoutCustomData
{
private:
OrthancPluginStorageRead read_;
OrthancPluginFree free_;
public:
PluginStorageAreaV1(const _OrthancPluginRegisterStorageArea& callbacks,
PluginsErrorDictionary& errorDictionary) :
StorageAreaWithoutCustomData(callbacks.create, callbacks.remove, errorDictionary),
read_(callbacks.read),
free_(callbacks.free)
{
if (read_ == NULL)
{
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement the \"Read\" primitive");
}
}
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
FileContentType type,
uint64_t start /* inclusive */,
uint64_t end /* exclusive */) ORTHANC_OVERRIDE
{
std::unique_ptr<IMemoryBuffer> whole(new MallocMemoryBuffer);
void* buffer = NULL;
int64_t size = 0;
OrthancPluginErrorCode error = read_(&buffer, &size, uuid.c_str(), Plugins::Convert(type));
if (error == OrthancPluginErrorCode_Success)
{
// Beware that the buffer must be unallocated by the "free_" function provided by the plugin,
// so we cannot use "PluginMemoryBuffer64"
dynamic_cast<MallocMemoryBuffer&>(*whole).Assign(buffer, size, free_);
return GetRangeFromWhole(whole, start, end);
}
else
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
{
return false;
}
};
// New in Orthanc 1.9.0
class PluginStorageAreaV2 : public StorageAreaWithoutCustomData
{
private:
OrthancPluginStorageReadWhole readWhole_;
OrthancPluginStorageReadRange readRange_;
public:
PluginStorageAreaV2(const _OrthancPluginRegisterStorageArea2& callbacks,
PluginsErrorDictionary& errorDictionary) :
StorageAreaWithoutCustomData(callbacks.create, callbacks.remove, errorDictionary),
readWhole_(callbacks.readWhole),
readRange_(callbacks.readRange)
{
if (readWhole_ == NULL)
{
throw OrthancException(ErrorCode_Plugin, "Storage area plugin doesn't implement the \"ReadWhole\" primitive");
}
}
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
FileContentType type,
uint64_t start /* inclusive */,
uint64_t end /* exclusive */) ORTHANC_OVERRIDE
{
if (readRange_ == NULL)
{
std::unique_ptr<IMemoryBuffer> whole(new PluginMemoryBuffer64);
OrthancPluginErrorCode error = readWhole_(dynamic_cast<PluginMemoryBuffer64&>(*whole).GetObject(),
uuid.c_str(), Plugins::Convert(type));
if (error == OrthancPluginErrorCode_Success)
{
return GetRangeFromWhole(whole, start, end);
}
else
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
else
{
std::unique_ptr<PluginMemoryBuffer64> buffer(new PluginMemoryBuffer64);
if (start > end)
{
throw OrthancException(ErrorCode_BadRange);
}
else if (start == end)
{
return buffer.release();
}
else
{
buffer->Resize(end - start);
assert(buffer->GetSize() > 0);
OrthancPluginErrorCode error =
readRange_(buffer->GetObject(), uuid.c_str(), Plugins::Convert(type), start);
if (error == OrthancPluginErrorCode_Success)
{
return buffer.release();
}
else
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
}
}
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
{
return (readRange_ != NULL);
}
};
// New in Orthanc 1.12.8
class PluginStorageAreaV3 : public IPluginStorageArea
{
private:
OrthancPluginStorageCreate2 create_;
OrthancPluginStorageReadRange2 readRange_;
OrthancPluginStorageRemove2 remove_;
PluginsErrorDictionary& errorDictionary_;
protected:
PluginsErrorDictionary& GetErrorDictionary() const
{
return errorDictionary_;
}
public:
PluginStorageAreaV3(const _OrthancPluginRegisterStorageArea3& callbacks,
PluginsErrorDictionary& errorDictionary) :
create_(callbacks.create),
readRange_(callbacks.readRange),
remove_(callbacks.remove),
errorDictionary_(errorDictionary)
{
if (create_ == NULL ||
readRange_ == NULL ||
remove_ == NULL)
{
throw OrthancException(ErrorCode_Plugin, "Storage area plugin does not implement all the required primitives (create, remove, and readRange)");
}
}
virtual void Create(std::string& customData /* out */,
const std::string& uuid,
const void* content,
size_t size,
FileContentType type,
CompressionType compression,
const DicomInstanceToStore* dicomInstance /* can be NULL if not a DICOM instance */) ORTHANC_OVERRIDE
{
PluginMemoryBuffer32 customDataBuffer;
OrthancPluginErrorCode error;
if (dicomInstance != NULL)
{
Orthanc::OrthancPlugins::DicomInstanceFromCallback wrapped(*dicomInstance);
error = create_(customDataBuffer.GetObject(), uuid.c_str(), content, size, Plugins::Convert(type), Plugins::Convert(compression),
reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped));
}
else
{
error = create_(customDataBuffer.GetObject(), uuid.c_str(), content, size, Plugins::Convert(type), Plugins::Convert(compression), NULL);
}
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
else
{
customDataBuffer.MoveToString(customData);
}
}
virtual void Remove(const std::string& uuid,
FileContentType type,
const std::string& customData) ORTHANC_OVERRIDE
{
OrthancPluginErrorCode error = remove_(uuid.c_str(), Plugins::Convert(type),
customData.empty() ? NULL : customData.c_str(), customData.size());
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
virtual IMemoryBuffer* ReadRange(const std::string& uuid,
FileContentType type,
uint64_t start /* inclusive */,
uint64_t end /* exclusive */,
const std::string& customData) ORTHANC_OVERRIDE
{
if (start > end)
{
throw OrthancException(ErrorCode_BadRange);
}
else if (start == end)
{
return new PluginMemoryBuffer64;
}
else
{
std::unique_ptr<PluginMemoryBuffer64> buffer(new PluginMemoryBuffer64);
buffer->Resize(end - start);
assert(buffer->GetSize() > 0);
OrthancPluginErrorCode error =
readRange_(buffer->GetObject(), uuid.c_str(), Plugins::Convert(type), start, customData.empty() ? NULL : customData.c_str(), customData.size());
if (error == OrthancPluginErrorCode_Success)
{
return buffer.release();
}
else
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
}
virtual bool HasEfficientReadRange() const ORTHANC_OVERRIDE
{
return true;
}
};
class StorageAreaFactory : public boost::noncopyable
{
private:
enum Version
{
Version1,
Version2,
Version3
};
SharedLibrary& sharedLibrary_;
Version version_;
_OrthancPluginRegisterStorageArea callbacks1_;
_OrthancPluginRegisterStorageArea2 callbacks2_;
_OrthancPluginRegisterStorageArea3 callbacks3_;
PluginsErrorDictionary& errorDictionary_;
static void WarnNoReadRange()
{
LOG(WARNING) << "Performance warning: The storage area plugin doesn't implement reading of file ranges";
}
public:
StorageAreaFactory(SharedLibrary& sharedLibrary,
const _OrthancPluginRegisterStorageArea& callbacks,
PluginsErrorDictionary& errorDictionary) :
sharedLibrary_(sharedLibrary),
version_(Version1),
callbacks1_(callbacks),
errorDictionary_(errorDictionary)
{
WarnNoReadRange();
}
StorageAreaFactory(SharedLibrary& sharedLibrary,
const _OrthancPluginRegisterStorageArea2& callbacks,
PluginsErrorDictionary& errorDictionary) :
sharedLibrary_(sharedLibrary),
version_(Version2),
callbacks2_(callbacks),
errorDictionary_(errorDictionary)
{
if (callbacks.readRange == NULL)
{
WarnNoReadRange();
}
}
StorageAreaFactory(SharedLibrary& sharedLibrary,
const _OrthancPluginRegisterStorageArea3& callbacks,
PluginsErrorDictionary& errorDictionary) :
sharedLibrary_(sharedLibrary),
version_(Version3),
callbacks3_(callbacks),
errorDictionary_(errorDictionary)
{
if (callbacks.readRange == NULL)
{
WarnNoReadRange();
}
}
SharedLibrary& GetSharedLibrary()
{
return sharedLibrary_;
}
IPluginStorageArea* Create() const
{
switch (version_)
{
case Version1:
return new PluginStorageAreaAdapter(new PluginStorageAreaV1(callbacks1_, errorDictionary_));
case Version2:
return new PluginStorageAreaAdapter(new PluginStorageAreaV2(callbacks2_, errorDictionary_));
case Version3:
return new PluginStorageAreaV3(callbacks3_, errorDictionary_);
default:
throw OrthancException(ErrorCode_InternalError);
}
}
};
class OrthancPeers : public boost::noncopyable
{
private:
std::vector<std::string> names_;
std::vector<WebServiceParameters> parameters_;
void CheckIndex(size_t i) const
{
assert(names_.size() == parameters_.size());
if (i >= names_.size())
{
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
}
public:
OrthancPeers()
{
OrthancConfiguration::ReaderLock lock;
std::set<std::string> peers;
lock.GetConfiguration().GetListOfOrthancPeers(peers);
names_.reserve(peers.size());
parameters_.reserve(peers.size());
for (std::set<std::string>::const_iterator
it = peers.begin(); it != peers.end(); ++it)
{
WebServiceParameters peer;
if (lock.GetConfiguration().LookupOrthancPeer(peer, *it))
{
names_.push_back(*it);
parameters_.push_back(peer);
}
}
}
size_t GetPeersCount() const
{
return names_.size();
}
const std::string& GetPeerName(size_t i) const
{
CheckIndex(i);
return names_[i];
}
const WebServiceParameters& GetPeerParameters(size_t i) const
{
CheckIndex(i);
return parameters_[i];
}
};
class DicomWebBinaryFormatter : public DicomWebJsonVisitor::IBinaryFormatter
{
private:
OrthancPluginDicomWebBinaryCallback oldCallback_;
OrthancPluginDicomWebBinaryCallback2 newCallback_; // New in Orthanc 1.7.0
void* newPayload_; // New in Orthanc 1.7.0
DicomWebJsonVisitor::BinaryMode currentMode_;
std::string currentBulkDataUri_;
static void Setter(OrthancPluginDicomWebNode* node,
OrthancPluginDicomWebBinaryMode mode,
const char* bulkDataUri)
{
DicomWebBinaryFormatter& that = *reinterpret_cast<DicomWebBinaryFormatter*>(node);
switch (mode)
{
case OrthancPluginDicomWebBinaryMode_Ignore:
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
break;
case OrthancPluginDicomWebBinaryMode_InlineBinary:
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_InlineBinary;
break;
case OrthancPluginDicomWebBinaryMode_BulkDataUri:
if (bulkDataUri == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
that.currentBulkDataUri_ = bulkDataUri;
that.currentMode_ = DicomWebJsonVisitor::BinaryMode_BulkDataUri;
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
}
public:
explicit DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback callback) :
oldCallback_(callback),
newCallback_(NULL),
newPayload_(NULL),
currentMode_(DicomWebJsonVisitor::BinaryMode_Ignore)
{
}
DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback2 callback,
void* payload) :
oldCallback_(NULL),
newCallback_(callback),
newPayload_(payload),
currentMode_(DicomWebJsonVisitor::BinaryMode_Ignore)
{
}
virtual DicomWebJsonVisitor::BinaryMode Format(std::string& bulkDataUri,
const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr) ORTHANC_OVERRIDE
{
if (oldCallback_ == NULL &&
newCallback_ == NULL)
{
return DicomWebJsonVisitor::BinaryMode_InlineBinary;
}
else
{
assert(parentTags.size() == parentIndexes.size());
std::vector<uint16_t> groups(parentTags.size());
std::vector<uint16_t> elements(parentTags.size());
std::vector<uint32_t> indexes(parentTags.size());
for (size_t i = 0; i < parentTags.size(); i++)
{
groups[i] = parentTags[i].GetGroup();
elements[i] = parentTags[i].GetElement();
indexes[i] = static_cast<uint32_t>(parentIndexes[i]);
}
bool empty = parentTags.empty();
currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
if (oldCallback_ != NULL)
{
oldCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
DicomWebBinaryFormatter::Setter,
static_cast<uint32_t>(parentTags.size()),
(empty ? NULL : &groups[0]),
(empty ? NULL : &elements[0]),
(empty ? NULL : &indexes[0]),
tag.GetGroup(),
tag.GetElement(),
Plugins::Convert(vr));
}
else
{
assert(newCallback_ != NULL);
newCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
DicomWebBinaryFormatter::Setter,
static_cast<uint32_t>(parentTags.size()),
(empty ? NULL : &groups[0]),
(empty ? NULL : &elements[0]),
(empty ? NULL : &indexes[0]),
tag.GetGroup(),
tag.GetElement(),
Plugins::Convert(vr),
newPayload_);
}
bulkDataUri = currentBulkDataUri_;
return currentMode_;
}
}
void Apply(char** target,
bool isJson,
const ParsedDicomFile& dicom)
{
DicomWebJsonVisitor visitor;
visitor.SetFormatter(*this);
dicom.Apply(visitor);
std::string s;
if (isJson)
{
s = visitor.GetResult().toStyledString();
}
else
{
visitor.FormatXml(s);
}
*target = CopyString(s);
}
void Apply(char** target,
bool isJson,
const void* dicom,
size_t dicomSize)
{
ParsedDicomFile parsed(dicom, dicomSize);
Apply(target, isJson, parsed);
}
};
}
class OrthancPlugins::PImpl
{
private:
boost::mutex contextMutex_;
boost::condition_variable contextCond_;
size_t contextRefCount_;
ServerContext* context_;
public:
class PluginHttpOutput : public boost::noncopyable
{
private:
enum State
{
State_None,
State_MultipartFirstPart,
State_MultipartSecondPart,
State_MultipartNextParts,
State_WritingStream
};
HttpOutput& output_;
std::unique_ptr<std::string> errorDetails_;
bool logDetails_;
State state_;
std::string multipartSubType_;
std::string multipartContentType_;
std::string multipartFirstPart_;
std::map<std::string, std::string> multipartFirstHeaders_;
public:
explicit PluginHttpOutput(HttpOutput& output) :
output_(output),
logDetails_(false),
state_(State_None)
{
}
HttpOutput& GetOutput()
{
if (state_ == State_None)
{
return output_;
}
else
{
// Must use "SendMultipartItem()" on multipart streams or "SendStreamChunk()"
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
}
void SetErrorDetails(const std::string& details,
bool logDetails)
{
errorDetails_.reset(new std::string(details));
logDetails_ = logDetails;
}
bool HasErrorDetails() const
{
return errorDetails_.get() != NULL;
}
bool IsLogDetails() const
{
return logDetails_;
}
const std::string& GetErrorDetails() const
{
if (errorDetails_.get() == NULL)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
return *errorDetails_;
}
}
void StartMultipart(const char* subType,
const char* contentType)
{
if (state_ != State_None)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
state_ = State_MultipartFirstPart;
multipartSubType_ = subType;
multipartContentType_ = contentType;
}
}
void StartStream(const char* contentType)
{
if (state_ != State_None)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
output_.StartStream(contentType);
state_ = State_WritingStream;
}
}
void SendStreamItem(const void* data,
size_t size)
{
if (state_ != State_WritingStream)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
output_.SendStreamItem(data, size);
}
}
void SendMultipartItem(const void* data,
size_t size,
const std::map<std::string, std::string>& headers)
{
if (size != 0 && data == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
switch (state_)
{
case State_None:
// Must call "StartMultipart()" before
throw OrthancException(ErrorCode_BadSequenceOfCalls);
case State_MultipartFirstPart:
multipartFirstPart_.assign(reinterpret_cast<const char*>(data), size);
multipartFirstHeaders_ = headers;
state_ = State_MultipartSecondPart;
break;
case State_MultipartSecondPart:
// Start an actual stream for chunked transfer as soon as
// there are more than 2 elements in the multipart stream
output_.StartMultipart(multipartSubType_, multipartContentType_);
output_.SendMultipartItem(multipartFirstPart_.c_str(), multipartFirstPart_.size(),
multipartFirstHeaders_);
multipartFirstPart_.clear(); // Release memory
output_.SendMultipartItem(data, size, headers);
state_ = State_MultipartNextParts;
break;
case State_MultipartNextParts:
output_.SendMultipartItem(data, size, headers);
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
}
void Close(OrthancPluginErrorCode error,
PluginsErrorDictionary& dictionary)
{
if (error == OrthancPluginErrorCode_Success)
{
switch (state_)
{
case State_None:
assert(!output_.IsWritingMultipart());
break;
case State_MultipartFirstPart: // Multipart started, but no part was sent
case State_MultipartSecondPart: // Multipart started, first part is pending
{
assert(!output_.IsWritingMultipart());
std::vector<const void*> parts;
std::vector<size_t> sizes;
std::vector<const std::map<std::string, std::string>*> headers;
if (state_ == State_MultipartSecondPart)
{
parts.push_back(multipartFirstPart_.c_str());
sizes.push_back(multipartFirstPart_.size());
headers.push_back(&multipartFirstHeaders_);
}
output_.AnswerMultipartWithoutChunkedTransfer(multipartSubType_, multipartContentType_,
parts, sizes, headers);
break;
}
case State_MultipartNextParts:
assert(output_.IsWritingMultipart());
output_.CloseMultipart();
break;
case State_WritingStream:
assert(output_.IsWritingStream());
output_.CloseStream();
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
}
else
{
dictionary.LogError(error, false);
if (HasErrorDetails())
{
throw OrthancException(static_cast<ErrorCode>(error),
GetErrorDetails(),
IsLogDetails());
}
else
{
throw OrthancException(static_cast<ErrorCode>(error));
}
}
}
};
class RestCallback : public boost::noncopyable
{
private:
boost::regex regex_;
OrthancPluginRestCallback callback_;
bool mutualExclusion_;
OrthancPluginErrorCode InvokeInternal(PluginHttpOutput& output,
const std::string& flatUri,
const OrthancPluginHttpRequest& request)
{
return callback_(reinterpret_cast<OrthancPluginRestOutput*>(&output),
flatUri.c_str(),
&request);
}
public:
RestCallback(const char* regex,
OrthancPluginRestCallback callback,
bool mutualExclusion) :
regex_(regex),
callback_(callback),
mutualExclusion_(mutualExclusion)
{
}
const boost::regex& GetRegularExpression() const
{
return regex_;
}
OrthancPluginErrorCode Invoke(boost::recursive_mutex& invokationMutex,
PluginHttpOutput& output,
const std::string& flatUri,
const OrthancPluginHttpRequest& request)
{
if (mutualExclusion_)
{
boost::recursive_mutex::scoped_lock lock(invokationMutex);
return InvokeInternal(output, flatUri, request);
}
else
{
return InvokeInternal(output, flatUri, request);
}
}
};
class ChunkedRestCallback : public boost::noncopyable
{
private:
_OrthancPluginChunkedRestCallback parameters_;
boost::regex regex_;
public:
explicit ChunkedRestCallback(const _OrthancPluginChunkedRestCallback& parameters) :
parameters_(parameters),
regex_(parameters.pathRegularExpression)
{
}
const boost::regex& GetRegularExpression() const
{
return regex_;
}
const _OrthancPluginChunkedRestCallback& GetParameters() const
{
return parameters_;
}
};
class StorageCommitmentScp : public IStorageCommitmentFactory
{
private:
class Handler : public IStorageCommitmentFactory::ILookupHandler
{
private:
_OrthancPluginRegisterStorageCommitmentScpCallback parameters_;
void* handler_;
public:
Handler(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters,
void* handler) :
parameters_(parameters),
handler_(handler)
{
if (handler == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
}
virtual ~Handler()
{
assert(handler_ != NULL);
parameters_.destructor(handler_);
handler_ = NULL;
}
virtual StorageCommitmentFailureReason Lookup(
const std::string& sopClassUid,
const std::string& sopInstanceUid) ORTHANC_OVERRIDE
{
assert(handler_ != NULL);
OrthancPluginStorageCommitmentFailureReason reason =
OrthancPluginStorageCommitmentFailureReason_Success;
OrthancPluginErrorCode error = parameters_.lookup(
&reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str());
if (error == OrthancPluginErrorCode_Success)
{
return Plugins::Convert(reason);
}
else
{
throw OrthancException(static_cast<ErrorCode>(error));
}
}
};
_OrthancPluginRegisterStorageCommitmentScpCallback parameters_;
public:
explicit StorageCommitmentScp(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters) :
parameters_(parameters)
{
}
virtual ILookupHandler* CreateStorageCommitment(
const std::string& jobId,
const std::string& transactionUid,
const std::vector<std::string>& sopClassUids,
const std::vector<std::string>& sopInstanceUids,
const std::string& remoteAet,
const std::string& calledAet) ORTHANC_OVERRIDE
{
const size_t n = sopClassUids.size();
if (sopInstanceUids.size() != n)
{
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
std::vector<const char*> a, b;
a.resize(n);
b.resize(n);
for (size_t i = 0; i < n; i++)
{
a[i] = sopClassUids[i].c_str();
b[i] = sopInstanceUids[i].c_str();
}
void* handler = NULL;
OrthancPluginErrorCode error = parameters_.factory(
&handler, jobId.c_str(), transactionUid.c_str(),
a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n),
remoteAet.c_str(), calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
throw OrthancException(static_cast<ErrorCode>(error));
}
else if (handler == NULL)
{
// This plugin won't handle this storage commitment request
return NULL;
}
else
{
return new Handler(parameters_, handler);
}
}
};
// This class ensures that the Context remains valid while being used.
// But it does not prevent multiple users to use the context at the same time.
// (new behavior in 1.12.2. In previous version, only one user could use the "locked" context)
class ServerContextReference
{
private:
ServerContext* context_;
boost::mutex& mutex_;
boost::condition_variable& cond_;
size_t& refCount_;
public:
explicit ServerContextReference(PImpl& that) :
context_(that.context_),
mutex_(that.contextMutex_),
cond_(that.contextCond_),
refCount_(that.contextRefCount_)
{
if (context_ == NULL)
{
throw OrthancException(ErrorCode_DatabaseNotInitialized);
}
boost::mutex::scoped_lock lock(mutex_);
refCount_++;
}
~ServerContextReference()
{
boost::mutex::scoped_lock lock(mutex_);
refCount_--;
cond_.notify_one();
}
ServerContext& GetContext()
{
assert(context_ != NULL);
return *context_;
}
};
void SetServerContext(ServerContext* context)
{
// update only the context while nobody is using it
boost::mutex::scoped_lock lock(contextMutex_);
while (contextRefCount_ > 0)
{
contextCond_.wait(lock);
}
context_ = context;
}
typedef std::pair<std::string, _OrthancPluginProperty> Property;
typedef std::list<RestCallback*> RestCallbacks;
typedef std::list<ChunkedRestCallback*> ChunkedRestCallbacks;
typedef std::list<OrthancPluginOnStoredInstanceCallback> OnStoredCallbacks;
typedef std::list<OrthancPluginOnChangeCallback> OnChangeCallbacks;
typedef std::list<OrthancPluginIncomingHttpRequestFilter> IncomingHttpRequestFilters;
typedef std::list<OrthancPluginIncomingHttpRequestFilter2> IncomingHttpRequestFilters2;
typedef std::list<OrthancPluginIncomingDicomInstanceFilter> IncomingDicomInstanceFilters;
typedef std::list<OrthancPluginIncomingCStoreInstanceFilter> IncomingCStoreInstanceFilters;
typedef std::list<OrthancPluginDecodeImageCallback> DecodeImageCallbacks;
typedef std::list<OrthancPluginTranscoderCallback> TranscoderCallbacks;
typedef std::list<OrthancPluginJobsUnserializer> JobsUnserializers;
typedef std::list<OrthancPluginRefreshMetricsCallback> RefreshMetricsCallbacks;
typedef std::list<StorageCommitmentScp*> StorageCommitmentScpCallbacks;
typedef std::map<Property, std::string> Properties;
typedef std::list<WebDavCollection*> WebDavCollections;
PluginsManager manager_;
RestCallbacks restCallbacks_;
ChunkedRestCallbacks chunkedRestCallbacks_;
OnStoredCallbacks onStoredCallbacks_;
OnChangeCallbacks onChangeCallbacks_;
OrthancPluginFindCallback findCallback_;
OrthancPluginWorklistCallback worklistCallback_;
DecodeImageCallbacks decodeImageCallbacks_;
TranscoderCallbacks transcoderCallbacks_;
JobsUnserializers jobsUnserializers_;
_OrthancPluginMoveCallback moveCallbacks_;
IncomingHttpRequestFilters incomingHttpRequestFilters_;
IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
IncomingDicomInstanceFilters incomingDicomInstanceFilters_;
IncomingCStoreInstanceFilters incomingCStoreInstanceFilters_; // New in Orthanc 1.10.0
OrthancPluginReceivedInstanceCallback receivedInstanceCallback_; // New in Orthanc 1.10.0
RefreshMetricsCallbacks refreshMetricsCallbacks_;
StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
WebDavCollections webDavCollections_; // New in Orthanc 1.10.1
std::unique_ptr<StorageAreaFactory> storageArea_;
std::set<std::string> authorizationTokens_;
boost::recursive_mutex restCallbackInvokationMutex_;
boost::shared_mutex restCallbackRegistrationMutex_; // New in Orthanc 1.9.0
boost::recursive_mutex storedCallbackMutex_;
boost::recursive_mutex changeCallbackMutex_;
boost::mutex findCallbackMutex_;
boost::mutex worklistCallbackMutex_;
boost::shared_mutex decoderTranscoderMutex_; // Changed from "boost::mutex" in Orthanc 1.7.0
boost::mutex jobsUnserializersMutex_;
boost::mutex refreshMetricsMutex_;
boost::mutex storageCommitmentScpMutex_;
boost::recursive_mutex invokeServiceMutex_;
boost::shared_mutex incomingHttpRequestFilterMutex_; // New in Orthanc 1.8.2
Properties properties_;
int argc_;
char** argv_;
std::unique_ptr<OrthancPluginDatabase> database_;
std::unique_ptr<OrthancPluginDatabaseV3> databaseV3_; // New in Orthanc 1.9.2
std::unique_ptr<OrthancPluginDatabaseV4> databaseV4_; // New in Orthanc 1.12.0
PluginsErrorDictionary dictionary_;
std::string databaseServerIdentifier_; // New in Orthanc 1.9.2
unsigned int maxDatabaseRetries_; // New in Orthanc 1.9.2
bool hasStorageAreaCustomData_; // New in Orthanc 1.12.8
explicit PImpl(const std::string& databaseServerIdentifier) :
contextRefCount_(0),
context_(NULL),
findCallback_(NULL),
worklistCallback_(NULL),
receivedInstanceCallback_(NULL),
argc_(1),
argv_(NULL),
databaseServerIdentifier_(databaseServerIdentifier),
maxDatabaseRetries_(0),
hasStorageAreaCustomData_(false)
{
memset(&moveCallbacks_, 0, sizeof(moveCallbacks_));
}
};
class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler
{
private:
OrthancPlugins& that_;
std::unique_ptr<HierarchicalMatcher> matcher_;
std::unique_ptr<ParsedDicomFile> filtered_;
ParsedDicomFile* currentQuery_;
void Reset()
{
matcher_.reset();
filtered_.reset();
currentQuery_ = NULL;
}
public:
explicit WorklistHandler(OrthancPlugins& that) : that_(that)
{
Reset();
}
virtual void Handle(DicomFindAnswers& answers,
ParsedDicomFile& query,
const std::string& remoteIp,
const std::string& remoteAet,
const std::string& calledAet,
ModalityManufacturer manufacturer) ORTHANC_OVERRIDE
{
{
static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter";
PImpl::ServerContextReference lock(*that_.pimpl_);
LuaScripting::Lock lua(lock.GetContext().GetLuaScripting());
if (!lua.GetLua().IsExistingFunction(LUA_CALLBACK))
{
currentQuery_ = &query;
}
else
{
Json::Value source, origin;
query.DatasetToJson(source, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
OrthancFindRequestHandler::FormatOrigin
(origin, remoteIp, remoteAet, calledAet, manufacturer);
LuaFunctionCall call(lua.GetLua(), LUA_CALLBACK);
call.PushJson(source);
call.PushJson(origin);
Json::Value target;
call.ExecuteToJson(target, true);
filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None,
"" /* no private creator */));
currentQuery_ = filtered_.get();
}
}
matcher_.reset(new HierarchicalMatcher(*currentQuery_));
{
boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_);
if (that_.pimpl_->worklistCallback_)
{
OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_
(reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers),
reinterpret_cast<const OrthancPluginWorklistQuery*>(this),
remoteAet.c_str(),
calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
Reset();
that_.GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
Reset();
}
}
void GetDicomQuery(OrthancPluginMemoryBuffer* target) const
{
if (currentQuery_ == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
std::string dicom;
currentQuery_->SaveToMemoryBuffer(dicom);
CopyToMemoryBuffer(target, dicom);
}
bool IsMatch(const void* dicom,
size_t size) const
{
if (matcher_.get() == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
ParsedDicomFile f(dicom, size);
return matcher_->Match(f);
}
void AddAnswer(OrthancPluginWorklistAnswers* answers,
const void* dicom,
size_t size) const
{
if (matcher_.get() == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
ParsedDicomFile f(dicom, size);
std::unique_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
}
};
class OrthancPlugins::FindHandler : public IFindRequestHandler
{
private:
OrthancPlugins& that_;
std::unique_ptr<DicomArray> currentQuery_;
void Reset()
{
currentQuery_.reset(NULL);
}
public:
explicit FindHandler(OrthancPlugins& that) : that_(that)
{
Reset();
}
virtual void Handle(DicomFindAnswers& answers,
const DicomMap& input,
const std::list<DicomTag>& sequencesToReturn,
const std::string& remoteIp,
const std::string& remoteAet,
const std::string& calledAet,
ModalityManufacturer manufacturer) ORTHANC_OVERRIDE
{
DicomMap tmp;
tmp.Assign(input);
for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin();
it != sequencesToReturn.end(); ++it)
{
if (!input.HasTag(*it))
{
tmp.SetValue(*it, "", false);
}
}
{
boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_);
currentQuery_.reset(new DicomArray(tmp));
if (that_.pimpl_->findCallback_)
{
OrthancPluginErrorCode error = that_.pimpl_->findCallback_
(reinterpret_cast<OrthancPluginFindAnswers*>(&answers),
reinterpret_cast<const OrthancPluginFindQuery*>(this),
remoteAet.c_str(),
calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
Reset();
that_.GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
Reset();
}
}
void Invoke(_OrthancPluginService service,
const _OrthancPluginFindOperation& operation) const
{
if (currentQuery_.get() == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
switch (service)
{
case _OrthancPluginService_GetFindQuerySize:
*operation.resultUint32 = currentQuery_->GetSize();
break;
case _OrthancPluginService_GetFindQueryTag:
{
const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag();
*operation.resultGroup = tag.GetGroup();
*operation.resultElement = tag.GetElement();
break;
}
case _OrthancPluginService_GetFindQueryTagName:
{
const DicomElement& element = currentQuery_->GetElement(operation.index);
*operation.resultString = CopyString(FromDcmtkBridge::GetTagName(element));
break;
}
case _OrthancPluginService_GetFindQueryValue:
{
*operation.resultString = CopyString(currentQuery_->GetElement(operation.index).GetValue().GetContent());
break;
}
default:
throw OrthancException(ErrorCode_InternalError);
}
}
};
class OrthancPlugins::MoveHandler : public IMoveRequestHandler
{
private:
class Driver : public IMoveRequestIterator
{
private:
void* driver_;
unsigned int count_;
unsigned int pos_;
OrthancPluginApplyMove apply_;
OrthancPluginFreeMove free_;
public:
Driver(void* driver,
unsigned int count,
OrthancPluginApplyMove apply,
OrthancPluginFreeMove free) :
driver_(driver),
count_(count),
pos_(0),
apply_(apply),
free_(free)
{
if (driver_ == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
}
virtual ~Driver()
{
if (driver_ != NULL)
{
free_(driver_);
driver_ = NULL;
}
}
virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE
{
return count_;
}
virtual Status DoNext() ORTHANC_OVERRIDE
{
if (pos_ >= count_)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
OrthancPluginErrorCode error = apply_(driver_);
if (error != OrthancPluginErrorCode_Success)
{
LOG(ERROR) << "Error while doing C-Move from plugin: "
<< EnumerationToString(static_cast<ErrorCode>(error));
return Status_Failure;
}
else
{
pos_++;
return Status_Success;
}
}
}
};
_OrthancPluginMoveCallback params_;
static std::string ReadTag(const DicomMap& input,
const DicomTag& tag)
{
const DicomValue* value = input.TestAndGetValue(tag);
if (value != NULL &&
!value->IsBinary() &&
!value->IsNull())
{
return value->GetContent();
}
else
{
return std::string();
}
}
public:
explicit MoveHandler(OrthancPlugins& that)
{
boost::recursive_mutex::scoped_lock lock(that.pimpl_->invokeServiceMutex_);
params_ = that.pimpl_->moveCallbacks_;
if (params_.callback == NULL ||
params_.getMoveSize == NULL ||
params_.applyMove == NULL ||
params_.freeMove == NULL)
{
throw OrthancException(ErrorCode_Plugin);
}
}
virtual IMoveRequestIterator* Handle(const std::string& targetAet,
const DicomMap& input,
const std::string& originatorIp,
const std::string& originatorAet,
const std::string& calledAet,
uint16_t originatorId) ORTHANC_OVERRIDE
{
std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID);
std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER);
std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID);
std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID);
std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID);
OrthancPluginResourceType level = OrthancPluginResourceType_None;
if (!levelString.empty())
{
level = Plugins::Convert(StringToResourceType(levelString.c_str()));
}
void* driver = params_.callback(level,
patientId.empty() ? NULL : patientId.c_str(),
accessionNumber.empty() ? NULL : accessionNumber.c_str(),
studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(),
seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(),
sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(),
originatorAet.c_str(),
calledAet.c_str(),
targetAet.c_str(),
originatorId);
if (driver == NULL)
{
throw OrthancException(ErrorCode_Plugin,
"Plugin cannot create a driver for an incoming C-MOVE request");
}
unsigned int size = params_.getMoveSize(driver);
return new Driver(driver, size, params_.applyMove, params_.freeMove);
}
};
class OrthancPlugins::HttpClientChunkedRequest : public HttpClient::IRequestBody
{
private:
const _OrthancPluginChunkedHttpClient& params_;
PluginsErrorDictionary& errorDictionary_;
public:
HttpClientChunkedRequest(const _OrthancPluginChunkedHttpClient& params,
PluginsErrorDictionary& errorDictionary) :
params_(params),
errorDictionary_(errorDictionary)
{
}
virtual bool ReadNextChunk(std::string& chunk) ORTHANC_OVERRIDE
{
if (params_.requestIsDone(params_.request))
{
return false;
}
else
{
size_t size = params_.requestChunkSize(params_.request);
chunk.resize(size);
if (size != 0)
{
const void* data = params_.requestChunkData(params_.request);
memcpy(&chunk[0], data, size);
}
OrthancPluginErrorCode error = params_.requestNext(params_.request);
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
else
{
return true;
}
}
}
};
class OrthancPlugins::HttpClientChunkedAnswer : public HttpClient::IAnswer
{
private:
const _OrthancPluginChunkedHttpClient& params_;
PluginsErrorDictionary& errorDictionary_;
public:
HttpClientChunkedAnswer(const _OrthancPluginChunkedHttpClient& params,
PluginsErrorDictionary& errorDictionary) :
params_(params),
errorDictionary_(errorDictionary)
{
}
virtual void AddHeader(const std::string& key,
const std::string& value) ORTHANC_OVERRIDE
{
OrthancPluginErrorCode error = params_.answerAddHeader(params_.answer, key.c_str(), value.c_str());
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
virtual void AddChunk(const void* data,
size_t size) ORTHANC_OVERRIDE
{
OrthancPluginErrorCode error = params_.answerAddChunk(params_.answer, data, size);
if (error != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
};
OrthancPlugins::OrthancPlugins(const std::string& databaseServerIdentifier)
{
/* Sanity check of the compiler */
if (sizeof(int32_t) != sizeof(int) || // Ensure binary compatibility with Orthanc SDK <= 1.12.1
sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
sizeof(int32_t) != sizeof(_OrthancPluginService) ||
sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
sizeof(int32_t) != sizeof(OrthancPluginJobStopReason) ||
sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
sizeof(int32_t) != sizeof(OrthancPluginReceivedInstanceAction) ||
sizeof(int32_t) != sizeof(OrthancPluginLoadDicomInstanceMode) ||
sizeof(int32_t) != sizeof(OrthancPluginLogLevel) ||
sizeof(int32_t) != sizeof(OrthancPluginLogCategory) ||
sizeof(int32_t) != sizeof(OrthancPluginStoreStatus) ||
sizeof(int32_t) != sizeof(OrthancPluginQueueOrigin) ||
// From OrthancCDatabasePlugin.h
sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) ||
sizeof(int32_t) != sizeof(OrthancPluginDatabaseTransactionType) ||
sizeof(int32_t) != sizeof(OrthancPluginDatabaseEventType) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_StopAfterPixelData) != static_cast<int>(DicomToJsonFlags_StopAfterPixelData) ||
static_cast<int>(OrthancPluginDicomToJsonFlags_SkipGroupLengths) != static_cast<int>(DicomToJsonFlags_SkipGroupLengths) ||
static_cast<int>(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast<int>(DicomFromJsonFlags_DecodeDataUriScheme) ||
static_cast<int>(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast<int>(DicomFromJsonFlags_GenerateIdentifiers))
{
throw OrthancException(ErrorCode_Plugin);
}
pimpl_.reset(new PImpl(databaseServerIdentifier));
pimpl_->manager_.RegisterServiceProvider(*this);
}
void OrthancPlugins::SetServerContext(ServerContext& context)
{
pimpl_->SetServerContext(&context);
}
void OrthancPlugins::ResetServerContext()
{
pimpl_->SetServerContext(NULL);
}
OrthancPlugins::~OrthancPlugins()
{
for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin();
it != pimpl_->restCallbacks_.end(); ++it)
{
delete *it;
}
for (PImpl::ChunkedRestCallbacks::iterator it = pimpl_->chunkedRestCallbacks_.begin();
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
{
delete *it;
}
for (PImpl::StorageCommitmentScpCallbacks::iterator
it = pimpl_->storageCommitmentScpCallbacks_.begin();
it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
{
delete *it;
}
for (PImpl::WebDavCollections::iterator it = pimpl_->webDavCollections_.begin();
it != pimpl_->webDavCollections_.end(); ++it)
{
delete *it;
}
}
static void ArgumentsToPlugin(std::vector<const char*>& keys,
std::vector<const char*>& values,
const HttpToolbox::Arguments& arguments)
{
keys.resize(arguments.size());
values.resize(arguments.size());
size_t pos = 0;
for (HttpToolbox::Arguments::const_iterator
it = arguments.begin(); it != arguments.end(); ++it)
{
keys[pos] = it->first.c_str();
values[pos] = it->second.c_str();
pos++;
}
}
static void ArgumentsToPlugin(std::vector<const char*>& keys,
std::vector<const char*>& values,
const HttpToolbox::GetArguments& arguments)
{
keys.resize(arguments.size());
values.resize(arguments.size());
for (size_t i = 0; i < arguments.size(); i++)
{
keys[i] = arguments[i].first.c_str();
values[i] = arguments[i].second.c_str();
}
}
namespace
{
class RestCallbackMatcher : public boost::noncopyable
{
private:
std::string flatUri_;
std::vector<std::string> groups_;
std::vector<const char*> cgroups_;
public:
explicit RestCallbackMatcher(const UriComponents& uri) :
flatUri_(Toolbox::FlattenUri(uri))
{
}
bool IsMatch(const boost::regex& re)
{
// Check whether the regular expression associated to this
// callback matches the URI
boost::cmatch what;
if (boost::regex_match(flatUri_.c_str(), what, re))
{
// Extract the value of the free parameters of the regular expression
if (what.size() > 1)
{
groups_.resize(what.size() - 1);
cgroups_.resize(what.size() - 1);
for (size_t i = 1; i < what.size(); i++)
{
groups_[i - 1] = what[i];
cgroups_[i - 1] = groups_[i - 1].c_str();
}
}
return true;
}
else
{
// Not a match
return false;
}
}
uint32_t GetGroupsCount() const
{
return cgroups_.size();
}
const char* const* GetGroups() const
{
return cgroups_.empty() ? NULL : &cgroups_[0];
}
const std::string& GetFlatUri() const
{
return flatUri_;
}
};
// WARNING - The lifetime of this internal object must be smaller
// than "matcher", "headers" and "getArguments" objects
class HttpRequestConverter
{
private:
std::vector<const char*> getKeys_;
std::vector<const char*> getValues_;
std::vector<const char*> headersKeys_;
std::vector<const char*> headersValues_;
OrthancPluginHttpRequest converted_;
public:
HttpRequestConverter(const RestCallbackMatcher& matcher,
HttpMethod method,
const HttpToolbox::Arguments& headers)
{
memset(&converted_, 0, sizeof(OrthancPluginHttpRequest));
ArgumentsToPlugin(headersKeys_, headersValues_, headers);
assert(headersKeys_.size() == headersValues_.size());
switch (method)
{
case HttpMethod_Get:
converted_.method = OrthancPluginHttpMethod_Get;
break;
case HttpMethod_Post:
converted_.method = OrthancPluginHttpMethod_Post;
break;
case HttpMethod_Delete:
converted_.method = OrthancPluginHttpMethod_Delete;
break;
case HttpMethod_Put:
converted_.method = OrthancPluginHttpMethod_Put;
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
converted_.groups = matcher.GetGroups();
converted_.groupsCount = matcher.GetGroupsCount();
converted_.getCount = 0;
converted_.getKeys = NULL;
converted_.getValues = NULL;
converted_.body = NULL;
converted_.bodySize = 0;
converted_.headersCount = headers.size();
if (headers.size() > 0)
{
converted_.headersKeys = &headersKeys_[0];
converted_.headersValues = &headersValues_[0];
}
}
void SetGetArguments(const HttpToolbox::GetArguments& getArguments)
{
ArgumentsToPlugin(getKeys_, getValues_, getArguments);
assert(getKeys_.size() == getValues_.size());
converted_.getCount = getArguments.size();
if (getArguments.size() > 0)
{
converted_.getKeys = &getKeys_[0];
converted_.getValues = &getValues_[0];
}
}
OrthancPluginHttpRequest& GetRequest()
{
return converted_;
}
};
}
static std::string GetAllowedMethods(_OrthancPluginChunkedRestCallback parameters)
{
std::string s;
if (parameters.getHandler != NULL)
{
s += "GET";
}
if (parameters.postHandler != NULL)
{
if (!s.empty())
{
s+= ",";
}
s += "POST";
}
if (parameters.deleteHandler != NULL)
{
if (!s.empty())
{
s+= ",";
}
s += "DELETE";
}
if (parameters.putHandler != NULL)
{
if (!s.empty())
{
s+= ",";
}
s += "PUT";
}
return s;
}
bool OrthancPlugins::HandleChunkedGetDelete(HttpOutput& output,
HttpMethod method,
const UriComponents& uri,
const HttpToolbox::Arguments& headers,
const HttpToolbox::GetArguments& getArguments)
{
RestCallbackMatcher matcher(uri);
PImpl::ChunkedRestCallback* callback = NULL;
// Loop over the callbacks registered by the plugins
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin();
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
{
if (matcher.IsMatch((*it)->GetRegularExpression()))
{
callback = *it;
break;
}
}
if (callback == NULL)
{
return false;
}
else
{
CLOG(INFO, PLUGINS) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
OrthancPluginRestCallback handler;
switch (method)
{
case HttpMethod_Get:
handler = callback->GetParameters().getHandler;
break;
case HttpMethod_Delete:
handler = callback->GetParameters().deleteHandler;
break;
default:
handler = NULL;
break;
}
if (handler == NULL)
{
output.SendMethodNotAllowed(GetAllowedMethods(callback->GetParameters()));
}
else
{
HttpRequestConverter converter(matcher, method, headers);
converter.SetGetArguments(getArguments);
PImpl::PluginHttpOutput pluginOutput(output);
OrthancPluginErrorCode error = handler(
reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput),
matcher.GetFlatUri().c_str(), &converter.GetRequest());
pluginOutput.Close(error, GetErrorDictionary());
}
return true;
}
}
bool OrthancPlugins::Handle(HttpOutput& output,
RequestOrigin /*origin*/,
const char* /*remoteIp*/,
const char* /*username*/,
HttpMethod method,
const UriComponents& uri,
const HttpToolbox::Arguments& headers,
const HttpToolbox::GetArguments& getArguments,
const void* bodyData,
size_t bodySize)
{
RestCallbackMatcher matcher(uri);
PImpl::RestCallback* callback = NULL;
// Loop over the callbacks registered by the plugins
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
for (PImpl::RestCallbacks::const_iterator it = pimpl_->restCallbacks_.begin();
it != pimpl_->restCallbacks_.end(); ++it)
{
if (matcher.IsMatch((*it)->GetRegularExpression()))
{
callback = *it;
break;
}
}
if (callback == NULL)
{
// Callback not found, try to find a chunked callback
return HandleChunkedGetDelete(output, method, uri, headers, getArguments);
}
CLOG(INFO, PLUGINS) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
HttpRequestConverter converter(matcher, method, headers);
converter.SetGetArguments(getArguments);
converter.GetRequest().body = bodyData;
converter.GetRequest().bodySize = bodySize;
PImpl::PluginHttpOutput pluginOutput(output);
assert(callback != NULL);
OrthancPluginErrorCode error = callback->Invoke
(pimpl_->restCallbackInvokationMutex_, pluginOutput, matcher.GetFlatUri(), converter.GetRequest());
pluginOutput.Close(error, GetErrorDictionary());
return true;
}
void OrthancPlugins::SignalStoredInstance(const std::string& instanceId,
const DicomInstanceToStore& instance,
const Json::Value& simplifiedTags)
{
DicomInstanceFromCallback wrapped(instance);
boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_);
for (PImpl::OnStoredCallbacks::const_iterator
callback = pimpl_->onStoredCallbacks_.begin();
callback != pimpl_->onStoredCallbacks_.end(); ++callback)
{
OrthancPluginErrorCode error = (*callback) (
reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped),
instanceId.c_str());
if (error != OrthancPluginErrorCode_Success)
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
}
bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance,
const Json::Value& simplified)
{
DicomInstanceFromCallback wrapped(instance);
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
for (PImpl::IncomingDicomInstanceFilters::const_iterator
filter = pimpl_->incomingDicomInstanceFilters_.begin();
filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter)
{
int32_t allowed = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
if (allowed == 0)
{
return false;
}
else if (allowed != 1)
{
// The callback is only allowed to answer 0 or 1
throw OrthancException(ErrorCode_Plugin);
}
}
return true;
}
bool OrthancPlugins::FilterIncomingCStoreInstance(uint16_t& dimseStatus,
const DicomInstanceToStore& instance,
const Json::Value& simplified)
{
DicomInstanceFromCallback wrapped(instance);
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
for (PImpl::IncomingCStoreInstanceFilters::const_iterator
filter = pimpl_->incomingCStoreInstanceFilters_.begin();
filter != pimpl_->incomingCStoreInstanceFilters_.end(); ++filter)
{
int32_t result = (*filter) (&dimseStatus, reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
if (result == 0)
{
// The instance must be discarded
return false;
}
else if (result == 1)
{
// The instance is accepted
return true;
}
else
{
// Error
throw OrthancException(ErrorCode_Plugin);
}
}
return true; // By default, the instance is accepted
}
OrthancPluginReceivedInstanceAction OrthancPlugins::ApplyReceivedInstanceCallbacks(PluginMemoryBuffer64& modified,
const void* receivedDicom,
size_t receivedDicomSize,
RequestOrigin origin)
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
modified.Clear();
if (pimpl_->receivedInstanceCallback_ == NULL)
{
return OrthancPluginReceivedInstanceAction_KeepAsIs;
}
else
{
return (*pimpl_->receivedInstanceCallback_) (modified.GetObject(), receivedDicom, receivedDicomSize, Plugins::Convert(origin));
}
}
void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType,
OrthancPluginResourceType resourceType,
const char* resource)
{
boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
for (std::list<OrthancPluginOnChangeCallback>::const_iterator
callback = pimpl_->onChangeCallbacks_.begin();
callback != pimpl_->onChangeCallbacks_.end(); ++callback)
{
OrthancPluginErrorCode error = (*callback) (changeType, resourceType, resource);
if (error != OrthancPluginErrorCode_Success)
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast<ErrorCode>(error));
}
}
}
void OrthancPlugins::SignalChange(const ServerIndexChange& change)
{
SignalChangeInternal(Plugins::Convert(change.GetChangeType()),
Plugins::Convert(change.GetResourceType()),
change.GetPublicId().c_str());
}
void OrthancPlugins::SignalJobEvent(const JobEvent& event)
{
// job events are actually considered as changes inside plugins -> translate
switch (event.GetEventType())
{
case JobEventType_Submitted:
SignalChangeInternal(OrthancPluginChangeType_JobSubmitted, OrthancPluginResourceType_None, event.GetJobId().c_str());
break;
case JobEventType_Success:
SignalChangeInternal(OrthancPluginChangeType_JobSuccess, OrthancPluginResourceType_None, event.GetJobId().c_str());
break;
case JobEventType_Failure:
SignalChangeInternal(OrthancPluginChangeType_JobFailure, OrthancPluginResourceType_None, event.GetJobId().c_str());
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
}
void OrthancPlugins::RegisterRestCallback(const void* parameters,
bool mutualExclusion)
{
const _OrthancPluginRestCallback& p =
*reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback "
<< (mutualExclusion ? "with" : "without")
<< " mutual exclusion on: "
<< p.pathRegularExpression;
{
boost::unique_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
pimpl_->restCallbacks_.push_back(new PImpl::RestCallback(p.pathRegularExpression, p.callback, mutualExclusion));
}
}
void OrthancPlugins::RegisterChunkedRestCallback(const void* parameters)
{
const _OrthancPluginChunkedRestCallback& p =
*reinterpret_cast<const _OrthancPluginChunkedRestCallback*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback for chunked streams on: "
<< p.pathRegularExpression;
{
boost::unique_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
pimpl_->chunkedRestCallbacks_.push_back(new PImpl::ChunkedRestCallback(p));
}
}
void OrthancPlugins::RegisterOnStoredInstanceCallback(const void* parameters)
{
const _OrthancPluginOnStoredInstanceCallback& p =
*reinterpret_cast<const _OrthancPluginOnStoredInstanceCallback*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered an OnStoredInstance callback";
pimpl_->onStoredCallbacks_.push_back(p.callback);
}
void OrthancPlugins::RegisterOnChangeCallback(const void* parameters)
{
boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
const _OrthancPluginOnChangeCallback& p =
*reinterpret_cast<const _OrthancPluginOnChangeCallback*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered an OnChange callback";
pimpl_->onChangeCallbacks_.push_back(p.callback);
}
void OrthancPlugins::RegisterWorklistCallback(const void* parameters)
{
const _OrthancPluginWorklistCallback& p =
*reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters);
boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
if (pimpl_->worklistCallback_ != NULL)
{
throw OrthancException(ErrorCode_Plugin,
"Can only register one plugin to handle modality worklists");
}
else
{
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle modality worklists";
pimpl_->worklistCallback_ = p.callback;
}
}
void OrthancPlugins::RegisterFindCallback(const void* parameters)
{
const _OrthancPluginFindCallback& p =
*reinterpret_cast<const _OrthancPluginFindCallback*>(parameters);
boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
if (pimpl_->findCallback_ != NULL)
{
throw OrthancException(ErrorCode_Plugin,
"Can only register one plugin to handle C-FIND requests");
}
else
{
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-FIND requests";
pimpl_->findCallback_ = p.callback;
}
}
void OrthancPlugins::RegisterMoveCallback(const void* parameters)
{
// invokeServiceMutex_ is assumed to be locked
const _OrthancPluginMoveCallback& p =
*reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters);
if (pimpl_->moveCallbacks_.callback != NULL)
{
throw OrthancException(ErrorCode_Plugin,
"Can only register one plugin to handle C-MOVE requests");
}
else
{
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-MOVE requests";
pimpl_->moveCallbacks_ = p;
}
}
void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
{
const _OrthancPluginDecodeImageCallback& p =
*reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
pimpl_->decodeImageCallbacks_.push_back(p.callback);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to decode DICOM images ("
<< pimpl_->decodeImageCallbacks_.size() << " decoder(s) now active)";
}
void OrthancPlugins::RegisterTranscoderCallback(const void* parameters)
{
const _OrthancPluginTranscoderCallback& p =
*reinterpret_cast<const _OrthancPluginTranscoderCallback*>(parameters);
boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
pimpl_->transcoderCallbacks_.push_back(p.callback);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to transcode DICOM images ("
<< pimpl_->transcoderCallbacks_.size() << " transcoder(s) now active)";
}
void OrthancPlugins::RegisterJobsUnserializer(const void* parameters)
{
const _OrthancPluginJobsUnserializer& p =
*reinterpret_cast<const _OrthancPluginJobsUnserializer*>(parameters);
boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
pimpl_->jobsUnserializers_.push_back(p.unserializer);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to unserialize jobs ("
<< pimpl_->jobsUnserializers_.size() << " unserializer(s) now active)";
}
void OrthancPlugins::RegisterIncomingHttpRequestFilter(const void* parameters)
{
const _OrthancPluginIncomingHttpRequestFilter& p =
*reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter*>(parameters);
boost::unique_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming HTTP requests";
pimpl_->incomingHttpRequestFilters_.push_back(p.callback);
}
void OrthancPlugins::RegisterIncomingHttpRequestFilter2(const void* parameters)
{
const _OrthancPluginIncomingHttpRequestFilter2& p =
*reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter2*>(parameters);
boost::unique_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming HTTP requests";
pimpl_->incomingHttpRequestFilters2_.push_back(p.callback);
}
void OrthancPlugins::RegisterIncomingDicomInstanceFilter(const void* parameters)
{
const _OrthancPluginIncomingDicomInstanceFilter& p =
*reinterpret_cast<const _OrthancPluginIncomingDicomInstanceFilter*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming DICOM instances";
pimpl_->incomingDicomInstanceFilters_.push_back(p.callback);
}
void OrthancPlugins::RegisterIncomingCStoreInstanceFilter(const void* parameters)
{
const _OrthancPluginIncomingCStoreInstanceFilter& p =
*reinterpret_cast<const _OrthancPluginIncomingCStoreInstanceFilter*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to filter incoming C-Store DICOM instances";
pimpl_->incomingCStoreInstanceFilters_.push_back(p.callback);
}
void OrthancPlugins::RegisterReceivedInstanceCallback(const void* parameters)
{
const _OrthancPluginReceivedInstanceCallback& p =
*reinterpret_cast<const _OrthancPluginReceivedInstanceCallback*>(parameters);
if (pimpl_->receivedInstanceCallback_ != NULL)
{
throw OrthancException(ErrorCode_Plugin,
"Can only register one plugin callback to process received instances");
}
else
{
CLOG(INFO, PLUGINS) << "Plugin has registered a received instance callback";
pimpl_->receivedInstanceCallback_ = p.callback;
}
}
void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters)
{
const _OrthancPluginRegisterRefreshMetricsCallback& p =
*reinterpret_cast<const _OrthancPluginRegisterRefreshMetricsCallback*>(parameters);
boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
CLOG(INFO, PLUGINS) << "Plugin has registered a callback to refresh its metrics";
pimpl_->refreshMetricsCallbacks_.push_back(p.callback);
}
void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters)
{
const _OrthancPluginRegisterStorageCommitmentScpCallback& p =
*reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback*>(parameters);
boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
CLOG(INFO, PLUGINS) << "Plugin has registered a storage commitment callback";
pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p));
}
void OrthancPlugins::AnswerBuffer(const void* parameters)
{
const _OrthancPluginAnswerBuffer& p =
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.SetContentType(p.mimeType);
translatedOutput.Answer(p.answer, p.answerSize);
}
void OrthancPlugins::Redirect(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.Redirect(p.argument);
}
void OrthancPlugins::SendHttpStatusCode(const void* parameters)
{
const _OrthancPluginSendHttpStatusCode& p =
*reinterpret_cast<const _OrthancPluginSendHttpStatusCode*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.SendStatus(static_cast<HttpStatus>(p.status));
}
void OrthancPlugins::SendHttpStatus(const void* parameters)
{
const _OrthancPluginSendHttpStatus& p =
*reinterpret_cast<const _OrthancPluginSendHttpStatus*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
HttpStatus status = static_cast<HttpStatus>(p.status);
if (p.bodySize > 0 && p.body != NULL)
{
translatedOutput.SendStatus(status, reinterpret_cast<const char*>(p.body), p.bodySize);
}
else
{
translatedOutput.SendStatus(status);
}
}
void OrthancPlugins::SendUnauthorized(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.SendUnauthorized(p.argument);
}
void OrthancPlugins::SendMethodNotAllowed(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.SendMethodNotAllowed(p.argument);
}
void OrthancPlugins::SetCookie(const void* parameters)
{
const _OrthancPluginSetHttpHeader& p =
*reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.SetCookie(p.key, p.value);
}
void OrthancPlugins::SetHttpHeader(const void* parameters)
{
const _OrthancPluginSetHttpHeader& p =
*reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
translatedOutput.AddHeader(p.key, p.value);
}
void OrthancPlugins::SetHttpErrorDetails(const void* parameters)
{
const _OrthancPluginSetHttpErrorDetails& p =
*reinterpret_cast<const _OrthancPluginSetHttpErrorDetails*>(parameters);
PImpl::PluginHttpOutput* output =
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output);
output->SetErrorDetails(p.details, (p.log != 0));
}
void OrthancPlugins::CompressAndAnswerPngImage(const void* parameters)
{
// Bridge for backward compatibility with Orthanc <= 0.9.3
const _OrthancPluginCompressAndAnswerPngImage& p =
*reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters);
_OrthancPluginCompressAndAnswerImage p2;
p2.output = p.output;
p2.imageFormat = OrthancPluginImageFormat_Png;
p2.pixelFormat = p.format;
p2.width = p.width;
p2.height = p.height;
p2.pitch = p.height;
p2.buffer = p.buffer;
p2.quality = 0;
CompressAndAnswerImage(&p2);
}
void OrthancPlugins::CompressAndAnswerImage(const void* parameters)
{
const _OrthancPluginCompressAndAnswerImage& p =
*reinterpret_cast<const _OrthancPluginCompressAndAnswerImage*>(parameters);
HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
ImageAccessor accessor;
accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
std::string compressed;
switch (p.imageFormat)
{
case OrthancPluginImageFormat_Png:
{
PngWriter writer;
IImageWriter::WriteToMemory(writer, compressed, accessor);
translatedOutput.SetContentType(MimeType_Png);
break;
}
case OrthancPluginImageFormat_Jpeg:
{
JpegWriter writer;
writer.SetQuality(p.quality);
IImageWriter::WriteToMemory(writer, compressed, accessor);
translatedOutput.SetContentType(MimeType_Jpeg);
break;
}
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
translatedOutput.Answer(compressed);
}
void OrthancPlugins::GetDicomForInstance(const void* parameters)
{
const _OrthancPluginGetDicomForInstance& p =
*reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
std::string dicom;
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().ReadDicom(dicom, p.instanceId);
}
CopyToMemoryBuffer(p.target, dicom);
}
static void ThrowOnHttpError(HttpStatus httpStatus)
{
int intHttpStatus = static_cast<int>(httpStatus);
if (intHttpStatus >= 200 && intHttpStatus <= 300)
{
return; // not an error
}
else if (intHttpStatus == 401 || intHttpStatus == 403)
{
throw OrthancException(ErrorCode_Unauthorized);
}
else if (intHttpStatus == 404)
{
throw OrthancException(ErrorCode_UnknownResource);
}
else if (intHttpStatus == 415)
{
throw OrthancException(ErrorCode_UnsupportedMediaType);
}
else
{
throw OrthancException(ErrorCode_BadRequest);
}
}
void OrthancPlugins::RestApiGet(const void* parameters,
bool afterPlugins)
{
const _OrthancPluginRestApiGet& p =
*reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin making REST GET call on URI " << p.uri
<< (afterPlugins ? " (after plugins)" : " (built-in API)");
IHttpHandler* handler;
{
PImpl::ServerContextReference lock(*pimpl_);
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
}
std::map<std::string, std::string> httpHeaders;
std::string result;
ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, httpHeaders));
CopyToMemoryBuffer(p.target, result);
}
void OrthancPlugins::RestApiGet2(const void* parameters)
{
const _OrthancPluginRestApiGet2& p =
*reinterpret_cast<const _OrthancPluginRestApiGet2*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin making REST GET call on URI " << p.uri
<< (p.afterPlugins ? " (after plugins)" : " (built-in API)");
HttpToolbox::Arguments headers;
for (uint32_t i = 0; i < p.headersCount; i++)
{
std::string name(p.headersKeys[i]);
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
headers[name] = p.headersValues[i];
}
IHttpHandler* handler;
{
PImpl::ServerContextReference lock(*pimpl_);
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
}
std::string result;
ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, headers));
CopyToMemoryBuffer(p.target, result);
}
void OrthancPlugins::RestApiPostPut(bool isPost,
const void* parameters,
bool afterPlugins)
{
const _OrthancPluginRestApiPostPut& p =
*reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put)
<< " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)");
IHttpHandler* handler;
{
PImpl::ServerContextReference lock(*pimpl_);
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
}
std::map<std::string, std::string> httpHeaders;
std::string result;
ThrowOnHttpError((isPost ?
IHttpHandler::SimplePost(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
p.body, p.bodySize, httpHeaders) :
IHttpHandler::SimplePut(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
p.body, p.bodySize, httpHeaders)));
CopyToMemoryBuffer(p.target, result);
}
void OrthancPlugins::RestApiDelete(const void* parameters,
bool afterPlugins)
{
const char* uri = reinterpret_cast<const char*>(parameters);
CLOG(INFO, PLUGINS) << "Plugin making REST DELETE call on URI " << uri
<< (afterPlugins ? " (after plugins)" : " (built-in API)");
IHttpHandler* handler;
{
PImpl::ServerContextReference lock(*pimpl_);
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
}
std::map<std::string, std::string> httpHeaders;
ThrowOnHttpError(IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders));
}
void OrthancPlugins::LookupResource(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginRetrieveDynamicString& p =
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
/**
* The enumeration below only uses the tags that are indexed in
* the Orthanc database. It reflects the
* "CandidateResources::ApplyFilter()" method of the
* "OrthancFindRequestHandler" class.
**/
DicomTag tag(0, 0);
ResourceType level;
switch (service)
{
case _OrthancPluginService_LookupPatient:
tag = DICOM_TAG_PATIENT_ID;
level = ResourceType_Patient;
break;
case _OrthancPluginService_LookupStudy:
tag = DICOM_TAG_STUDY_INSTANCE_UID;
level = ResourceType_Study;
break;
case _OrthancPluginService_LookupStudyWithAccessionNumber:
tag = DICOM_TAG_ACCESSION_NUMBER;
level = ResourceType_Study;
break;
case _OrthancPluginService_LookupSeries:
tag = DICOM_TAG_SERIES_INSTANCE_UID;
level = ResourceType_Series;
break;
case _OrthancPluginService_LookupInstance:
tag = DICOM_TAG_SOP_INSTANCE_UID;
level = ResourceType_Instance;
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
std::vector<std::string> result;
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().GetIndex().LookupIdentifierExact(result, level, tag, p.argument);
}
if (result.size() == 1)
{
*p.result = CopyString(result[0]);
}
else
{
if (result.size() > 1)
{
LOG(WARNING) << "LookupResource(): Multiple resources match the query (instead of 0 or 1), which indicates "
<< "your DICOM database breaks the DICOM model of the real world";
}
throw OrthancException(ErrorCode_UnknownResource);
}
}
static void AccessInstanceMetadataInternal(bool checkExistence,
const _OrthancPluginAccessDicomInstance& params,
const DicomInstanceToStore& instance)
{
MetadataType metadata;
try
{
metadata = StringToMetadata(params.key);
}
catch (OrthancException&)
{
// Unknown metadata
if (checkExistence)
{
*params.resultInt64 = -1;
}
else
{
*params.resultString = NULL;
}
return;
}
ServerIndex::MetadataMap::const_iterator it =
instance.GetMetadata().find(std::make_pair(ResourceType_Instance, metadata));
if (checkExistence)
{
if (it != instance.GetMetadata().end())
{
*params.resultInt64 = 1;
}
else
{
*params.resultInt64 = 0;
}
}
else
{
if (it != instance.GetMetadata().end())
{
*params.resultString = it->second.c_str();
}
else
{
// Error: Missing metadata
*params.resultString = NULL;
}
}
}
void OrthancPlugins::AccessDicomInstance(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginAccessDicomInstance& p =
*reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
if (p.instance == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
const DicomInstanceToStore& instance =
reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
switch (service)
{
case _OrthancPluginService_GetInstanceRemoteAet:
*p.resultString = instance.GetOrigin().GetRemoteAetC();
return;
case _OrthancPluginService_GetInstanceSize:
*p.resultInt64 = instance.GetBufferSize();
return;
case _OrthancPluginService_GetInstanceData:
*p.resultString = reinterpret_cast<const char*>(instance.GetBufferData());
return;
case _OrthancPluginService_HasInstanceMetadata:
AccessInstanceMetadataInternal(true, p, instance);
return;
case _OrthancPluginService_GetInstanceMetadata:
AccessInstanceMetadataInternal(false, p, instance);
return;
case _OrthancPluginService_GetInstanceJson:
case _OrthancPluginService_GetInstanceSimplifiedJson:
{
Json::Value dicomAsJson;
std::set<DicomTag> ignoreTagLength;
instance.GetDicomAsJson(dicomAsJson, ignoreTagLength);
std::string s;
if (service == _OrthancPluginService_GetInstanceJson)
{
Toolbox::WriteStyledJson(s, dicomAsJson);
}
else
{
Json::Value simplified;
Toolbox::SimplifyDicomAsJson(simplified, dicomAsJson, DicomToJsonFormat_Human);
Toolbox::WriteStyledJson(s, simplified);
}
*p.resultStringToFree = CopyString(s);
return;
}
case _OrthancPluginService_GetInstanceOrigin: // New in Orthanc 0.9.5
*p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin());
return;
case _OrthancPluginService_GetInstanceTransferSyntaxUid: // New in Orthanc 1.6.1
{
DicomTransferSyntax s;
if (instance.LookupTransferSyntax(s))
{
*p.resultStringToFree = CopyString(GetTransferSyntaxUid(s));
}
else
{
*p.resultStringToFree = CopyString("");
}
return;
}
case _OrthancPluginService_HasInstancePixelData: // New in Orthanc 1.6.1
*p.resultInt64 = instance.HasPixelData();
return;
case _OrthancPluginService_GetInstanceFramesCount: // New in Orthanc 1.7.0
*p.resultInt64 = instance.GetFramesCount();
return;
default:
throw OrthancException(ErrorCode_InternalError);
}
}
void OrthancPlugins::BufferCompression(const void* parameters)
{
const _OrthancPluginBufferCompression& p =
*reinterpret_cast<const _OrthancPluginBufferCompression*>(parameters);
std::string result;
{
std::unique_ptr<DeflateBaseCompressor> compressor;
switch (p.compression)
{
case OrthancPluginCompressionType_Zlib:
{
compressor.reset(new ZlibCompressor);
compressor->SetPrefixWithUncompressedSize(false);
break;
}
case OrthancPluginCompressionType_ZlibWithSize:
{
compressor.reset(new ZlibCompressor);
compressor->SetPrefixWithUncompressedSize(true);
break;
}
case OrthancPluginCompressionType_Gzip:
{
compressor.reset(new GzipCompressor);
compressor->SetPrefixWithUncompressedSize(false);
break;
}
case OrthancPluginCompressionType_GzipWithSize:
{
compressor.reset(new GzipCompressor);
compressor->SetPrefixWithUncompressedSize(true);
break;
}
case OrthancPluginCompressionType_None:
{
CopyToMemoryBuffer(p.target, p.source, p.size);
return;
}
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
if (p.uncompress)
{
compressor->Uncompress(result, p.source, p.size);
}
else
{
compressor->Compress(result, p.source, p.size);
}
}
CopyToMemoryBuffer(p.target, result);
}
static OrthancPluginImage* ReturnImage(std::unique_ptr<ImageAccessor>& image)
{
// Images returned to plugins are assumed to be writeable. If the
// input image is read-only, we return a copy so that it can be modified.
if (image.get() == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
if (image->IsReadOnly())
{
std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
ImageProcessing::Copy(*copy, *image);
image.reset(NULL);
return reinterpret_cast<OrthancPluginImage*>(copy.release());
}
else
{
return reinterpret_cast<OrthancPluginImage*>(image.release());
}
}
void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginAccessDicomInstance2& p =
*reinterpret_cast<const _OrthancPluginAccessDicomInstance2*>(parameters);
if (p.instance == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
const DicomInstanceToStore& instance =
reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
switch (service)
{
case _OrthancPluginService_GetInstanceFramesCount:
*p.targetUint32 = instance.GetFramesCount();
return;
case _OrthancPluginService_GetInstanceRawFrame:
{
if (p.targetBuffer == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
p.targetBuffer->data = NULL;
p.targetBuffer->size = 0;
MimeType mime;
std::string frame;
instance.GetParsedDicomFile().GetRawFrame(frame, mime, p.frameIndex);
CopyToMemoryBuffer(p.targetBuffer, frame);
return;
}
case _OrthancPluginService_GetInstanceDecodedFrame:
{
if (p.targetImage == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
std::unique_ptr<ImageAccessor> decoded;
{
PImpl::ServerContextReference lock(*pimpl_);
decoded.reset(lock.GetContext().DecodeDicomFrame(instance, p.frameIndex));
}
*(p.targetImage) = ReturnImage(decoded);
return;
}
case _OrthancPluginService_SerializeDicomInstance:
{
if (p.targetBuffer == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
p.targetBuffer->data = NULL;
p.targetBuffer->size = 0;
CopyToMemoryBuffer(p.targetBuffer, instance.GetBufferData(), instance.GetBufferSize());
return;
}
case _OrthancPluginService_GetInstanceAdvancedJson:
{
if (p.targetStringToFree == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
Json::Value json;
instance.DatasetToJson(json, Plugins::Convert(p.format),
static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
std::string s;
Toolbox::WriteFastJson(s, json);
*p.targetStringToFree = CopyString(s);
return;
}
case _OrthancPluginService_GetInstanceDicomWebJson:
case _OrthancPluginService_GetInstanceDicomWebXml:
{
if (p.targetStringToFree == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
DicomWebBinaryFormatter formatter(p.dicomWebCallback, p.dicomWebPayload);
formatter.Apply(p.targetStringToFree,
(service == _OrthancPluginService_GetInstanceDicomWebJson),
instance.GetParsedDicomFile());
return;
}
default:
throw OrthancException(ErrorCode_InternalError);
}
}
void OrthancPlugins::UncompressImage(const void* parameters)
{
const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
std::unique_ptr<ImageAccessor> image;
switch (p.format)
{
case OrthancPluginImageFormat_Png:
{
image.reset(new PngReader);
reinterpret_cast<PngReader&>(*image).ReadFromMemory(p.data, p.size);
break;
}
case OrthancPluginImageFormat_Jpeg:
{
image.reset(new JpegReader);
reinterpret_cast<JpegReader&>(*image).ReadFromMemory(p.data, p.size);
break;
}
case OrthancPluginImageFormat_Dicom:
{
PImpl::ServerContextReference lock(*pimpl_);
image.reset(lock.GetContext().DecodeDicomFrame(p.data, p.size, 0));
break;
}
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
*(p.target) = ReturnImage(image);
}
void OrthancPlugins::CompressImage(const void* parameters)
{
const _OrthancPluginCompressImage& p = *reinterpret_cast<const _OrthancPluginCompressImage*>(parameters);
std::string compressed;
ImageAccessor accessor;
accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
switch (p.imageFormat)
{
case OrthancPluginImageFormat_Png:
{
PngWriter writer;
IImageWriter::WriteToMemory(writer, compressed, accessor);
break;
}
case OrthancPluginImageFormat_Jpeg:
{
JpegWriter writer;
writer.SetQuality(p.quality);
IImageWriter::WriteToMemory(writer, compressed, accessor);
break;
}
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
CopyToMemoryBuffer(p.target, compressed);
}
static void SetupHttpClient(HttpClient& client,
const _OrthancPluginCallHttpClient2& parameters)
{
client.SetUrl(parameters.url);
client.SetConvertHeadersToLowerCase(false);
if (parameters.timeout != 0)
{
client.SetTimeout(parameters.timeout);
}
if (parameters.username != NULL &&
parameters.password != NULL)
{
client.SetCredentials(parameters.username, parameters.password);
}
if (parameters.certificateFile != NULL)
{
std::string certificate(parameters.certificateFile);
std::string key, password;
if (parameters.certificateKeyFile)
{
key.assign(parameters.certificateKeyFile);
}
if (parameters.certificateKeyPassword)
{
password.assign(parameters.certificateKeyPassword);
}
client.SetClientCertificate(certificate, key, password);
}
client.SetPkcs11Enabled(parameters.pkcs11 ? true : false);
for (uint32_t i = 0; i < parameters.headersCount; i++)
{
if (parameters.headersKeys[i] == NULL ||
parameters.headersValues[i] == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
client.AddHeader(parameters.headersKeys[i], parameters.headersValues[i]);
}
switch (parameters.method)
{
case OrthancPluginHttpMethod_Get:
client.SetMethod(HttpMethod_Get);
break;
case OrthancPluginHttpMethod_Post:
client.SetMethod(HttpMethod_Post);
client.SetExternalBody(parameters.body, parameters.bodySize);
break;
case OrthancPluginHttpMethod_Put:
client.SetMethod(HttpMethod_Put);
client.SetExternalBody(parameters.body, parameters.bodySize);
break;
case OrthancPluginHttpMethod_Delete:
client.SetMethod(HttpMethod_Delete);
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
}
static void ExecuteHttpClientWithoutChunkedBody(uint16_t& httpStatus,
OrthancPluginMemoryBuffer* answerBody,
OrthancPluginMemoryBuffer* answerHeaders,
HttpClient& client)
{
std::string body;
HttpClient::HttpHeaders headers;
bool success = client.Apply(body, headers);
// The HTTP request has succeeded
httpStatus = static_cast<uint16_t>(client.GetLastStatus());
if (!success)
{
HttpClient::ThrowException(client.GetLastStatus());
}
// Copy the HTTP headers of the answer, if the plugin requested them
PluginMemoryBuffer32 tmpHeaders;
if (answerHeaders != NULL)
{
CopyDictionary(tmpHeaders, headers);
}
// Copy the body of the answer if it makes sense
PluginMemoryBuffer32 tmpBody;
if (client.GetMethod() != HttpMethod_Delete &&
answerBody != NULL)
{
tmpBody.Assign(body);
}
// All the memory has been allocated at this point, so we can safely release the buffers
if (answerHeaders != NULL)
{
tmpHeaders.Release(answerHeaders);
}
if (answerBody != NULL)
{
tmpBody.Release(answerBody);
}
}
void OrthancPlugins::CallHttpClient(const void* parameters)
{
const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters);
HttpClient client;
{
_OrthancPluginCallHttpClient2 converted;
memset(&converted, 0, sizeof(converted));
converted.answerBody = NULL;
converted.answerHeaders = NULL;
converted.httpStatus = NULL;
converted.method = p.method;
converted.url = p.url;
converted.headersCount = 0;
converted.headersKeys = NULL;
converted.headersValues = NULL;
converted.body = p.body;
converted.bodySize = p.bodySize;
converted.username = p.username;
converted.password = p.password;
converted.timeout = 0; // Use default timeout
converted.certificateFile = NULL;
converted.certificateKeyFile = NULL;
converted.certificateKeyPassword = NULL;
converted.pkcs11 = false;
SetupHttpClient(client, converted);
}
uint16_t status;
ExecuteHttpClientWithoutChunkedBody(status, p.target, NULL, client);
}
void OrthancPlugins::CallHttpClient2(const void* parameters)
{
const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
if (p.httpStatus == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
HttpClient client;
if (p.method == OrthancPluginHttpMethod_Post ||
p.method == OrthancPluginHttpMethod_Put)
{
client.SetExternalBody(p.body, p.bodySize);
}
SetupHttpClient(client, p);
ExecuteHttpClientWithoutChunkedBody(*p.httpStatus, p.answerBody, p.answerHeaders, client);
}
void OrthancPlugins::ChunkedHttpClient(const void* parameters)
{
const _OrthancPluginChunkedHttpClient& p =
*reinterpret_cast<const _OrthancPluginChunkedHttpClient*>(parameters);
if (p.httpStatus == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
HttpClient client;
{
_OrthancPluginCallHttpClient2 converted;
memset(&converted, 0, sizeof(converted));
converted.answerBody = NULL;
converted.answerHeaders = NULL;
converted.httpStatus = NULL;
converted.method = p.method;
converted.url = p.url;
converted.headersCount = p.headersCount;
converted.headersKeys = p.headersKeys;
converted.headersValues = p.headersValues;
converted.body = NULL;
converted.bodySize = 0;
converted.username = p.username;
converted.password = p.password;
converted.timeout = p.timeout;
converted.certificateFile = p.certificateFile;
converted.certificateKeyFile = p.certificateKeyFile;
converted.certificateKeyPassword = p.certificateKeyPassword;
converted.pkcs11 = p.pkcs11;
SetupHttpClient(client, converted);
}
HttpClientChunkedRequest body(p, pimpl_->dictionary_);
client.SetBody(body);
HttpClientChunkedAnswer answer(p, pimpl_->dictionary_);
bool success = client.Apply(answer);
*p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
if (!success)
{
HttpClient::ThrowException(client.GetLastStatus());
}
}
void OrthancPlugins::CallRestApi(const void* parameters)
{
const _OrthancPluginCallRestApi& p = *reinterpret_cast<const _OrthancPluginCallRestApi*>(parameters);
if (p.httpStatus == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
const char* methodString;
switch (p.method)
{
case OrthancPluginHttpMethod_Get:
methodString = "GET";
break;
case OrthancPluginHttpMethod_Post:
methodString = "POST";
break;
case OrthancPluginHttpMethod_Put:
methodString = "PUT";
break;
case OrthancPluginHttpMethod_Delete:
methodString = "DELETE";
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
CLOG(INFO, PLUGINS) << "Plugin making REST " << methodString << " call to URI " << p.uri
<< (p.afterPlugins ? " (after plugins)" : " (built-in API)");
HttpToolbox::Arguments headers;
for (uint32_t i = 0; i < p.headersCount; i++)
{
std::string name(p.headersKeys[i]);
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
headers[name] = p.headersValues[i];
}
IHttpHandler* handler;
{
PImpl::ServerContextReference lock(*pimpl_);
handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
}
std::string answerBody;
std::map<std::string, std::string> answerHeaders;
HttpStatus status;
switch (p.method)
{
case OrthancPluginHttpMethod_Get:
status = IHttpHandler::SimpleGet(
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
break;
case OrthancPluginHttpMethod_Post:
status = IHttpHandler::SimplePost(
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
break;
case OrthancPluginHttpMethod_Put:
status = IHttpHandler::SimplePut(
answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
break;
case OrthancPluginHttpMethod_Delete:
status = IHttpHandler::SimpleDelete(
&answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
*p.httpStatus = static_cast<uint16_t>(status);
PluginMemoryBuffer32 tmpHeaders;
if (p.answerHeaders != NULL)
{
CopyDictionary(tmpHeaders, answerHeaders);
}
PluginMemoryBuffer32 tmpBody;
if (p.method != OrthancPluginHttpMethod_Delete &&
p.answerBody != NULL)
{
tmpBody.Assign(answerBody);
}
// All the memory has been allocated at this point, so we can safely release the buffers
if (p.answerHeaders != NULL)
{
tmpHeaders.Release(p.answerHeaders);
}
if (p.answerBody != NULL)
{
tmpBody.Release(p.answerBody);
}
}
void OrthancPlugins::CallPeerApi(const void* parameters)
{
const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters);
const OrthancPeers& peers = *reinterpret_cast<const OrthancPeers*>(p.peers);
HttpClient client(peers.GetPeerParameters(p.peerIndex), p.uri);
client.SetConvertHeadersToLowerCase(false);
if (p.timeout != 0)
{
client.SetTimeout(p.timeout);
}
for (uint32_t i = 0; i < p.additionalHeadersCount; i++)
{
if (p.additionalHeadersKeys[i] == NULL ||
p.additionalHeadersValues[i] == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
client.AddHeader(p.additionalHeadersKeys[i], p.additionalHeadersValues[i]);
}
switch (p.method)
{
case OrthancPluginHttpMethod_Get:
client.SetMethod(HttpMethod_Get);
break;
case OrthancPluginHttpMethod_Post:
client.SetMethod(HttpMethod_Post);
client.SetExternalBody(p.body, p.bodySize);
break;
case OrthancPluginHttpMethod_Put:
client.SetMethod(HttpMethod_Put);
client.SetExternalBody(p.body, p.bodySize);
break;
case OrthancPluginHttpMethod_Delete:
client.SetMethod(HttpMethod_Delete);
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
std::string body;
HttpClient::HttpHeaders headers;
bool success = client.Apply(body, headers);
// The HTTP request has succeeded
*p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
if (!success)
{
HttpClient::ThrowException(client.GetLastStatus());
}
// Copy the HTTP headers of the answer, if the plugin requested them
PluginMemoryBuffer32 tmpHeaders;
if (p.answerHeaders != NULL)
{
CopyDictionary(tmpHeaders, headers);
}
// Copy the body of the answer if it makes sense
PluginMemoryBuffer32 tmpBody;
if (p.method != OrthancPluginHttpMethod_Delete &&
p.answerBody != NULL)
{
tmpBody.Assign(body);
}
// All the memory has been allocated at this point, so we can safely release the buffers
if (p.answerHeaders != NULL)
{
tmpHeaders.Release(p.answerHeaders);
}
if (p.answerBody != NULL)
{
tmpBody.Release(p.answerBody);
}
}
void OrthancPlugins::ConvertPixelFormat(const void* parameters)
{
const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters);
const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source);
std::unique_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false));
ImageProcessing::Convert(*target, source);
*(p.target) = ReturnImage(target);
}
void OrthancPlugins::GetFontInfo(const void* parameters)
{
const _OrthancPluginGetFontInfo& p = *reinterpret_cast<const _OrthancPluginGetFontInfo*>(parameters);
{
OrthancConfiguration::ReaderLock lock;
const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
if (p.name != NULL)
{
*(p.name) = font.GetName().c_str();
}
else if (p.size != NULL)
{
*(p.size) = font.GetSize();
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
}
void OrthancPlugins::DrawText(const void* parameters)
{
const _OrthancPluginDrawText& p = *reinterpret_cast<const _OrthancPluginDrawText*>(parameters);
{
OrthancConfiguration::ReaderLock lock;
const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
ImageAccessor& target = *reinterpret_cast<ImageAccessor*>(p.image);
font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b);
}
}
void OrthancPlugins::ApplyDicomToJson(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginDicomToJson& p =
*reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters);
std::unique_ptr<ParsedDicomFile> dicom;
if (service == _OrthancPluginService_DicomBufferToJson)
{
dicom.reset(new ParsedDicomFile(p.buffer, p.size));
}
else
{
if (p.instanceId == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
std::string content;
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().ReadDicom(content, p.instanceId);
}
dicom.reset(new ParsedDicomFile(content));
}
Json::Value json;
dicom->DatasetToJson(json, Plugins::Convert(p.format),
static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
std::string s;
Toolbox::WriteFastJson(s, json);
*p.result = CopyString(s);
}
void OrthancPlugins::ApplyCreateDicom(const _OrthancPluginCreateDicom& parameters,
const char* privateCreatorC)
{
Json::Value json;
if (parameters.json == NULL)
{
json = Json::objectValue;
}
else if (!Toolbox::ReadJson(json, parameters.json))
{
throw OrthancException(ErrorCode_BadJson);
}
std::string dicom;
{
// Fix issue 168 (Plugins can't read private tags from the
// configuration file)
// https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=168
std::string privateCreator;
if (privateCreatorC == NULL)
{
OrthancConfiguration::ReaderLock lock;
privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
}
else
{
// New in Orthanc 1.9.0
privateCreator.assign(privateCreatorC);
}
std::unique_ptr<ParsedDicomFile> file
(ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(parameters.flags),
privateCreator));
if (parameters.pixelData)
{
file->EmbedImage(*reinterpret_cast<const ImageAccessor*>(parameters.pixelData));
}
file->SaveToMemoryBuffer(dicom);
}
CopyToMemoryBuffer(parameters.target, dicom);
}
void OrthancPlugins::ComputeHash(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginComputeHash& p =
*reinterpret_cast<const _OrthancPluginComputeHash*>(parameters);
std::string hash;
switch (service)
{
case _OrthancPluginService_ComputeMd5:
Toolbox::ComputeMD5(hash, p.buffer, p.size);
break;
case _OrthancPluginService_ComputeSha1:
Toolbox::ComputeSHA1(hash, p.buffer, p.size);
break;
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
*p.result = CopyString(hash);
}
void OrthancPlugins::GetTagName(const void* parameters)
{
const _OrthancPluginGetTagName& p =
*reinterpret_cast<const _OrthancPluginGetTagName*>(parameters);
std::string privateCreator;
if (p.privateCreator != NULL)
{
privateCreator = p.privateCreator;
}
DicomTag tag(p.group, p.element);
*p.result = CopyString(FromDcmtkBridge::GetTagName(tag, privateCreator));
}
void OrthancPlugins::ApplyCreateImage(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginCreateImage& p =
*reinterpret_cast<const _OrthancPluginCreateImage*>(parameters);
std::unique_ptr<ImageAccessor> result;
switch (service)
{
case _OrthancPluginService_CreateImage:
result.reset(new Image(Plugins::Convert(p.format), p.width, p.height, false));
break;
case _OrthancPluginService_CreateImageAccessor:
result.reset(new ImageAccessor);
result->AssignWritable(Plugins::Convert(p.format), p.width, p.height, p.pitch, p.buffer);
break;
case _OrthancPluginService_DecodeDicomImage:
{
PImpl::ServerContextReference lock(*pimpl_);
result.reset(lock.GetContext().DecodeDicomFrame(p.constBuffer, p.bufferSize, p.frameIndex));
break;
}
default:
throw OrthancException(ErrorCode_InternalError);
}
*(p.target) = ReturnImage(result);
}
void OrthancPlugins::ApplySendMultipartItem(const void* parameters)
{
// An exception might be raised in this function if the
// connection was closed by the HTTP client.
const _OrthancPluginAnswerBuffer& p =
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
std::map<std::string, std::string> headers; // No custom headers
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
}
void OrthancPlugins::ApplySendMultipartItem2(const void* parameters)
{
// An exception might be raised in this function if the
// connection was closed by the HTTP client.
const _OrthancPluginSendMultipartItem2& p =
*reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters);
std::map<std::string, std::string> headers;
for (uint32_t i = 0; i < p.headersCount; i++)
{
headers[p.headersKeys[i]] = p.headersValues[i];
}
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
}
void OrthancPlugins::ApplyAdoptDicomInstance(const _OrthancPluginAdoptDicomInstance& parameters)
{
if (!pimpl_->hasStorageAreaCustomData_)
{
LOG(WARNING) << "The adoption of a DICOM instance should only be used in combination with a custom "
<< "storage area registered using OrthancPluginRegisterStorageArea3()";
}
std::string md5;
Toolbox::ComputeMD5(md5, parameters.dicom, parameters.dicomSize);
std::unique_ptr<DicomInstanceToStore> dicom(DicomInstanceToStore::CreateFromBuffer(parameters.dicom, parameters.dicomSize));
dicom->SetOrigin(DicomInstanceOrigin::FromPlugins());
const std::string attachmentUuid = Toolbox::GenerateUuid();
FileInfo adoptedFile(attachmentUuid, FileContentType_Dicom, parameters.dicomSize, md5);
adoptedFile.SetCustomData(parameters.customData, parameters.customDataSize);
std::string instanceId;
ServerContext::StoreResult result;
{
PImpl::ServerContextReference lock(*pimpl_);
result = lock.GetContext().AdoptDicomInstance(instanceId, *dicom, StoreInstanceMode_Default, adoptedFile);
}
CopyToMemoryBuffer(parameters.attachmentUuid, attachmentUuid);
CopyToMemoryBuffer(parameters.instanceId, instanceId);
*(parameters.storeStatus) = Plugins::Convert(result.GetStatus());
}
static void CheckAttachmentCustomDataSupport(ServerContext& context)
{
if (!context.GetIndex().HasAttachmentCustomDataSupport())
{
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support custom data for attachments");
}
}
void OrthancPlugins::ApplyGetAttachmentCustomData(const _OrthancPluginGetAttachmentCustomData& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckAttachmentCustomDataSupport(lock.GetContext());
std::string customData;
lock.GetContext().GetIndex().GetAttachmentCustomData(customData, parameters.attachmentUuid);
CopyToMemoryBuffer(parameters.customData, customData);
}
void OrthancPlugins::ApplySetAttachmentCustomData(const _OrthancPluginSetAttachmentCustomData& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckAttachmentCustomDataSupport(lock.GetContext());
lock.GetContext().GetIndex().SetAttachmentCustomData(parameters.attachmentUuid, parameters.customData, parameters.customDataSize);
}
static void CheckKeyValueStoresSupport(ServerContext& context)
{
if (!context.GetIndex().HasKeyValueStoresSupport())
{
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support key-value stores");
}
}
void OrthancPlugins::ApplyStoreKeyValue(const _OrthancPluginStoreKeyValue& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckKeyValueStoresSupport(lock.GetContext());
lock.GetContext().GetIndex().StoreKeyValue(parameters.storeId, parameters.key, parameters.value, parameters.valueSize);
}
void OrthancPlugins::ApplyDeleteKeyValue(const _OrthancPluginDeleteKeyValue& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckKeyValueStoresSupport(lock.GetContext());
lock.GetContext().GetIndex().DeleteKeyValue(parameters.storeId, parameters.key);
}
void OrthancPlugins::ApplyGetKeyValue(const _OrthancPluginGetKeyValue& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckKeyValueStoresSupport(lock.GetContext());
std::string value;
if (lock.GetContext().GetIndex().GetKeyValue(value, parameters.storeId, parameters.key))
{
CopyToMemoryBuffer(parameters.target, value);
*parameters.found = true;
}
else
{
*parameters.found = false;
}
}
void OrthancPlugins::ApplyCreateKeysValuesIterator(const _OrthancPluginCreateKeysValuesIterator& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckKeyValueStoresSupport(lock.GetContext());
*parameters.target = reinterpret_cast<OrthancPluginKeysValuesIterator*>(
new StatelessDatabaseOperations::KeysValuesIterator(lock.GetContext().GetIndex(), parameters.storeId));
}
static void CheckQueuesSupport(ServerContext& context)
{
if (!context.GetIndex().HasQueuesSupport())
{
throw OrthancException(ErrorCode_NotImplemented, "The database engine does not support queues");
}
}
void OrthancPlugins::ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckQueuesSupport(lock.GetContext());
lock.GetContext().GetIndex().EnqueueValue(parameters.queueId, parameters.value, parameters.valueSize);
}
void OrthancPlugins::ApplyDequeueValue(const _OrthancPluginDequeueValue& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckQueuesSupport(lock.GetContext());
std::string value;
if (lock.GetContext().GetIndex().DequeueValue(value, parameters.queueId, Plugins::Convert(parameters.origin)))
{
CopyToMemoryBuffer(parameters.target, value);
*parameters.found = true;
}
else
{
*parameters.found = false;
}
}
void OrthancPlugins::ApplyGetQueueSize(const _OrthancPluginGetQueueSize& parameters)
{
PImpl::ServerContextReference lock(*pimpl_);
CheckQueuesSupport(lock.GetContext());
*parameters.size = lock.GetContext().GetIndex().GetQueueSize(parameters.queueId);
}
void OrthancPlugins::ApplyLoadDicomInstance(const _OrthancPluginLoadDicomInstance& params)
{
std::unique_ptr<IDicomInstance> target;
switch (params.mode)
{
case OrthancPluginLoadDicomInstanceMode_WholeDicom:
{
std::string buffer;
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().ReadDicom(buffer, params.instanceId);
}
target.reset(new DicomInstanceFromBuffer(buffer));
break;
}
case OrthancPluginLoadDicomInstanceMode_UntilPixelData:
case OrthancPluginLoadDicomInstanceMode_EmptyPixelData:
{
std::unique_ptr<ParsedDicomFile> parsed;
{
std::string buffer;
{
PImpl::ServerContextReference lock(*pimpl_);
if (!lock.GetContext().ReadDicomUntilPixelData(buffer, params.instanceId))
{
lock.GetContext().ReadDicom(buffer, params.instanceId);
}
}
parsed.reset(new ParsedDicomFile(buffer));
}
parsed->RemoveFromPixelData();
if (params.mode == OrthancPluginLoadDicomInstanceMode_EmptyPixelData)
{
bool hasPixelData = false;
ValueRepresentation pixelDataVR = parsed->GuessPixelDataValueRepresentation();
{
PImpl::ServerContextReference lock(*pimpl_);
std::string s;
if (lock.GetContext().GetIndex().LookupMetadata(
s, params.instanceId,
ResourceType_Instance, MetadataType_Instance_PixelDataVR))
{
hasPixelData = true;
if (s == "OB")
{
pixelDataVR = ValueRepresentation_OtherByte;
}
else if (s == "OW")
{
pixelDataVR = ValueRepresentation_OtherWord;
}
else
{
LOG(WARNING) << "Corrupted PixelDataVR metadata associated with instance "
<< params.instanceId << ": " << s;
}
}
else if (lock.GetContext().GetIndex().LookupMetadata(
s, params.instanceId,
ResourceType_Instance, MetadataType_Instance_PixelDataOffset))
{
// This file was stored by an older version of Orthanc,
// "PixelDataVR" is not available, so use the guess
hasPixelData = true;
}
else
{
hasPixelData = false;
}
}
if (hasPixelData)
{
parsed->InjectEmptyPixelData(pixelDataVR);
}
}
target.reset(new DicomInstanceFromParsed(parsed.release()));
break;
}
default:
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
if (target.get() == NULL)
{
throw OrthancException(ErrorCode_InternalError);
}
else
{
*params.target = reinterpret_cast<OrthancPluginDicomInstance*>(target.release());
}
}
void OrthancPlugins::DatabaseAnswer(const void* parameters)
{
const _OrthancPluginDatabaseAnswer& p =
*reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
if (pimpl_->database_.get() != NULL)
{
pimpl_->database_->AnswerReceived(p);
}
else
{
throw OrthancException(ErrorCode_BadRequest,
"Cannot invoke this service without a custom database back-end");
}
}
void OrthancPlugins::ApplyLookupDictionary(const void* parameters)
{
const _OrthancPluginLookupDictionary& p =
*reinterpret_cast<const _OrthancPluginLookupDictionary*>(parameters);
DicomTag tag(FromDcmtkBridge::ParseTag(p.name));
DcmTagKey tag2(tag.GetGroup(), tag.GetElement());
{
FromDcmtkBridge::DictionaryReaderLock lock;
const DcmDictEntry* entry = NULL; // This value is only valid while "lock" is active
if (tag.IsPrivate())
{
// Fix issue 168 (Plugins can't read private tags from the
// configuration file)
// https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=168
std::string privateCreator;
{
OrthancConfiguration::ReaderLock configurationLock;
privateCreator = configurationLock.GetConfiguration().GetDefaultPrivateCreator();
}
entry = lock.GetDictionary().findEntry(tag2, privateCreator.c_str());
}
else
{
entry = lock.GetDictionary().findEntry(tag2, NULL);
}
if (entry == NULL)
{
throw OrthancException(ErrorCode_UnknownDicomTag, p.name);
}
else
{
p.target->group = entry->getKey().getGroup();
p.target->element = entry->getKey().getElement();
p.target->vr = Plugins::Convert(FromDcmtkBridge::Convert(entry->getEVR()));
p.target->minMultiplicity = static_cast<uint32_t>(entry->getVMMin());
p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast<uint32_t>(entry->getVMMax()));
}
}
}
bool OrthancPlugins::InvokeSafeService(SharedLibrary& plugin,
_OrthancPluginService service,
const void* parameters)
{
// Services that can be run without mutual exclusion
switch (service)
{
case _OrthancPluginService_GetOrthancPath:
{
std::string s = SystemToolbox::GetPathToExecutable();
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
return true;
}
case _OrthancPluginService_GetOrthancDirectory:
{
std::string s = SystemToolbox::GetDirectoryOfExecutable();
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
return true;
}
case _OrthancPluginService_GetConfigurationPath:
{
std::string s;
{
OrthancConfiguration::ReaderLock lock;
s = lock.GetConfiguration().GetConfigurationAbsolutePath();
}
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
return true;
}
case _OrthancPluginService_GetConfiguration:
{
std::string s;
{
OrthancConfiguration::ReaderLock lock;
lock.GetConfiguration().Format(s);
}
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
return true;
}
case _OrthancPluginService_BufferCompression:
BufferCompression(parameters);
return true;
case _OrthancPluginService_AnswerBuffer:
AnswerBuffer(parameters);
return true;
case _OrthancPluginService_CompressAndAnswerPngImage:
CompressAndAnswerPngImage(parameters);
return true;
case _OrthancPluginService_CompressAndAnswerImage:
CompressAndAnswerImage(parameters);
return true;
case _OrthancPluginService_GetDicomForInstance:
GetDicomForInstance(parameters);
return true;
case _OrthancPluginService_RestApiGet:
RestApiGet(parameters, false);
return true;
case _OrthancPluginService_RestApiGetAfterPlugins:
RestApiGet(parameters, true);
return true;
case _OrthancPluginService_RestApiGet2:
RestApiGet2(parameters);
return true;
case _OrthancPluginService_RestApiPost:
RestApiPostPut(true, parameters, false);
return true;
case _OrthancPluginService_RestApiPostAfterPlugins:
RestApiPostPut(true, parameters, true);
return true;
case _OrthancPluginService_RestApiDelete:
RestApiDelete(parameters, false);
return true;
case _OrthancPluginService_RestApiDeleteAfterPlugins:
RestApiDelete(parameters, true);
return true;
case _OrthancPluginService_RestApiPut:
RestApiPostPut(false, parameters, false);
return true;
case _OrthancPluginService_RestApiPutAfterPlugins:
RestApiPostPut(false, parameters, true);
return true;
case _OrthancPluginService_Redirect:
Redirect(parameters);
return true;
case _OrthancPluginService_SendUnauthorized:
SendUnauthorized(parameters);
return true;
case _OrthancPluginService_SendMethodNotAllowed:
SendMethodNotAllowed(parameters);
return true;
case _OrthancPluginService_SendHttpStatus:
SendHttpStatus(parameters);
return true;
case _OrthancPluginService_SendHttpStatusCode:
SendHttpStatusCode(parameters);
return true;
case _OrthancPluginService_SetCookie:
SetCookie(parameters);
return true;
case _OrthancPluginService_SetHttpHeader:
SetHttpHeader(parameters);
return true;
case _OrthancPluginService_SetHttpErrorDetails:
SetHttpErrorDetails(parameters);
return true;
case _OrthancPluginService_LookupPatient:
case _OrthancPluginService_LookupStudy:
case _OrthancPluginService_LookupStudyWithAccessionNumber:
case _OrthancPluginService_LookupSeries:
case _OrthancPluginService_LookupInstance:
LookupResource(service, parameters);
return true;
case _OrthancPluginService_GetInstanceRemoteAet:
case _OrthancPluginService_GetInstanceSize:
case _OrthancPluginService_GetInstanceData:
case _OrthancPluginService_GetInstanceJson:
case _OrthancPluginService_GetInstanceSimplifiedJson:
case _OrthancPluginService_HasInstanceMetadata:
case _OrthancPluginService_GetInstanceMetadata:
case _OrthancPluginService_GetInstanceOrigin:
case _OrthancPluginService_GetInstanceTransferSyntaxUid:
case _OrthancPluginService_HasInstancePixelData:
AccessDicomInstance(service, parameters);
return true;
case _OrthancPluginService_GetInstanceFramesCount:
case _OrthancPluginService_GetInstanceRawFrame:
case _OrthancPluginService_GetInstanceDecodedFrame:
case _OrthancPluginService_SerializeDicomInstance:
case _OrthancPluginService_GetInstanceAdvancedJson:
case _OrthancPluginService_GetInstanceDicomWebJson:
case _OrthancPluginService_GetInstanceDicomWebXml:
AccessDicomInstance2(service, parameters);
return true;
case _OrthancPluginService_SetGlobalProperty:
{
const _OrthancPluginGlobalProperty& p =
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
if (p.property < 1024)
{
return false;
}
else
{
// TODO - Plugins can only access global properties of their
// own Orthanc server (no access to the shared global properties)
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property),
false /* not shared */, p.value);
return true;
}
}
case _OrthancPluginService_GetGlobalProperty:
{
const _OrthancPluginGlobalProperty& p =
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
std::string result;
{
// TODO - Plugins can only access global properties of their
// own Orthanc server (no access to the shared global properties)
PImpl::ServerContextReference lock(*pimpl_);
result = lock.GetContext().GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property),
false /* not shared */, p.value);
}
*(p.result) = CopyString(result);
return true;
}
case _OrthancPluginService_GetExpectedDatabaseVersion:
{
const _OrthancPluginReturnSingleValue& p =
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
*(p.resultUint32) = ORTHANC_DATABASE_VERSION;
return true;
}
case _OrthancPluginService_StartMultipartAnswer:
{
const _OrthancPluginStartMultipartAnswer& p =
*reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters);
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartMultipart(p.subType, p.contentType);
return true;
}
case _OrthancPluginService_SendMultipartItem:
ApplySendMultipartItem(parameters);
return true;
case _OrthancPluginService_SendMultipartItem2:
ApplySendMultipartItem2(parameters);
return true;
case _OrthancPluginService_StartStreamAnswer:
{
const _OrthancPluginStartStreamAnswer& p =
*reinterpret_cast<const _OrthancPluginStartStreamAnswer*>(parameters);
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartStream(p.contentType);
return true;
}
case _OrthancPluginService_SendStreamChunk:
{
const _OrthancPluginAnswerBuffer& p =
*reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendStreamItem(p.answer, p.answerSize);
return true;
}
case _OrthancPluginService_ReadFile:
{
const _OrthancPluginReadFile& p =
*reinterpret_cast<const _OrthancPluginReadFile*>(parameters);
std::string content;
SystemToolbox::ReadFile(content, p.path);
CopyToMemoryBuffer(p.target, content);
return true;
}
case _OrthancPluginService_WriteFile:
{
const _OrthancPluginWriteFile& p =
*reinterpret_cast<const _OrthancPluginWriteFile*>(parameters);
SystemToolbox::WriteFile(p.data, p.size, p.path, true /* run fsync() */);
return true;
}
case _OrthancPluginService_GetErrorDescription:
{
const _OrthancPluginGetErrorDescription& p =
*reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters);
*(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error));
return true;
}
case _OrthancPluginService_GetImagePixelFormat:
{
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
*(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat());
return true;
}
case _OrthancPluginService_GetImageWidth:
{
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth();
return true;
}
case _OrthancPluginService_GetImageHeight:
{
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight();
return true;
}
case _OrthancPluginService_GetImagePitch:
{
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
*(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch();
return true;
}
case _OrthancPluginService_GetImageBuffer:
{
const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
*(p.resultBuffer) = const_cast<void*>(reinterpret_cast<const ImageAccessor*>(p.image)->GetConstBuffer());
return true;
}
case _OrthancPluginService_FreeImage:
{
const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters);
if (p.image != NULL)
{
delete reinterpret_cast<ImageAccessor*>(p.image);
}
return true;
}
case _OrthancPluginService_UncompressImage:
UncompressImage(parameters);
return true;
case _OrthancPluginService_CompressImage:
CompressImage(parameters);
return true;
case _OrthancPluginService_CallHttpClient:
CallHttpClient(parameters);
return true;
case _OrthancPluginService_CallHttpClient2:
CallHttpClient2(parameters);
return true;
case _OrthancPluginService_ChunkedHttpClient:
ChunkedHttpClient(parameters);
return true;
case _OrthancPluginService_CallRestApi:
CallRestApi(parameters);
return true;
case _OrthancPluginService_ConvertPixelFormat:
ConvertPixelFormat(parameters);
return true;
case _OrthancPluginService_GetFontsCount:
{
const _OrthancPluginReturnSingleValue& p =
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
{
OrthancConfiguration::ReaderLock lock;
*(p.resultUint32) = lock.GetConfiguration().GetFontRegistry().GetSize();
}
return true;
}
case _OrthancPluginService_GetFontInfo:
GetFontInfo(parameters);
return true;
case _OrthancPluginService_DrawText:
DrawText(parameters);
return true;
case _OrthancPluginService_StorageAreaCreate:
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaCreate() is only available in Orthanc <= 1.12.6");
case _OrthancPluginService_StorageAreaRead:
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaRead() is only available in Orthanc <= 1.12.6");
case _OrthancPluginService_StorageAreaRemove:
throw OrthancException(ErrorCode_NotImplemented, "The SDK function OrthancPluginStorageAreaRemove() is only available in Orthanc <= 1.12.6");
case _OrthancPluginService_DicomBufferToJson:
case _OrthancPluginService_DicomInstanceToJson:
ApplyDicomToJson(service, parameters);
return true;
case _OrthancPluginService_CreateDicom:
{
const _OrthancPluginCreateDicom& p =
*reinterpret_cast<const _OrthancPluginCreateDicom*>(parameters);
ApplyCreateDicom(p, NULL);
return true;
}
case _OrthancPluginService_CreateDicom2:
{
// New in Orthanc 1.9.0
const _OrthancPluginCreateDicom2& p =
*reinterpret_cast<const _OrthancPluginCreateDicom2*>(parameters);
ApplyCreateDicom(p.createDicom, p.privateCreator);
return true;
}
case _OrthancPluginService_WorklistAddAnswer:
{
const _OrthancPluginWorklistAnswersOperation& p =
*reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
reinterpret_cast<const WorklistHandler*>(p.query)->AddAnswer(p.answers, p.dicom, p.size);
return true;
}
case _OrthancPluginService_WorklistMarkIncomplete:
{
const _OrthancPluginWorklistAnswersOperation& p =
*reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
return true;
}
case _OrthancPluginService_WorklistIsMatch:
{
const _OrthancPluginWorklistQueryOperation& p =
*reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
*p.isMatch = reinterpret_cast<const WorklistHandler*>(p.query)->IsMatch(p.dicom, p.size);
return true;
}
case _OrthancPluginService_WorklistGetDicomQuery:
{
const _OrthancPluginWorklistQueryOperation& p =
*reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
reinterpret_cast<const WorklistHandler*>(p.query)->GetDicomQuery(p.target);
return true;
}
case _OrthancPluginService_FindAddAnswer:
{
const _OrthancPluginFindOperation& p =
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
reinterpret_cast<DicomFindAnswers*>(p.answers)->Add(p.dicom, p.size);
return true;
}
case _OrthancPluginService_FindMarkIncomplete:
{
const _OrthancPluginFindOperation& p =
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
return true;
}
case _OrthancPluginService_GetFindQuerySize:
case _OrthancPluginService_GetFindQueryTag:
case _OrthancPluginService_GetFindQueryTagName:
case _OrthancPluginService_GetFindQueryValue:
{
const _OrthancPluginFindOperation& p =
*reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
reinterpret_cast<const FindHandler*>(p.query)->Invoke(service, p);
return true;
}
case _OrthancPluginService_CreateImage:
case _OrthancPluginService_CreateImageAccessor:
case _OrthancPluginService_DecodeDicomImage:
ApplyCreateImage(service, parameters);
return true;
case _OrthancPluginService_ComputeMd5:
case _OrthancPluginService_ComputeSha1:
ComputeHash(service, parameters);
return true;
case _OrthancPluginService_LookupDictionary:
ApplyLookupDictionary(parameters);
return true;
case _OrthancPluginService_GenerateUuid:
{
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result =
CopyString(Toolbox::GenerateUuid());
return true;
}
case _OrthancPluginService_CreateFindMatcher:
{
const _OrthancPluginCreateFindMatcher& p =
*reinterpret_cast<const _OrthancPluginCreateFindMatcher*>(parameters);
ParsedDicomFile query(p.query, p.size);
*(p.target) = reinterpret_cast<OrthancPluginFindMatcher*>(new HierarchicalMatcher(query));
return true;
}
case _OrthancPluginService_FreeFindMatcher:
{
const _OrthancPluginFreeFindMatcher& p =
*reinterpret_cast<const _OrthancPluginFreeFindMatcher*>(parameters);
if (p.matcher != NULL)
{
delete reinterpret_cast<HierarchicalMatcher*>(p.matcher);
}
return true;
}
case _OrthancPluginService_FindMatcherIsMatch:
{
const _OrthancPluginFindMatcherIsMatch& p =
*reinterpret_cast<const _OrthancPluginFindMatcherIsMatch*>(parameters);
if (p.matcher == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
ParsedDicomFile query(p.dicom, p.size);
*p.isMatch = reinterpret_cast<const HierarchicalMatcher*>(p.matcher)->Match(query) ? 1 : 0;
return true;
}
}
case _OrthancPluginService_GetPeers:
{
const _OrthancPluginGetPeers& p =
*reinterpret_cast<const _OrthancPluginGetPeers*>(parameters);
*(p.peers) = reinterpret_cast<OrthancPluginPeers*>(new OrthancPeers);
return true;
}
case _OrthancPluginService_FreePeers:
{
const _OrthancPluginFreePeers& p =
*reinterpret_cast<const _OrthancPluginFreePeers*>(parameters);
if (p.peers != NULL)
{
delete reinterpret_cast<OrthancPeers*>(p.peers);
}
return true;
}
case _OrthancPluginService_GetPeersCount:
{
const _OrthancPluginGetPeersCount& p =
*reinterpret_cast<const _OrthancPluginGetPeersCount*>(parameters);
if (p.peers == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeersCount();
return true;
}
}
case _OrthancPluginService_GetPeerName:
{
const _OrthancPluginGetPeerProperty& p =
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
if (p.peers == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerName(p.peerIndex).c_str();
return true;
}
}
case _OrthancPluginService_GetPeerUrl:
{
const _OrthancPluginGetPeerProperty& p =
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
if (p.peers == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
*(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUrl().c_str();
return true;
}
}
case _OrthancPluginService_GetPeerUserProperty:
{
const _OrthancPluginGetPeerProperty& p =
*reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
if (p.peers == NULL ||
p.userProperty == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
else
{
const WebServiceParameters::Dictionary& properties =
reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUserProperties();
WebServiceParameters::Dictionary::const_iterator found =
properties.find(p.userProperty);
if (found == properties.end())
{
*(p.target) = NULL;
}
else
{
*(p.target) = found->second.c_str();
}
return true;
}
}
case _OrthancPluginService_CallPeerApi:
CallPeerApi(parameters);
return true;
case _OrthancPluginService_CreateJob:
{
const _OrthancPluginCreateJob& p =
*reinterpret_cast<const _OrthancPluginCreateJob*>(parameters);
*(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
return true;
}
case _OrthancPluginService_CreateJob2:
{
const _OrthancPluginCreateJob2& p =
*reinterpret_cast<const _OrthancPluginCreateJob2*>(parameters);
*(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
return true;
}
case _OrthancPluginService_FreeJob:
{
const _OrthancPluginFreeJob& p =
*reinterpret_cast<const _OrthancPluginFreeJob*>(parameters);
if (p.job != NULL)
{
delete reinterpret_cast<PluginsJob*>(p.job);
}
return true;
}
case _OrthancPluginService_SubmitJob:
{
const _OrthancPluginSubmitJob& p =
*reinterpret_cast<const _OrthancPluginSubmitJob*>(parameters);
std::string uuid;
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().GetJobsEngine().GetRegistry().Submit
(uuid, reinterpret_cast<PluginsJob*>(p.job), p.priority);
*p.resultId = CopyString(uuid);
return true;
}
case _OrthancPluginService_AutodetectMimeType:
{
const _OrthancPluginRetrieveStaticString& p =
*reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
*p.result = EnumerationToString(SystemToolbox::AutodetectMimeType(p.argument));
return true;
}
case _OrthancPluginService_SetMetricsValue:
{
const _OrthancPluginSetMetricsValue& p =
*reinterpret_cast<const _OrthancPluginSetMetricsValue*>(parameters);
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().GetMetricsRegistry().SetFloatValue(p.name, p.value, Plugins::Convert(p.type));
}
return true;
}
case _OrthancPluginService_SetMetricsIntegerValue:
{
const _OrthancPluginSetMetricsIntegerValue& p =
*reinterpret_cast<const _OrthancPluginSetMetricsIntegerValue*>(parameters);
{
PImpl::ServerContextReference lock(*pimpl_);
lock.GetContext().GetMetricsRegistry().SetIntegerValue(p.name, p.value, Plugins::Convert(p.type));
}
return true;
}
case _OrthancPluginService_EncodeDicomWebJson:
case _OrthancPluginService_EncodeDicomWebXml:
{
const _OrthancPluginEncodeDicomWeb& p =
*reinterpret_cast<const _OrthancPluginEncodeDicomWeb*>(parameters);
DicomWebBinaryFormatter formatter(p.callback);
formatter.Apply(p.target,
(service == _OrthancPluginService_EncodeDicomWebJson),
p.dicom, p.dicomSize);
return true;
}
case _OrthancPluginService_EncodeDicomWebJson2:
case _OrthancPluginService_EncodeDicomWebXml2:
{
const _OrthancPluginEncodeDicomWeb2& p =
*reinterpret_cast<const _OrthancPluginEncodeDicomWeb2*>(parameters);
DicomWebBinaryFormatter formatter(p.callback, p.payload);
formatter.Apply(p.target,
(service == _OrthancPluginService_EncodeDicomWebJson2),
p.dicom, p.dicomSize);
return true;
}
case _OrthancPluginService_GetTagName:
GetTagName(parameters);
return true;
case _OrthancPluginService_CreateDicomInstance:
{
const _OrthancPluginCreateDicomInstance& p =
*reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
*(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
new DicomInstanceFromBuffer(p.buffer, p.size));
return true;
}
case _OrthancPluginService_FreeDicomInstance:
{
const _OrthancPluginFreeDicomInstance& p =
*reinterpret_cast<const _OrthancPluginFreeDicomInstance*>(parameters);
if (p.dicom != NULL)
{
IDicomInstance* obj = reinterpret_cast<IDicomInstance*>(p.dicom);
if (obj->CanBeFreed())
{
delete obj;
}
else
{
throw OrthancException(ErrorCode_Plugin, "Cannot free a DICOM instance provided to a callback");
}
}
return true;
}
case _OrthancPluginService_TranscodeDicomInstance:
{
const _OrthancPluginCreateDicomInstance& p =
*reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
DicomTransferSyntax transferSyntax;
if (p.transferSyntax == NULL ||
!LookupTransferSyntax(transferSyntax, p.transferSyntax))
{
throw OrthancException(ErrorCode_ParameterOutOfRange, "Unsupported transfer syntax: " +
std::string(p.transferSyntax == NULL ? "(null)" : p.transferSyntax));
}
else
{
std::set<DicomTransferSyntax> syntaxes;
syntaxes.insert(transferSyntax);
IDicomTranscoder::DicomImage source;
source.SetExternalBuffer(p.buffer, p.size);
IDicomTranscoder::DicomImage transcoded;
bool success;
{
PImpl::ServerContextReference lock(*pimpl_);
success = lock.GetContext().Transcode(
transcoded, source, syntaxes, true /* allow new sop */);
}
if (success)
{
*(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
new DicomInstanceFromParsed(transcoded));
return true;
}
else
{
throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode image");
}
}
}
case _OrthancPluginService_CreateMemoryBuffer:
{
const _OrthancPluginCreateMemoryBuffer& p =
*reinterpret_cast<const _OrthancPluginCreateMemoryBuffer*>(parameters);
PluginMemoryBuffer32 buffer;
buffer.Resize(p.size);
buffer.Release(p.target);
return true;
}
case _OrthancPluginService_CreateMemoryBuffer64:
{
const _OrthancPluginCreateMemoryBuffer64& p =
*reinterpret_cast<const _OrthancPluginCreateMemoryBuffer64*>(parameters);
PluginMemoryBuffer64 buffer;
buffer.Resize(p.size);
buffer.Release(p.target);
return true;
}
case _OrthancPluginService_RegisterIncomingHttpRequestFilter:
RegisterIncomingHttpRequestFilter(parameters);
return true;
case _OrthancPluginService_RegisterIncomingHttpRequestFilter2:
RegisterIncomingHttpRequestFilter2(parameters);
return true;
case _OrthancPluginService_LoadDicomInstance:
{
const _OrthancPluginLoadDicomInstance& p =
*reinterpret_cast<const _OrthancPluginLoadDicomInstance*>(parameters);
ApplyLoadDicomInstance(p);
return true;
}
case _OrthancPluginService_SetCurrentThreadName:
{
Logging::SetCurrentThreadName(std::string(reinterpret_cast<const char*>(parameters)));
return true;
}
case _OrthancPluginService_AdoptDicomInstance:
{
const _OrthancPluginAdoptDicomInstance& p = *reinterpret_cast<const _OrthancPluginAdoptDicomInstance*>(parameters);
ApplyAdoptDicomInstance(p);
return true;
}
case _OrthancPluginService_GetAttachmentCustomData:
{
const _OrthancPluginGetAttachmentCustomData& p = *reinterpret_cast<const _OrthancPluginGetAttachmentCustomData*>(parameters);
ApplyGetAttachmentCustomData(p);
return true;
}
case _OrthancPluginService_SetAttachmentCustomData:
{
const _OrthancPluginSetAttachmentCustomData& p = *reinterpret_cast<const _OrthancPluginSetAttachmentCustomData*>(parameters);
ApplySetAttachmentCustomData(p);
return true;
}
case _OrthancPluginService_StoreKeyValue:
{
const _OrthancPluginStoreKeyValue& p = *reinterpret_cast<const _OrthancPluginStoreKeyValue*>(parameters);
ApplyStoreKeyValue(p);
return true;
}
case _OrthancPluginService_DeleteKeyValue:
{
const _OrthancPluginDeleteKeyValue& p = *reinterpret_cast<const _OrthancPluginDeleteKeyValue*>(parameters);
ApplyDeleteKeyValue(p);
return true;
}
case _OrthancPluginService_GetKeyValue:
{
const _OrthancPluginGetKeyValue& p = *reinterpret_cast<const _OrthancPluginGetKeyValue*>(parameters);
ApplyGetKeyValue(p);
return true;
}
case _OrthancPluginService_CreateKeysValuesIterator:
{
const _OrthancPluginCreateKeysValuesIterator& p = *reinterpret_cast<const _OrthancPluginCreateKeysValuesIterator*>(parameters);
ApplyCreateKeysValuesIterator(p);
return true;
}
case _OrthancPluginService_FreeKeysValuesIterator:
{
const _OrthancPluginFreeKeysValuesIterator& p = *reinterpret_cast<const _OrthancPluginFreeKeysValuesIterator*>(parameters);
delete reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
return true;
}
case _OrthancPluginService_KeysValuesIteratorNext:
{
const _OrthancPluginKeysValuesIteratorNext& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorNext*>(parameters);
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
*p.done = iterator.Next() ? 1 : 0;
return true;
}
case _OrthancPluginService_KeysValuesIteratorGetKey:
{
const _OrthancPluginKeysValuesIteratorGetKey& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetKey*>(parameters);
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
*p.target = iterator.GetKey().c_str();
return true;
}
case _OrthancPluginService_KeysValuesIteratorGetValue:
{
const _OrthancPluginKeysValuesIteratorGetValue& p = *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetValue*>(parameters);
StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
CopyToMemoryBuffer(p.target, iterator.GetValue());
return true;
}
case _OrthancPluginService_EnqueueValue:
{
const _OrthancPluginEnqueueValue& p = *reinterpret_cast<const _OrthancPluginEnqueueValue*>(parameters);
ApplyEnqueueValue(p);
return true;
}
case _OrthancPluginService_DequeueValue:
{
const _OrthancPluginDequeueValue& p = *reinterpret_cast<const _OrthancPluginDequeueValue*>(parameters);
ApplyDequeueValue(p);
return true;
}
case _OrthancPluginService_GetQueueSize:
{
const _OrthancPluginGetQueueSize& p = *reinterpret_cast<const _OrthancPluginGetQueueSize*>(parameters);
ApplyGetQueueSize(p);
return true;
}
default:
return false;
}
}
bool OrthancPlugins::InvokeProtectedService(SharedLibrary& plugin,
_OrthancPluginService service,
const void* parameters)
{
// Services that must be run in mutual exclusion. Guideline:
// Whenever "pimpl_" is directly accessed by the service, it
// should be listed here.
switch (service)
{
case _OrthancPluginService_RegisterRestCallback:
RegisterRestCallback(parameters, true);
return true;
case _OrthancPluginService_RegisterRestCallbackNoLock:
RegisterRestCallback(parameters, false);
return true;
case _OrthancPluginService_RegisterChunkedRestCallback:
RegisterChunkedRestCallback(parameters);
return true;
case _OrthancPluginService_RegisterOnStoredInstanceCallback:
RegisterOnStoredInstanceCallback(parameters);
return true;
case _OrthancPluginService_RegisterOnChangeCallback:
RegisterOnChangeCallback(parameters);
return true;
case _OrthancPluginService_RegisterWorklistCallback:
RegisterWorklistCallback(parameters);
return true;
case _OrthancPluginService_RegisterFindCallback:
RegisterFindCallback(parameters);
return true;
case _OrthancPluginService_RegisterMoveCallback:
RegisterMoveCallback(parameters);
return true;
case _OrthancPluginService_RegisterDecodeImageCallback:
RegisterDecodeImageCallback(parameters);
return true;
case _OrthancPluginService_RegisterTranscoderCallback:
RegisterTranscoderCallback(parameters);
return true;
case _OrthancPluginService_RegisterJobsUnserializer:
RegisterJobsUnserializer(parameters);
return true;
case _OrthancPluginService_RegisterIncomingDicomInstanceFilter:
RegisterIncomingDicomInstanceFilter(parameters);
return true;
case _OrthancPluginService_RegisterIncomingCStoreInstanceFilter:
RegisterIncomingCStoreInstanceFilter(parameters);
return true;
case _OrthancPluginService_RegisterReceivedInstanceCallback:
RegisterReceivedInstanceCallback(parameters);
return true;
case _OrthancPluginService_RegisterRefreshMetricsCallback:
RegisterRefreshMetricsCallback(parameters);
return true;
case _OrthancPluginService_RegisterStorageCommitmentScpCallback:
RegisterStorageCommitmentScpCallback(parameters);
return true;
case _OrthancPluginService_RegisterStorageArea:
case _OrthancPluginService_RegisterStorageArea2:
case _OrthancPluginService_RegisterStorageArea3:
{
if (pimpl_->storageArea_.get() == NULL)
{
if (service == _OrthancPluginService_RegisterStorageArea)
{
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v1)";
const _OrthancPluginRegisterStorageArea& p =
*reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters);
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
}
else if (service == _OrthancPluginService_RegisterStorageArea2)
{
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v2)";
const _OrthancPluginRegisterStorageArea2& p =
*reinterpret_cast<const _OrthancPluginRegisterStorageArea2*>(parameters);
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
}
else if (service == _OrthancPluginService_RegisterStorageArea3)
{
CLOG(INFO, PLUGINS) << "Plugin has registered a custom storage area (v3)";
const _OrthancPluginRegisterStorageArea3& p =
*reinterpret_cast<const _OrthancPluginRegisterStorageArea3*>(parameters);
pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
pimpl_->hasStorageAreaCustomData_ = true;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
else
{
throw OrthancException(ErrorCode_StorageAreaAlreadyRegistered);
}
return true;
}
case _OrthancPluginService_SetPluginProperty:
{
const _OrthancPluginSetPluginProperty& p =
*reinterpret_cast<const _OrthancPluginSetPluginProperty*>(parameters);
pimpl_->properties_[std::make_pair(p.plugin, p.property)] = p.value;
return true;
}
case _OrthancPluginService_GetCommandLineArgumentsCount:
{
const _OrthancPluginReturnSingleValue& p =
*reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
*(p.resultUint32) = pimpl_->argc_ - 1;
return true;
}
case _OrthancPluginService_GetCommandLineArgument:
{
const _OrthancPluginGlobalProperty& p =
*reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
if (p.property + 1 > pimpl_->argc_)
{
return false;
}
else
{
std::string arg = std::string(pimpl_->argv_[p.property + 1]);
*(p.result) = CopyString(arg);
return true;
}
}
case _OrthancPluginService_RegisterDatabaseBackend:
{
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 1)";
LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
const _OrthancPluginRegisterDatabaseBackend& p =
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters);
if (pimpl_->database_.get() == NULL &&
pimpl_->databaseV3_.get() == NULL &&
pimpl_->databaseV4_.get() == NULL)
{
pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
*p.backend, NULL, 0, p.payload));
}
else
{
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
}
*(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
return true;
}
case _OrthancPluginService_RegisterDatabaseBackendV2:
{
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 2)";
LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
const _OrthancPluginRegisterDatabaseBackendV2& p =
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV2*>(parameters);
if (pimpl_->database_.get() == NULL &&
pimpl_->databaseV3_.get() == NULL &&
pimpl_->databaseV4_.get() == NULL)
{
pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
*p.backend, p.extensions,
p.extensionsSize, p.payload));
}
else
{
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
}
*(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
return true;
}
case _OrthancPluginService_RegisterDatabaseBackendV3:
{
LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 3)";
const _OrthancPluginRegisterDatabaseBackendV3& p =
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV3*>(parameters);
if (pimpl_->database_.get() == NULL &&
pimpl_->databaseV3_.get() == NULL &&
pimpl_->databaseV4_.get() == NULL)
{
pimpl_->databaseV3_.reset(new OrthancPluginDatabaseV3(plugin, GetErrorDictionary(), p.backend,
p.backendSize, p.database, pimpl_->databaseServerIdentifier_));
pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries;
}
else
{
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
}
return true;
}
case _OrthancPluginService_RegisterDatabaseBackendV4:
{
CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end (v4)";
const _OrthancPluginRegisterDatabaseBackendV4& p =
*reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV4*>(parameters);
if (pimpl_->database_.get() == NULL &&
pimpl_->databaseV3_.get() == NULL &&
pimpl_->databaseV4_.get() == NULL)
{
pimpl_->databaseV4_.reset(new OrthancPluginDatabaseV4(plugin, GetErrorDictionary(), p, pimpl_->databaseServerIdentifier_));
pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries;
}
else
{
throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
}
return true;
}
case _OrthancPluginService_DatabaseAnswer:
throw OrthancException(ErrorCode_InternalError); // Implemented before locking (*)
case _OrthancPluginService_RegisterErrorCode:
{
const _OrthancPluginRegisterErrorCode& p =
*reinterpret_cast<const _OrthancPluginRegisterErrorCode*>(parameters);
*(p.target) = pimpl_->dictionary_.Register(plugin, p.code, p.httpStatus, p.message);
return true;
}
case _OrthancPluginService_RegisterDictionaryTag:
{
const _OrthancPluginRegisterDictionaryTag& p =
*reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters);
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
Plugins::Convert(p.vr), p.name,
p.minMultiplicity, p.maxMultiplicity, "");
return true;
}
case _OrthancPluginService_RegisterPrivateDictionaryTag:
{
const _OrthancPluginRegisterPrivateDictionaryTag& p =
*reinterpret_cast<const _OrthancPluginRegisterPrivateDictionaryTag*>(parameters);
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
Plugins::Convert(p.vr), p.name,
p.minMultiplicity, p.maxMultiplicity, p.privateCreator);
return true;
}
case _OrthancPluginService_ReconstructMainDicomTags:
{
const _OrthancPluginReconstructMainDicomTags& p =
*reinterpret_cast<const _OrthancPluginReconstructMainDicomTags*>(parameters);
if (pimpl_->database_.get() == NULL)
{
throw OrthancException(ErrorCode_DatabasePlugin,
"The service ReconstructMainDicomTags can only be invoked by custom database plugins");
}
VoidDatabaseListener listener;
{
IPluginStorageArea& storage = *reinterpret_cast<IPluginStorageArea*>(p.storageArea);
std::unique_ptr<IDatabaseWrapper::ITransaction> transaction(
pimpl_->database_->StartTransaction(TransactionType_ReadWrite, listener));
ServerToolbox::ReconstructMainDicomTags(*transaction, storage, Plugins::Convert(p.level));
transaction->Commit(0);
}
return true;
}
case _OrthancPluginService_GenerateRestApiAuthorizationToken:
{
const _OrthancPluginRetrieveDynamicString& p =
*reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
const std::string token = Toolbox::GenerateUuid();
pimpl_->authorizationTokens_.insert(token);
*p.result = CopyString("Bearer " + token);
return true;
}
case _OrthancPluginService_RegisterWebDavCollection:
{
CLOG(INFO, PLUGINS) << "Plugin has registered a WebDAV collection";
const _OrthancPluginRegisterWebDavCollection& p =
*reinterpret_cast<const _OrthancPluginRegisterWebDavCollection*>(parameters);
pimpl_->webDavCollections_.push_back(new WebDavCollection(GetErrorDictionary(), p));
return true;
}
case _OrthancPluginService_GetDatabaseServerIdentifier:
{
const _OrthancPluginRetrieveStaticString& p =
*reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
*p.result = pimpl_->databaseServerIdentifier_.c_str();
return true;
}
default:
{
// This service is unknown to the Orthanc plugin engine
return false;
}
}
}
bool OrthancPlugins::InvokeService(SharedLibrary& plugin,
_OrthancPluginService service,
const void* parameters)
{
CLOG(TRACE, PLUGINS) << "Calling service " << service << " from plugin " << plugin.GetPath();
if (service == _OrthancPluginService_DatabaseAnswer)
{
// This case solves a deadlock at (*) reported by James Webster
// on 2015-10-27 that was present in versions of Orthanc <=
// 0.9.4 and related to database plugins implementing a custom
// index. The problem was that locking the database is already
// ensured by the "ServerIndex" class if the invoked service is
// "DatabaseAnswer".
DatabaseAnswer(parameters);
return true;
}
if (InvokeSafeService(plugin, service, parameters))
{
// The invoked service does not require locking
return true;
}
else
{
// The invoked service requires locking
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); // (*)
return InvokeProtectedService(plugin, service, parameters);
}
}
bool OrthancPlugins::HasStorageArea() const
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
return pimpl_->storageArea_.get() != NULL;
}
bool OrthancPlugins::HasDatabaseBackend() const
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
return (pimpl_->database_.get() != NULL ||
pimpl_->databaseV3_.get() != NULL ||
pimpl_->databaseV4_.get() != NULL);
}
IPluginStorageArea* OrthancPlugins::CreateStorageArea()
{
if (!HasStorageArea())
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
return pimpl_->storageArea_->Create();
}
}
const SharedLibrary& OrthancPlugins::GetStorageAreaLibrary() const
{
if (!HasStorageArea())
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
else
{
return pimpl_->storageArea_->GetSharedLibrary();
}
}
IDatabaseWrapper& OrthancPlugins::GetDatabaseBackend()
{
if (pimpl_->database_.get() != NULL)
{
return *pimpl_->database_;
}
else if (pimpl_->databaseV3_.get() != NULL)
{
return *pimpl_->databaseV3_;
}
else if (pimpl_->databaseV4_.get() != NULL)
{
return *pimpl_->databaseV4_;
}
else
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
}
const SharedLibrary& OrthancPlugins::GetDatabaseBackendLibrary() const
{
if (pimpl_->database_.get() != NULL)
{
return pimpl_->database_->GetSharedLibrary();
}
else if (pimpl_->databaseV3_.get() != NULL)
{
return pimpl_->databaseV3_->GetSharedLibrary();
}
else if (pimpl_->databaseV4_.get() != NULL)
{
return pimpl_->databaseV4_->GetSharedLibrary();
}
else
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
}
const char* OrthancPlugins::GetProperty(const char* plugin,
_OrthancPluginProperty property) const
{
PImpl::Property p = std::make_pair(plugin, property);
PImpl::Properties::const_iterator it = pimpl_->properties_.find(p);
if (it == pimpl_->properties_.end())
{
return NULL;
}
else
{
return it->second.c_str();
}
}
void OrthancPlugins::SetCommandLineArguments(int argc, char* argv[])
{
if (argc < 1 || argv == NULL)
{
throw OrthancException(ErrorCode_ParameterOutOfRange);
}
pimpl_->argc_ = argc;
pimpl_->argv_ = argv;
}
PluginsManager& OrthancPlugins::GetManager()
{
return pimpl_->manager_;
}
const PluginsManager& OrthancPlugins::GetManager() const
{
return pimpl_->manager_;
}
PluginsErrorDictionary& OrthancPlugins::GetErrorDictionary()
{
return pimpl_->dictionary_;
}
IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler()
{
if (HasWorklistHandler())
{
return new WorklistHandler(*this);
}
else
{
return NULL;
}
}
bool OrthancPlugins::HasWorklistHandler()
{
boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
return pimpl_->worklistCallback_ != NULL;
}
IFindRequestHandler* OrthancPlugins::ConstructFindRequestHandler()
{
if (HasFindHandler())
{
return new FindHandler(*this);
}
else
{
return NULL;
}
}
bool OrthancPlugins::HasFindHandler()
{
boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
return pimpl_->findCallback_ != NULL;
}
IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler()
{
if (HasMoveHandler())
{
return new MoveHandler(*this);
}
else
{
return NULL;
}
}
bool OrthancPlugins::HasMoveHandler()
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
return pimpl_->moveCallbacks_.callback != NULL;
}
bool OrthancPlugins::HasCustomImageDecoder()
{
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
return !pimpl_->decodeImageCallbacks_.empty();
}
bool OrthancPlugins::HasCustomTranscoder()
{
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
return !pimpl_->transcoderCallbacks_.empty();
}
ImageAccessor* OrthancPlugins::Decode(const void* dicom,
size_t size,
unsigned int frame)
{
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
for (PImpl::DecodeImageCallbacks::const_iterator
decoder = pimpl_->decodeImageCallbacks_.begin();
decoder != pimpl_->decodeImageCallbacks_.end(); ++decoder)
{
OrthancPluginImage* pluginImage = NULL;
if ((*decoder) (&pluginImage, dicom, size, frame) == OrthancPluginErrorCode_Success &&
pluginImage != NULL)
{
return reinterpret_cast<ImageAccessor*>(pluginImage);
}
}
return NULL;
}
bool OrthancPlugins::IsAllowed(HttpMethod method,
const char* uri,
const char* ip,
const char* username,
const HttpToolbox::Arguments& httpHeaders,
const HttpToolbox::GetArguments& getArguments)
{
OrthancPluginHttpMethod cMethod = Plugins::Convert(method);
std::vector<const char*> httpKeys(httpHeaders.size());
std::vector<const char*> httpValues(httpHeaders.size());
size_t pos = 0;
for (HttpToolbox::Arguments::const_iterator
it = httpHeaders.begin(); it != httpHeaders.end(); ++it, pos++)
{
httpKeys[pos] = it->first.c_str();
httpValues[pos] = it->second.c_str();
}
std::vector<const char*> getKeys(getArguments.size());
std::vector<const char*> getValues(getArguments.size());
for (size_t i = 0; i < getArguments.size(); i++)
{
getKeys[i] = getArguments[i].first.c_str();
getValues[i] = getArguments[i].second.c_str();
}
{
boost::shared_lock<boost::shared_mutex> lock(pimpl_->incomingHttpRequestFilterMutex_);
// Improved callback with support for GET arguments, since Orthanc 1.3.0
for (PImpl::IncomingHttpRequestFilters2::const_iterator
filter = pimpl_->incomingHttpRequestFilters2_.begin();
filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter)
{
int32_t allowed = (*filter) (cMethod, uri, ip,
httpKeys.size(),
httpKeys.empty() ? NULL : &httpKeys[0],
httpValues.empty() ? NULL : &httpValues[0],
getKeys.size(),
getKeys.empty() ? NULL : &getKeys[0],
getValues.empty() ? NULL : &getValues[0]);
if (allowed == 0)
{
return false;
}
else if (allowed != 1)
{
// The callback is only allowed to answer 0 or 1
throw OrthancException(ErrorCode_Plugin);
}
}
for (PImpl::IncomingHttpRequestFilters::const_iterator
filter = pimpl_->incomingHttpRequestFilters_.begin();
filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter)
{
int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(),
httpKeys.empty() ? NULL : &httpKeys[0],
httpValues.empty() ? NULL : &httpValues[0]);
if (allowed == 0)
{
return false;
}
else if (allowed != 1)
{
// The callback is only allowed to answer 0 or 1
throw OrthancException(ErrorCode_Plugin);
}
}
}
return true;
}
IJob* OrthancPlugins::UnserializeJob(const std::string& type,
const Json::Value& value)
{
const std::string serialized = value.toStyledString();
boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
for (PImpl::JobsUnserializers::iterator
unserializer = pimpl_->jobsUnserializers_.begin();
unserializer != pimpl_->jobsUnserializers_.end(); ++unserializer)
{
OrthancPluginJob* job = (*unserializer) (type.c_str(), serialized.c_str());
if (job != NULL)
{
return reinterpret_cast<PluginsJob*>(job);
}
}
return NULL;
}
void OrthancPlugins::RefreshMetrics()
{
boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
for (PImpl::RefreshMetricsCallbacks::iterator
it = pimpl_->refreshMetricsCallbacks_.begin();
it != pimpl_->refreshMetricsCallbacks_.end(); ++it)
{
if (*it != NULL)
{
(*it) ();
}
}
}
class OrthancPlugins::HttpServerChunkedReader : public IHttpHandler::IChunkedRequestReader
{
private:
OrthancPluginServerChunkedRequestReader* reader_;
_OrthancPluginChunkedRestCallback parameters_;
PluginsErrorDictionary& errorDictionary_;
public:
HttpServerChunkedReader(OrthancPluginServerChunkedRequestReader* reader,
const _OrthancPluginChunkedRestCallback& parameters,
PluginsErrorDictionary& errorDictionary) :
reader_(reader),
parameters_(parameters),
errorDictionary_(errorDictionary)
{
assert(reader_ != NULL);
}
virtual ~HttpServerChunkedReader()
{
assert(reader_ != NULL);
parameters_.finalize(reader_);
}
virtual void AddBodyChunk(const void* data,
size_t size) ORTHANC_OVERRIDE
{
if (static_cast<uint32_t>(size) != size)
{
throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
}
assert(reader_ != NULL);
parameters_.addChunk(reader_, data, size);
}
virtual void Execute(HttpOutput& output) ORTHANC_OVERRIDE
{
assert(reader_ != NULL);
PImpl::PluginHttpOutput pluginOutput(output);
OrthancPluginErrorCode error = parameters_.execute(
reader_, reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput));
pluginOutput.Close(error, errorDictionary_);
}
};
bool OrthancPlugins::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
RequestOrigin origin,
const char* remoteIp,
const char* username,
HttpMethod method,
const UriComponents& uri,
const HttpToolbox::Arguments& headers)
{
if (method != HttpMethod_Post &&
method != HttpMethod_Put)
{
throw OrthancException(ErrorCode_InternalError);
}
RestCallbackMatcher matcher(uri);
PImpl::ChunkedRestCallback* callback = NULL;
// Loop over the callbacks registered by the plugins
boost::shared_lock<boost::shared_mutex> lock(pimpl_->restCallbackRegistrationMutex_);
for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin();
it != pimpl_->chunkedRestCallbacks_.end(); ++it)
{
if (matcher.IsMatch((*it)->GetRegularExpression()))
{
callback = *it;
break;
}
}
if (callback == NULL)
{
// Callback not found
return false;
}
else
{
OrthancPluginServerChunkedRequestReaderFactory handler;
switch (method)
{
case HttpMethod_Post:
handler = callback->GetParameters().postHandler;
break;
case HttpMethod_Put:
handler = callback->GetParameters().putHandler;
break;
default:
handler = NULL;
break;
}
if (handler == NULL)
{
return false;
}
else
{
CLOG(INFO, PLUGINS) << "Delegating chunked HTTP request to plugin for URI: " << matcher.GetFlatUri();
HttpRequestConverter converter(matcher, method, headers);
converter.GetRequest().body = NULL;
converter.GetRequest().bodySize = 0;
OrthancPluginServerChunkedRequestReader* reader = NULL;
OrthancPluginErrorCode errorCode = handler(
&reader, matcher.GetFlatUri().c_str(), &converter.GetRequest());
if (errorCode != OrthancPluginErrorCode_Success)
{
throw OrthancException(static_cast<ErrorCode>(errorCode));
}
else if (reader == NULL)
{
// The plugin has not created a reader for chunked body
return false;
}
else
{
target.reset(new HttpServerChunkedReader(reader, callback->GetParameters(), GetErrorDictionary()));
return true;
}
}
}
}
IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::CreateStorageCommitment(
const std::string& jobId,
const std::string& transactionUid,
const std::vector<std::string>& sopClassUids,
const std::vector<std::string>& sopInstanceUids,
const std::string& remoteAet,
const std::string& calledAet)
{
boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
for (PImpl::StorageCommitmentScpCallbacks::iterator
it = pimpl_->storageCommitmentScpCallbacks_.begin();
it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
{
assert(*it != NULL);
IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment
(jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
if (handler != NULL)
{
return handler;
}
}
return NULL;
}
bool OrthancPlugins::TranscodeBuffer(std::string& target,
const void* buffer,
size_t size,
const std::set<DicomTransferSyntax>& allowedSyntaxes,
bool allowNewSopInstanceUid)
{
boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
if (pimpl_->transcoderCallbacks_.empty())
{
return false;
}
std::vector<const char*> uids;
uids.reserve(allowedSyntaxes.size());
for (std::set<DicomTransferSyntax>::const_iterator
it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it)
{
uids.push_back(GetTransferSyntaxUid(*it));
}
for (PImpl::TranscoderCallbacks::const_iterator
transcoder = pimpl_->transcoderCallbacks_.begin();
transcoder != pimpl_->transcoderCallbacks_.end(); ++transcoder)
{
PluginMemoryBuffer32 a;
if ((*transcoder) (a.GetObject(), buffer, size, uids.empty() ? NULL : &uids[0],
static_cast<uint32_t>(uids.size()), allowNewSopInstanceUid) ==
OrthancPluginErrorCode_Success)
{
a.MoveToString(target);
return true;
}
}
return false;
}
bool OrthancPlugins::IsValidAuthorizationToken(const std::string& token) const
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
return (pimpl_->authorizationTokens_.find(token) != pimpl_->authorizationTokens_.end());
}
unsigned int OrthancPlugins::GetMaxDatabaseRetries() const
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
return pimpl_->maxDatabaseRetries_;
}
void OrthancPlugins::RegisterWebDavCollections(HttpServer& target)
{
boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
while (!pimpl_->webDavCollections_.empty())
{
WebDavCollection* collection = pimpl_->webDavCollections_.front();
assert(collection != NULL);
UriComponents components;
Toolbox::SplitUriComponents(components, collection->GetUri());
target.Register(components, collection);
pimpl_->webDavCollections_.pop_front();
}
}
}