Orthanc/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h
2025-06-23 19:07:37 +05:30

1730 lines
48 KiB
C++

/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#pragma once
#include "OrthancPluginException.h"
#include <orthanc/OrthancCPlugin.h>
#include <boost/noncopyable.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <json/value.h>
#include <vector>
#include <list>
#include <set>
#include <map>
/**
* The definition of ORTHANC_PLUGINS_VERSION_IS_ABOVE below is for
* backward compatibility with Orthanc SDK <= 1.3.0.
*
* $ hg diff -r Orthanc-1.3.0:Orthanc-1.3.1 ../../../Plugins/Include/orthanc/OrthancCPlugin.h
*
**/
#if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major || \
(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major && \
(ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor || \
(ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor && \
ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
#endif
#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision) \
(ORTHANC_VERSION_MAJOR > major || \
(ORTHANC_VERSION_MAJOR == major && \
(ORTHANC_VERSION_MINOR > minor || \
(ORTHANC_VERSION_MINOR == minor && \
ORTHANC_VERSION_REVISION >= revision))))
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 1
#else
# define HAS_ORTHANC_PLUGIN_FIND_MATCHER 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 4, 2)
# define HAS_ORTHANC_PLUGIN_PEERS 1
# define HAS_ORTHANC_PLUGIN_JOB 1
#else
# define HAS_ORTHANC_PLUGIN_PEERS 0
# define HAS_ORTHANC_PLUGIN_JOB 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 1
#else
# define HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4)
# define HAS_ORTHANC_PLUGIN_METRICS 1
#else
# define HAS_ORTHANC_PLUGIN_METRICS 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 1, 0)
# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 1
#else
# define HAS_ORTHANC_PLUGIN_HTTP_CLIENT 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 1
#else
# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 1
#else
# define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 1
#else
# define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2)
# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 1
#else
# define HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 1)
# define HAS_ORTHANC_PLUGIN_WEBDAV 1
#else
# define HAS_ORTHANC_PLUGIN_WEBDAV 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
# define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 1
#else
# define HAS_ORTHANC_PLUGIN_LOG_MESSAGE 0
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 1
# define HAS_ORTHANC_PLUGIN_QUEUES 1
#else
# define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES 0
# define HAS_ORTHANC_PLUGIN_QUEUES 0
#endif
// Macro to tag a function as having been deprecated
#if (__cplusplus >= 201402L) // C++14
# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) [[deprecated]] f
#elif defined(__GNUC__) || defined(__clang__)
# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) f __attribute__((deprecated))
#elif defined(_MSC_VER)
# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(f) __declspec(deprecated) f
#else
# define ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED
#endif
#if !defined(__ORTHANC_FILE__)
# if defined(_MSC_VER)
# pragma message("Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries")
# else
# warning Warning: Macro __ORTHANC_FILE__ is not defined, this will leak the full path of the source files in the binaries
# endif
# define __ORTHANC_FILE__ __FILE__
#endif
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
# define ORTHANC_PLUGINS_LOG_ERROR(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Error, __ORTHANC_FILE__, __LINE__, msg)
# define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Warning, __ORTHANC_FILE__, __LINE__, msg)
# define ORTHANC_PLUGINS_LOG_INFO(msg) ::OrthancPlugins::LogMessage(OrthancPluginLogLevel_Info, __ORTHANC_FILE__, __LINE__, msg)
#else
# define ORTHANC_PLUGINS_LOG_ERROR(msg) ::OrthancPlugins::LogError(msg)
# define ORTHANC_PLUGINS_LOG_WARNING(msg) ::OrthancPlugins::LogWarning(msg)
# define ORTHANC_PLUGINS_LOG_INFO(msg) ::OrthancPlugins::LogInfo(msg)
#endif
namespace OrthancPlugins
{
typedef std::map<std::string, std::string> HttpHeaders;
typedef void (*RestCallback) (OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request);
void SetGlobalContext(OrthancPluginContext* context);
void SetGlobalContext(OrthancPluginContext* context,
const char* pluginName);
void ResetGlobalContext();
bool HasGlobalContext();
OrthancPluginContext* GetGlobalContext();
class OrthancImage;
class MemoryBuffer : public boost::noncopyable
{
private:
OrthancPluginMemoryBuffer buffer_;
void Check(OrthancPluginErrorCode code);
bool CheckHttp(OrthancPluginErrorCode code);
public:
MemoryBuffer();
~MemoryBuffer()
{
Clear();
}
OrthancPluginMemoryBuffer* operator*()
{
return &buffer_;
}
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
// Copy of the given buffer into the memory managed by the Orthanc core
void Assign(const void* buffer,
size_t size);
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
void Assign(const std::string& s);
#endif
// This transfers ownership from "other" to "this"
void Assign(OrthancPluginMemoryBuffer& other);
void Swap(MemoryBuffer& other);
OrthancPluginMemoryBuffer Release();
const void* GetData() const
{
if (buffer_.size > 0)
{
return buffer_.data;
}
else
{
return NULL;
}
}
size_t GetSize() const
{
return buffer_.size;
}
bool IsEmpty() const
{
return GetSize() == 0 || GetData() == NULL;
}
void Clear();
void ToString(std::string& target) const;
void ToJson(Json::Value& target) const;
bool RestApiGet(const std::string& uri,
bool applyPlugins);
bool RestApiGet(const std::string& uri,
const HttpHeaders& httpHeaders,
bool applyPlugins);
bool RestApiPost(const std::string& uri,
const void* body,
size_t bodySize,
bool applyPlugins);
bool RestApiPut(const std::string& uri,
const void* body,
size_t bodySize,
bool applyPlugins);
bool RestApiPost(const std::string& uri,
const Json::Value& body,
bool applyPlugins);
#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
bool RestApiPost(const std::string& uri,
const Json::Value& body,
const HttpHeaders& httpHeaders,
bool applyPlugins);
bool RestApiPost(const std::string& uri,
const void* body,
size_t bodySize,
const HttpHeaders& httpHeaders,
bool applyPlugins);
#endif
bool RestApiPut(const std::string& uri,
const Json::Value& body,
bool applyPlugins);
bool RestApiPost(const std::string& uri,
const std::string& body,
bool applyPlugins)
{
return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
}
bool RestApiPut(const std::string& uri,
const std::string& body,
bool applyPlugins)
{
return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
}
void CreateDicom(const Json::Value& tags,
OrthancPluginCreateDicomFlags flags);
void CreateDicom(const Json::Value& tags,
const OrthancImage& pixelData,
OrthancPluginCreateDicomFlags flags);
void ReadFile(const std::string& path);
void GetDicomQuery(const OrthancPluginWorklistQuery* query);
void DicomToJson(Json::Value& target,
OrthancPluginDicomToJsonFormat format,
OrthancPluginDicomToJsonFlags flags,
uint32_t maxStringLength);
bool HttpGet(const std::string& url,
const std::string& username,
const std::string& password);
bool HttpPost(const std::string& url,
const std::string& body,
const std::string& username,
const std::string& password);
bool HttpPut(const std::string& url,
const std::string& body,
const std::string& username,
const std::string& password);
void GetDicomInstance(const std::string& instanceId);
};
class OrthancString : public boost::noncopyable
{
private:
char* str_;
void Clear();
public:
OrthancString() :
str_(NULL)
{
}
~OrthancString()
{
Clear();
}
// This transfers ownership, warning: The string must have been
// allocated by the Orthanc core
void Assign(char* str);
const char* GetContent() const
{
return str_;
}
bool IsNullOrEmpty() const
{
return str_ == NULL || str_[0] == 0;
}
void ToString(std::string& target) const;
void ToJson(Json::Value& target) const;
void ToJsonWithoutComments(Json::Value& target) const;
};
class OrthancConfiguration : public boost::noncopyable
{
private:
Json::Value configuration_; // Necessarily a Json::objectValue
std::string path_;
std::string GetPath(const std::string& key) const;
void LoadConfiguration();
public:
OrthancConfiguration(); // loads the full Orthanc configuration
explicit OrthancConfiguration(bool load);
explicit OrthancConfiguration(const Json::Value& configuration, const std::string& path); // e.g. to load a section from a default json content
const Json::Value& GetJson() const
{
return configuration_;
}
bool IsSection(const std::string& key) const;
void GetSection(OrthancConfiguration& target,
const std::string& key) const;
bool LookupStringValue(std::string& target,
const std::string& key) const;
bool LookupIntegerValue(int& target,
const std::string& key) const;
bool LookupUnsignedIntegerValue(unsigned int& target,
const std::string& key) const;
bool LookupBooleanValue(bool& target,
const std::string& key) const;
bool LookupFloatValue(float& target,
const std::string& key) const;
bool LookupListOfStrings(std::list<std::string>& target,
const std::string& key,
bool allowSingleString) const;
bool LookupSetOfStrings(std::set<std::string>& target,
const std::string& key,
bool allowSingleString) const;
std::string GetStringValue(const std::string& key,
const std::string& defaultValue) const;
int GetIntegerValue(const std::string& key,
int defaultValue) const;
unsigned int GetUnsignedIntegerValue(const std::string& key,
unsigned int defaultValue) const;
bool GetBooleanValue(const std::string& key,
bool defaultValue) const;
float GetFloatValue(const std::string& key,
float defaultValue) const;
void GetDictionary(std::map<std::string, std::string>& target,
const std::string& key) const;
};
class OrthancImage : public boost::noncopyable
{
private:
OrthancPluginImage* image_;
void Clear();
void CheckImageAvailable() const;
public:
OrthancImage();
explicit OrthancImage(OrthancPluginImage* image);
OrthancImage(OrthancPluginPixelFormat format,
uint32_t width,
uint32_t height);
OrthancImage(OrthancPluginPixelFormat format,
uint32_t width,
uint32_t height,
uint32_t pitch,
void* buffer);
~OrthancImage()
{
Clear();
}
void UncompressPngImage(const void* data,
size_t size);
void UncompressJpegImage(const void* data,
size_t size);
void DecodeDicomImage(const void* data,
size_t size,
unsigned int frame);
OrthancPluginPixelFormat GetPixelFormat() const;
unsigned int GetWidth() const;
unsigned int GetHeight() const;
unsigned int GetPitch() const;
void* GetBuffer() const;
const OrthancPluginImage* GetObject() const
{
return image_;
}
void CompressPngImage(MemoryBuffer& target) const;
void CompressJpegImage(MemoryBuffer& target,
uint8_t quality) const;
void AnswerPngImage(OrthancPluginRestOutput* output) const;
void AnswerJpegImage(OrthancPluginRestOutput* output,
uint8_t quality) const;
void* GetWriteableBuffer();
OrthancPluginImage* Release();
};
#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
class FindMatcher : public boost::noncopyable
{
private:
OrthancPluginFindMatcher* matcher_;
const OrthancPluginWorklistQuery* worklist_;
void SetupDicom(const void* query,
uint32_t size);
public:
explicit FindMatcher(const OrthancPluginWorklistQuery* worklist);
FindMatcher(const void* query,
uint32_t size)
{
SetupDicom(query, size);
}
explicit FindMatcher(const MemoryBuffer& dicom)
{
SetupDicom(dicom.GetData(), dicom.GetSize());
}
~FindMatcher();
bool IsMatch(const void* dicom,
uint32_t size) const;
bool IsMatch(const MemoryBuffer& dicom) const
{
return IsMatch(dicom.GetData(), dicom.GetSize());
}
};
#endif
bool ReadJson(Json::Value& target,
const std::string& source);
bool ReadJson(Json::Value& target,
const void* buffer,
size_t size);
bool ReadJsonWithoutComments(Json::Value& target,
const std::string& source);
bool ReadJsonWithoutComments(Json::Value& target,
const void* buffer,
size_t size);
void WriteFastJson(std::string& target,
const Json::Value& source);
void WriteStyledJson(std::string& target,
const Json::Value& source);
bool RestApiGet(Json::Value& result,
const std::string& uri,
bool applyPlugins);
bool RestApiGet(Json::Value& result,
const std::string& uri,
const HttpHeaders& httpHeaders,
bool applyPlugins);
bool RestApiGetString(std::string& result,
const std::string& uri,
bool applyPlugins);
bool RestApiGetString(std::string& result,
const std::string& uri,
const HttpHeaders& httpHeaders,
bool applyPlugins);
bool RestApiPost(std::string& result,
const std::string& uri,
const void* body,
size_t bodySize,
bool applyPlugins);
bool RestApiPost(Json::Value& result,
const std::string& uri,
const void* body,
size_t bodySize,
bool applyPlugins);
#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
bool RestApiPost(Json::Value& result,
const std::string& uri,
const Json::Value& body,
const HttpHeaders& httpHeaders,
bool applyPlugins);
#endif
bool RestApiPost(Json::Value& result,
const std::string& uri,
const Json::Value& body,
bool applyPlugins);
inline bool RestApiPost(Json::Value& result,
const std::string& uri,
const std::string& body,
bool applyPlugins)
{
return RestApiPost(result, uri, body.empty() ? NULL : body.c_str(),
body.size(), applyPlugins);
}
inline bool RestApiPost(Json::Value& result,
const std::string& uri,
const MemoryBuffer& body,
bool applyPlugins)
{
return RestApiPost(result, uri, body.GetData(),
body.GetSize(), applyPlugins);
}
bool RestApiPut(Json::Value& result,
const std::string& uri,
const void* body,
size_t bodySize,
bool applyPlugins);
bool RestApiPut(Json::Value& result,
const std::string& uri,
const Json::Value& body,
bool applyPlugins);
inline bool RestApiPut(Json::Value& result,
const std::string& uri,
const std::string& body,
bool applyPlugins)
{
return RestApiPut(result, uri, body.empty() ? NULL : body.c_str(),
body.size(), applyPlugins);
}
bool RestApiDelete(const std::string& uri,
bool applyPlugins);
bool HttpDelete(const std::string& url,
const std::string& username,
const std::string& password);
void AnswerJson(const Json::Value& value,
OrthancPluginRestOutput* output);
void AnswerString(const std::string& answer,
const char* mimeType,
OrthancPluginRestOutput* output);
void AnswerHttpError(uint16_t httpError,
OrthancPluginRestOutput* output);
void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods);
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0)
const char* AutodetectMimeType(const std::string& path);
#endif
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
void LogMessage(OrthancPluginLogLevel level,
const char* file,
uint32_t line,
const std::string& message);
#endif
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
// Use macro ORTHANC_PLUGINS_LOG_ERROR() instead
ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogError(const std::string& message));
#else
void LogError(const std::string& message);
#endif
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
// Use macro ORTHANC_PLUGINS_LOG_WARNING() instead
ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogWarning(const std::string& message));
#else
void LogWarning(const std::string& message);
#endif
#if HAS_ORTHANC_PLUGIN_LOG_MESSAGE == 1
// Use macro ORTHANC_PLUGINS_LOG_INFO() instead
ORTHANC_PLUGIN_CPP_WRAPPER_DEPRECATED(void LogInfo(const std::string& message));
#else
void LogInfo(const std::string& message);
#endif
void ReportMinimalOrthancVersion(unsigned int major,
unsigned int minor,
unsigned int revision);
bool CheckMinimalOrthancVersion(unsigned int major,
unsigned int minor,
unsigned int revision);
bool CheckMinimalVersion(const char* version,
unsigned int major,
unsigned int minor,
unsigned int revision);
namespace Internals
{
template <RestCallback Callback>
static OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request)
{
try
{
Callback(output, url, request);
return OrthancPluginErrorCode_Success;
}
catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
{
#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
if (HasGlobalContext() &&
e.HasDetails())
{
// The "false" instructs Orthanc not to log the detailed
// error message. This is to avoid duplicating the details,
// because "OrthancException" already does it on construction.
OrthancPluginSetHttpErrorDetails
(GetGlobalContext(), output, e.GetDetails(), false);
}
#endif
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
}
catch (boost::bad_lexical_cast&)
{
return OrthancPluginErrorCode_BadFileFormat;
}
catch (...)
{
return OrthancPluginErrorCode_Plugin;
}
}
}
template <RestCallback Callback>
void RegisterRestCallback(const std::string& uri,
bool isThreadSafe)
{
if (isThreadSafe)
{
OrthancPluginRegisterRestCallbackNoLock
(GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
}
else
{
OrthancPluginRegisterRestCallback
(GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>);
}
}
#if HAS_ORTHANC_PLUGIN_PEERS == 1
class OrthancPeers : public boost::noncopyable
{
private:
typedef std::map<std::string, uint32_t> Index;
OrthancPluginPeers *peers_;
Index index_;
uint32_t timeout_;
size_t GetPeerIndex(const std::string& name) const;
public:
OrthancPeers();
~OrthancPeers();
uint32_t GetTimeout() const
{
return timeout_;
}
void SetTimeout(uint32_t timeout)
{
timeout_ = timeout;
}
bool LookupName(size_t& target,
const std::string& name) const;
std::string GetPeerName(size_t index) const;
std::string GetPeerUrl(size_t index) const;
std::string GetPeerUrl(const std::string& name) const;
size_t GetPeersCount() const
{
return index_.size();
}
bool LookupUserProperty(std::string& value,
size_t index,
const std::string& key) const;
bool LookupUserProperty(std::string& value,
const std::string& peer,
const std::string& key) const;
bool DoGet(MemoryBuffer& target,
size_t index,
const std::string& uri,
const HttpHeaders& headers) const;
bool DoGet(MemoryBuffer& target,
const std::string& name,
const std::string& uri,
const HttpHeaders& headers) const;
bool DoGet(Json::Value& target,
size_t index,
const std::string& uri,
const HttpHeaders& headers) const;
bool DoGet(Json::Value& target,
const std::string& name,
const std::string& uri,
const HttpHeaders& headers) const;
bool DoPost(MemoryBuffer& target,
size_t index,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoPost(MemoryBuffer& target,
size_t index,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers,
unsigned int timeout) const;
bool DoPost(MemoryBuffer& target,
const std::string& name,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoPost(Json::Value& target,
size_t index,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoPost(Json::Value& target,
size_t index,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers,
unsigned int timeout) const;
bool DoPost(Json::Value& target,
const std::string& name,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoPut(size_t index,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoPut(const std::string& name,
const std::string& uri,
const std::string& body,
const HttpHeaders& headers) const;
bool DoDelete(size_t index,
const std::string& uri,
const HttpHeaders& headers) const;
bool DoDelete(const std::string& name,
const std::string& uri,
const HttpHeaders& headers) const;
};
#endif
#if HAS_ORTHANC_PLUGIN_JOB == 1
class OrthancJob : public boost::noncopyable
{
private:
std::string jobType_;
std::string content_;
bool hasSerialized_;
std::string serialized_;
float progress_;
static void CallbackFinalize(void* job);
static float CallbackGetProgress(void* job);
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
static OrthancPluginErrorCode CallbackGetContent(OrthancPluginMemoryBuffer* target,
void* job);
#else
static const char* CallbackGetContent(void* job);
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 11, 3)
static int32_t CallbackGetSerialized(OrthancPluginMemoryBuffer* target,
void* job);
#else
static const char* CallbackGetSerialized(void* job);
#endif
static OrthancPluginJobStepStatus CallbackStep(void* job);
static OrthancPluginErrorCode CallbackStop(void* job,
OrthancPluginJobStopReason reason);
static OrthancPluginErrorCode CallbackReset(void* job);
protected:
void ClearContent();
void UpdateContent(const Json::Value& content);
void ClearSerialized();
void UpdateSerialized(const Json::Value& serialized);
void UpdateProgress(float progress);
public:
explicit OrthancJob(const std::string& jobType);
virtual ~OrthancJob()
{
}
virtual OrthancPluginJobStepStatus Step() = 0;
virtual void Stop(OrthancPluginJobStopReason reason) = 0;
virtual void Reset() = 0;
static OrthancPluginJob* Create(OrthancJob* job /* takes ownership */);
static std::string Submit(OrthancJob* job /* takes ownership */,
int priority);
static void SubmitAndWait(Json::Value& result,
OrthancJob* job /* takes ownership */,
int priority);
// Submit a job from a POST on the REST API with the same
// conventions as in the Orthanc core (according to the
// "Synchronous" and "Priority" options)
static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
const Json::Value& body,
OrthancJob* job);
};
#endif
#if HAS_ORTHANC_PLUGIN_METRICS == 1
inline void SetMetricsValue(char* name,
float value)
{
OrthancPluginSetMetricsValue(GetGlobalContext(), name,
value, OrthancPluginMetricsType_Default);
}
class MetricsTimer : public boost::noncopyable
{
private:
std::string name_;
boost::posix_time::ptime start_;
public:
explicit MetricsTimer(const char* name);
~MetricsTimer();
};
#endif
#if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
class HttpClient : public boost::noncopyable
{
public:
class IRequestBody : public boost::noncopyable
{
public:
virtual ~IRequestBody()
{
}
virtual bool ReadNextChunk(std::string& chunk) = 0;
};
class IAnswer : public boost::noncopyable
{
public:
virtual ~IAnswer()
{
}
virtual void AddHeader(const std::string& key,
const std::string& value) = 0;
virtual void AddChunk(const void* data,
size_t size) = 0;
};
private:
class RequestBodyWrapper;
uint16_t httpStatus_;
OrthancPluginHttpMethod method_;
std::string url_;
HttpHeaders headers_;
std::string username_;
std::string password_;
uint32_t timeout_;
std::string certificateFile_;
std::string certificateKeyFile_;
std::string certificateKeyPassword_;
bool pkcs11_;
std::string fullBody_;
IRequestBody* chunkedBody_;
bool allowChunkedTransfers_;
#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_CLIENT == 1
void ExecuteWithStream(uint16_t& httpStatus, // out
IAnswer& answer, // out
IRequestBody& body) const;
#endif
void ExecuteWithoutStream(uint16_t& httpStatus, // out
HttpHeaders& answerHeaders, // out
std::string& answerBody, // out
const std::string& body) const;
public:
HttpClient();
uint16_t GetHttpStatus() const
{
return httpStatus_;
}
void SetMethod(OrthancPluginHttpMethod method)
{
method_ = method;
}
const std::string& GetUrl() const
{
return url_;
}
void SetUrl(const std::string& url)
{
url_ = url;
}
void SetHeaders(const HttpHeaders& headers)
{
headers_ = headers;
}
void AddHeader(const std::string& key,
const std::string& value)
{
headers_[key] = value;
}
void AddHeaders(const HttpHeaders& headers);
void SetCredentials(const std::string& username,
const std::string& password);
void ClearCredentials();
void SetTimeout(unsigned int timeout) // 0 for default timeout
{
timeout_ = timeout;
}
void SetCertificate(const std::string& certificateFile,
const std::string& keyFile,
const std::string& keyPassword);
void ClearCertificate();
void SetPkcs11(bool pkcs11)
{
pkcs11_ = pkcs11;
}
void ClearBody();
void SwapBody(std::string& body);
void SetBody(const std::string& body);
void SetBody(IRequestBody& body);
// This function can be used to disable chunked transfers if the
// remote server is Orthanc with a version <= 1.5.6.
void SetChunkedTransfersAllowed(bool allow)
{
allowChunkedTransfers_ = allow;
}
bool IsChunkedTransfersAllowed() const
{
return allowChunkedTransfers_;
}
void Execute(IAnswer& answer);
void Execute(HttpHeaders& answerHeaders /* out */,
std::string& answerBody /* out */);
void Execute(HttpHeaders& answerHeaders /* out */,
Json::Value& answerBody /* out */);
void Execute();
};
#endif
class IChunkedRequestReader : public boost::noncopyable
{
public:
virtual ~IChunkedRequestReader()
{
}
virtual void AddChunk(const void* data,
size_t size) = 0;
virtual void Execute(OrthancPluginRestOutput* output) = 0;
};
typedef IChunkedRequestReader* (*ChunkedRestCallback) (const char* url,
const OrthancPluginHttpRequest* request);
namespace Internals
{
void NullRestCallback(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request);
IChunkedRequestReader *NullChunkedRestCallback(const char* url,
const OrthancPluginHttpRequest* request);
#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
template <ChunkedRestCallback Callback>
static OrthancPluginErrorCode ChunkedProtect(OrthancPluginServerChunkedRequestReader** reader,
const char* url,
const OrthancPluginHttpRequest* request)
{
try
{
if (reader == NULL)
{
return OrthancPluginErrorCode_InternalError;
}
else
{
*reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(url, request));
if (*reader == NULL)
{
return OrthancPluginErrorCode_Plugin;
}
else
{
return OrthancPluginErrorCode_Success;
}
}
}
catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
{
return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
}
catch (boost::bad_lexical_cast&)
{
return OrthancPluginErrorCode_BadFileFormat;
}
catch (...)
{
return OrthancPluginErrorCode_Plugin;
}
}
OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
OrthancPluginServerChunkedRequestReader* reader,
const void* data,
uint32_t size);
OrthancPluginErrorCode ChunkedRequestReaderExecute(
OrthancPluginServerChunkedRequestReader* reader,
OrthancPluginRestOutput* output);
void ChunkedRequestReaderFinalize(
OrthancPluginServerChunkedRequestReader* reader);
#else
OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request,
RestCallback GetHandler,
ChunkedRestCallback PostHandler,
RestCallback DeleteHandler,
ChunkedRestCallback PutHandler);
template<
RestCallback GetHandler,
ChunkedRestCallback PostHandler,
RestCallback DeleteHandler,
ChunkedRestCallback PutHandler
>
inline OrthancPluginErrorCode ChunkedRestCompatibility(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request)
{
return ChunkedRestCompatibility(output, url, request, GetHandler,
PostHandler, DeleteHandler, PutHandler);
}
#endif
}
// NB: We use a templated class instead of a templated function, because
// default values are only available in functions since C++11
template<
RestCallback GetHandler = Internals::NullRestCallback,
ChunkedRestCallback PostHandler = Internals::NullChunkedRestCallback,
RestCallback DeleteHandler = Internals::NullRestCallback,
ChunkedRestCallback PutHandler = Internals::NullChunkedRestCallback
>
class ChunkedRestRegistration : public boost::noncopyable
{
public:
static void Apply(const std::string& uri)
{
#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
OrthancPluginRegisterChunkedRestCallback(
GetGlobalContext(), uri.c_str(),
GetHandler == Internals::NullRestCallback ? NULL : Internals::Protect<GetHandler>,
PostHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PostHandler>,
DeleteHandler == Internals::NullRestCallback ? NULL : Internals::Protect<DeleteHandler>,
PutHandler == Internals::NullChunkedRestCallback ? NULL : Internals::ChunkedProtect<PutHandler>,
Internals::ChunkedRequestReaderAddChunk,
Internals::ChunkedRequestReaderExecute,
Internals::ChunkedRequestReaderFinalize);
#else
OrthancPluginRegisterRestCallbackNoLock(
GetGlobalContext(), uri.c_str(),
Internals::ChunkedRestCompatibility<GetHandler, PostHandler, DeleteHandler, PutHandler>);
#endif
}
};
#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
class IStorageCommitmentScpHandler : public boost::noncopyable
{
public:
virtual ~IStorageCommitmentScpHandler()
{
}
virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
const std::string& sopInstanceUid) = 0;
static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
void* rawHandler,
const char* sopClassUid,
const char* sopInstanceUid);
static void Destructor(void* rawHandler);
};
#endif
class DicomInstance : public boost::noncopyable
{
private:
bool toFree_;
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
const OrthancPluginDicomInstance* instance_;
#else
OrthancPluginDicomInstance* instance_;
#endif
public:
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
explicit DicomInstance(const OrthancPluginDicomInstance* instance);
#else
explicit DicomInstance(OrthancPluginDicomInstance* instance);
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
DicomInstance(const void* buffer,
size_t size);
#endif
~DicomInstance();
const OrthancPluginDicomInstance* GetObject() const
{
return instance_;
}
std::string GetRemoteAet() const;
const void* GetBuffer() const
{
return OrthancPluginGetInstanceData(GetGlobalContext(), instance_);
}
size_t GetSize() const
{
return static_cast<size_t>(OrthancPluginGetInstanceSize(GetGlobalContext(), instance_));
}
void GetJson(Json::Value& target) const;
void GetSimplifiedJson(Json::Value& target) const;
OrthancPluginInstanceOrigin GetOrigin() const
{
return OrthancPluginGetInstanceOrigin(GetGlobalContext(), instance_);
}
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
std::string GetTransferSyntaxUid() const;
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 1)
bool HasPixelData() const;
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
unsigned int GetFramesCount() const
{
return OrthancPluginGetInstanceFramesCount(GetGlobalContext(), instance_);
}
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
void GetRawFrame(std::string& target,
unsigned int frameIndex) const;
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
OrthancImage* GetDecodedFrame(unsigned int frameIndex) const;
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
void Serialize(std::string& target) const;
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
static DicomInstance* Transcode(const void* buffer,
size_t size,
const std::string& transferSyntax);
#endif
#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 1)
static DicomInstance* Load(const std::string& instanceId,
OrthancPluginLoadDicomInstanceMode mode);
#endif
};
// helper method to convert Http headers from the plugin SDK to a std::map
void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request);
// helper method to re-serialize the get arguments from the SDK into a string
void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request);
#if HAS_ORTHANC_PLUGIN_WEBDAV == 1
class IWebDavCollection : public boost::noncopyable
{
public:
class FileInfo
{
private:
std::string name_;
uint64_t contentSize_;
std::string mime_;
std::string dateTime_;
public:
FileInfo(const std::string& name,
uint64_t contentSize,
const std::string& dateTime) :
name_(name),
contentSize_(contentSize),
dateTime_(dateTime)
{
}
const std::string& GetName() const
{
return name_;
}
uint64_t GetContentSize() const
{
return contentSize_;
}
void SetMimeType(const std::string& mime)
{
mime_ = mime;
}
const std::string& GetMimeType() const
{
return mime_;
}
const std::string& GetDateTime() const
{
return dateTime_;
}
};
class FolderInfo
{
private:
std::string name_;
std::string dateTime_;
public:
FolderInfo(const std::string& name,
const std::string& dateTime) :
name_(name),
dateTime_(dateTime)
{
}
const std::string& GetName() const
{
return name_;
}
const std::string& GetDateTime() const
{
return dateTime_;
}
};
virtual ~IWebDavCollection()
{
}
virtual bool IsExistingFolder(const std::vector<std::string>& path) = 0;
virtual bool ListFolder(std::list<FileInfo>& files,
std::list<FolderInfo>& subfolders,
const std::vector<std::string>& path) = 0;
virtual bool GetFile(std::string& content /* out */,
std::string& mime /* out */,
std::string& dateTime /* out */,
const std::vector<std::string>& path) = 0;
virtual bool StoreFile(const std::vector<std::string>& path,
const void* data,
size_t size) = 0;
virtual bool CreateFolder(const std::vector<std::string>& path) = 0;
virtual bool DeleteItem(const std::vector<std::string>& path) = 0;
static void Register(const std::string& uri,
IWebDavCollection& collection);
};
#endif
void SetRootUri(const std::string& pluginIdentifier,
const std::string& uri);
void SetDescription(const std::string& pluginIdentifier,
const std::string& description);
void ExtendOrthancExplorer(const std::string& pluginIdentifier,
const std::string& javascript);
#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
class RestApiClient : public boost::noncopyable
{
private:
// Request
OrthancPluginHttpMethod method_;
std::string path_;
HttpHeaders requestHeaders_;
std::string requestBody_;
bool afterPlugins_;
// Answer
uint16_t httpStatus_;
HttpHeaders answerHeaders_;
std::string answerBody_;
public:
RestApiClient();
// used to forward a call from the plugin to the core
RestApiClient(const char* url,
const OrthancPluginHttpRequest* request);
void SetMethod(OrthancPluginHttpMethod method)
{
method_ = method;
}
OrthancPluginHttpMethod GetMethod() const
{
return method_;
}
void SetPath(const std::string& path)
{
path_ = path;
}
const std::string& GetPath() const
{
return path_;
}
void AddRequestHeader(const std::string& key,
const std::string& value);
const HttpHeaders& GetRequestHeaders() const
{
return requestHeaders_;
}
void SetRequestBody(const std::string& body)
{
requestBody_ = body;
}
void SwapRequestBody(std::string& body)
{
requestBody_.swap(body);
}
void SetAfterPlugins(bool afterPlugins)
{
afterPlugins_ = afterPlugins;
}
bool IsAfterPlugins() const
{
return afterPlugins_;
}
const std::string& GetRequestBody() const
{
return requestBody_;
}
bool Execute();
// Execute and forward the response as is
void Forward(OrthancPluginContext* context, OrthancPluginRestOutput* output);
uint16_t GetHttpStatus() const;
bool LookupAnswerHeader(std::string& value,
const std::string& key) const;
const std::string& GetAnswerBody() const;
bool GetAnswerJson(Json::Value& output) const;
};
#endif
#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
class KeyValueStore : public boost::noncopyable
{
public:
class Iterator : public boost::noncopyable
{
private:
OrthancPluginKeysValuesIterator *iterator_;
public:
Iterator(OrthancPluginKeysValuesIterator *iterator);
~Iterator();
bool Next();
std::string GetKey() const;
void GetValue(std::string& target) const;
};
private:
std::string storeId_;
public:
explicit KeyValueStore(const std::string& storeId) :
storeId_(storeId)
{
}
const std::string& GetStoreId() const
{
return storeId_;
}
void Store(const std::string& key,
const void* value,
size_t valueSize);
void Store(const std::string& key,
const std::string& value)
{
Store(key, value.empty() ? NULL : value.c_str(), value.size());
}
bool GetValue(std::string& value,
const std::string& key);
void DeleteKey(const std::string& key);
Iterator* CreateIterator();
};
#endif
#if HAS_ORTHANC_PLUGIN_QUEUES == 1
class Queue : public boost::noncopyable
{
private:
std::string queueId_;
bool DequeueInternal(std::string& value, OrthancPluginQueueOrigin origin);
public:
explicit Queue(const std::string& queueId) :
queueId_(queueId)
{
}
const std::string& GetQueueId() const
{
return queueId_;
}
void Enqueue(const void* value,
size_t valueSize);
void Enqueue(const std::string& value)
{
Enqueue(value.empty() ? NULL : value.c_str(), value.size());
}
bool DequeueBack(std::string& value)
{
return DequeueInternal(value, OrthancPluginQueueOrigin_Back);
}
bool DequeueFront(std::string& value)
{
return DequeueInternal(value, OrthancPluginQueueOrigin_Front);
}
uint64_t GetSize();
};
#endif
}