/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
**/
#include "../../Sources/PrecompiledHeadersServer.h"
#include "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
#include
#include
#include
#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 instance_;
void Setup(const void* buffer,
size_t size)
{
buffer_.assign(reinterpret_cast(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 parsed_;
std::unique_ptr 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 items_;
public:
explicit PathHelper(const std::vector& 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(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 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)->AddResource(f.release());
return OrthancPluginErrorCode_Success;
}
catch (OrthancException& e)
{
return static_cast(e.GetErrorCode());
}
catch (...)
{
return OrthancPluginErrorCode_InternalError;
}
}
static OrthancPluginErrorCode AddFolder(
OrthancPluginWebDavCollection* collection,
const char* displayName,
const char* creationTime)
{
try
{
std::unique_ptr f(new Folder(displayName));
f->SetCreationTime(boost::posix_time::from_iso_string(creationTime));
reinterpret_cast(collection)->AddResource(f.release());
return OrthancPluginErrorCode_Success;
}
catch (OrthancException& e)
{
return static_cast(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(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(data), size);
target.modificationTime_ = boost::posix_time::from_iso_string(creationTime);
return OrthancPluginErrorCode_Success;
}
catch (Orthanc::OrthancException& e)
{
return static_cast(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& 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(code));
}
}
virtual bool ListCollection(Collection& collection,
const std::vector& path)
{
PathHelper helper(path);
uint8_t isExisting;
OrthancPluginErrorCode code = listFolder_(&isExisting, reinterpret_cast(&collection),
AddFile, AddFolder, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return (isExisting != 0);
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast(code));
}
}
virtual bool GetFileContent(MimeType& mime,
std::string& content,
boost::posix_time::ptime& modificationTime,
const std::vector& path)
{
PathHelper helper(path);
ContentTarget target(path.back(), mime, content, modificationTime);
OrthancPluginErrorCode code = retrieveFile_(
reinterpret_cast(&target),
ContentTarget::RetrieveFile, helper.GetSize(), helper.GetItems(), payload_);
if (code == OrthancPluginErrorCode_Success)
{
return target.IsSent();
}
else
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast(code));
}
}
virtual bool StoreFile(const std::string& content,
const std::vector& 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(code));
}
}
virtual bool CreateFolder(const std::vector& 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(code));
}
}
virtual bool DeleteItem(const std::vector& 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(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(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& 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& 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 range(new PluginMemoryBuffer64);
range->Assign(reinterpret_cast(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(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(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 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(*whole).Assign(buffer, size, free_);
return GetRangeFromWhole(whole, start, end);
}
else
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast(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 whole(new PluginMemoryBuffer64);
OrthancPluginErrorCode error = readWhole_(dynamic_cast(*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(error));
}
}
else
{
std::unique_ptr 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(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(&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(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(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 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(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 names_;
std::vector 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 peers;
lock.GetConfiguration().GetListOfOrthancPeers(peers);
names_.reserve(peers.size());
parameters_.reserve(peers.size());
for (std::set::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(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& parentTags,
const std::vector& 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 groups(parentTags.size());
std::vector elements(parentTags.size());
std::vector 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(parentIndexes[i]);
}
bool empty = parentTags.empty();
currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
if (oldCallback_ != NULL)
{
oldCallback_(reinterpret_cast(this),
DicomWebBinaryFormatter::Setter,
static_cast(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(this),
DicomWebBinaryFormatter::Setter,
static_cast(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 errorDetails_;
bool logDetails_;
State state_;
std::string multipartSubType_;
std::string multipartContentType_;
std::string multipartFirstPart_;
std::map 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& 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(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 parts;
std::vector sizes;
std::vector*> 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(error),
GetErrorDetails(),
IsLogDetails());
}
else
{
throw OrthancException(static_cast(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(&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(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& sopClassUids,
const std::vector& 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 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(n),
remoteAet.c_str(), calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
throw OrthancException(static_cast(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 Property;
typedef std::list RestCallbacks;
typedef std::list ChunkedRestCallbacks;
typedef std::list OnStoredCallbacks;
typedef std::list OnChangeCallbacks;
typedef std::list IncomingHttpRequestFilters;
typedef std::list IncomingHttpRequestFilters2;
typedef std::list IncomingDicomInstanceFilters;
typedef std::list IncomingCStoreInstanceFilters;
typedef std::list DecodeImageCallbacks;
typedef std::list TranscoderCallbacks;
typedef std::list JobsUnserializers;
typedef std::list RefreshMetricsCallbacks;
typedef std::list StorageCommitmentScpCallbacks;
typedef std::map Properties;
typedef std::list 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 storageArea_;
std::set 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 database_;
std::unique_ptr databaseV3_; // New in Orthanc 1.9.2
std::unique_ptr 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 matcher_;
std::unique_ptr 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(&answers),
reinterpret_cast(this),
remoteAet.c_str(),
calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
Reset();
that_.GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast(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 summary(matcher_->Extract(f));
reinterpret_cast(answers)->Add(*summary);
}
};
class OrthancPlugins::FindHandler : public IFindRequestHandler
{
private:
OrthancPlugins& that_;
std::unique_ptr 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& 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::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(&answers),
reinterpret_cast(this),
remoteAet.c_str(),
calledAet.c_str());
if (error != OrthancPluginErrorCode_Success)
{
Reset();
that_.GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast(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(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(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(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(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(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast(DicomToJsonFlags_IncludeBinary) ||
static_cast(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast(DicomToJsonFlags_IncludePrivateTags) ||
static_cast(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast(DicomToJsonFlags_IncludeUnknownTags) ||
static_cast(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast(DicomToJsonFlags_IncludePixelData) ||
static_cast(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast(DicomToJsonFlags_ConvertBinaryToNull) ||
static_cast(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast(DicomToJsonFlags_ConvertBinaryToAscii) ||
static_cast(OrthancPluginDicomToJsonFlags_StopAfterPixelData) != static_cast(DicomToJsonFlags_StopAfterPixelData) ||
static_cast(OrthancPluginDicomToJsonFlags_SkipGroupLengths) != static_cast(DicomToJsonFlags_SkipGroupLengths) ||
static_cast(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast(DicomFromJsonFlags_DecodeDataUriScheme) ||
static_cast(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast(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& keys,
std::vector& 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& keys,
std::vector& 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 groups_;
std::vector 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 getKeys_;
std::vector getValues_;
std::vector headersKeys_;
std::vector 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 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(&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 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(&wrapped),
instanceId.c_str());
if (error != OrthancPluginErrorCode_Success)
{
GetErrorDictionary().LogError(error, true);
throw OrthancException(static_cast(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(&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(&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::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(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(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback "
<< (mutualExclusion ? "with" : "without")
<< " mutual exclusion on: "
<< p.pathRegularExpression;
{
boost::unique_lock 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(parameters);
CLOG(INFO, PLUGINS) << "Plugin has registered a REST callback for chunked streams on: "
<< p.pathRegularExpression;
{
boost::unique_lock lock(pimpl_->restCallbackRegistrationMutex_);
pimpl_->chunkedRestCallbacks_.push_back(new PImpl::ChunkedRestCallback(p));
}
}
void OrthancPlugins::RegisterOnStoredInstanceCallback(const void* parameters)
{
const _OrthancPluginOnStoredInstanceCallback& p =
*reinterpret_cast(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(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(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(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(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(parameters);
boost::unique_lock 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(parameters);
boost::unique_lock 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(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(parameters);
boost::unique_lock 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(parameters);
boost::unique_lock 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(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(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(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(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(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(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.SetContentType(p.mimeType);
translatedOutput.Answer(p.answer, p.answerSize);
}
void OrthancPlugins::Redirect(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.Redirect(p.argument);
}
void OrthancPlugins::SendHttpStatusCode(const void* parameters)
{
const _OrthancPluginSendHttpStatusCode& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.SendStatus(static_cast(p.status));
}
void OrthancPlugins::SendHttpStatus(const void* parameters)
{
const _OrthancPluginSendHttpStatus& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
HttpStatus status = static_cast(p.status);
if (p.bodySize > 0 && p.body != NULL)
{
translatedOutput.SendStatus(status, reinterpret_cast(p.body), p.bodySize);
}
else
{
translatedOutput.SendStatus(status);
}
}
void OrthancPlugins::SendUnauthorized(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.SendUnauthorized(p.argument);
}
void OrthancPlugins::SendMethodNotAllowed(const void* parameters)
{
const _OrthancPluginOutputPlusArgument& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.SendMethodNotAllowed(p.argument);
}
void OrthancPlugins::SetCookie(const void* parameters)
{
const _OrthancPluginSetHttpHeader& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.SetCookie(p.key, p.value);
}
void OrthancPlugins::SetHttpHeader(const void* parameters)
{
const _OrthancPluginSetHttpHeader& p =
*reinterpret_cast(parameters);
HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput();
translatedOutput.AddHeader(p.key, p.value);
}
void OrthancPlugins::SetHttpErrorDetails(const void* parameters)
{
const _OrthancPluginSetHttpErrorDetails& p =
*reinterpret_cast(parameters);
PImpl::PluginHttpOutput* output =
reinterpret_cast(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(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(parameters);
HttpOutput& translatedOutput = reinterpret_cast(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(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(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(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 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(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(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 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(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 httpHeaders;
ThrowOnHttpError(IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders));
}
void OrthancPlugins::LookupResource(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginRetrieveDynamicString& p =
*reinterpret_cast(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 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(parameters);
if (p.instance == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
const DicomInstanceToStore& instance =
reinterpret_cast(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(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 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(parameters);
std::string result;
{
std::unique_ptr 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& 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 copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
ImageProcessing::Copy(*copy, *image);
image.reset(NULL);
return reinterpret_cast(copy.release());
}
else
{
return reinterpret_cast(image.release());
}
}
void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
const void* parameters)
{
const _OrthancPluginAccessDicomInstance2& p =
*reinterpret_cast(parameters);
if (p.instance == NULL)
{
throw OrthancException(ErrorCode_NullPointer);
}
const DicomInstanceToStore& instance =
reinterpret_cast(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 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(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(parameters);
std::unique_ptr image;
switch (p.format)
{
case OrthancPluginImageFormat_Png:
{
image.reset(new PngReader);
reinterpret_cast(*image).ReadFromMemory(p.data, p.size);
break;
}
case OrthancPluginImageFormat_Jpeg:
{
image.reset(new JpegReader);
reinterpret_cast(*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(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(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(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(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(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(client.GetLastStatus());
if (!success)
{
HttpClient::ThrowException(client.GetLastStatus());
}
}
void OrthancPlugins::CallRestApi(const void* parameters)
{
const _OrthancPluginCallRestApi& p = *reinterpret_cast(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 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(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(parameters);
const OrthancPeers& peers = *reinterpret_cast(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(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(parameters);
const ImageAccessor& source = *reinterpret_cast(p.source);
std::unique_ptr 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(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