# HG changeset patch # User Sebastien Jodogne # Date 1581447593 -3600 # Node ID d8371b4302ffd0e0e5e68ece6bf09a302acd10fc # Parent 25117919a36bcb736649675c2092c674751caf8a OrthancPluginRegisterStorageCommitmentScpCallback() diff -r 25117919a36b -r d8371b4302ff NEWS --- a/NEWS Mon Feb 10 17:54:40 2020 +0100 +++ b/NEWS Tue Feb 11 19:59:53 2020 +0100 @@ -19,6 +19,7 @@ ------- * New sample plugin: "ConnectivityChecks" +* New primitives to handle storage commitment SCP by plugins Maintenance ----------- diff -r 25117919a36b -r d8371b4302ff OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Feb 10 17:54:40 2020 +0100 +++ b/OrthancServer/ServerContext.cpp Tue Feb 11 19:59:53 2020 +0100 @@ -1058,13 +1058,15 @@ ServerContext::CreateStorageCommitment(const std::string& jobId, const std::string& transactionUid, const std::vector& sopClassUids, - const std::vector& sopInstanceUids) + const std::vector& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) { #if ORTHANC_ENABLE_PLUGINS == 1 if (HasPlugins()) { - // TODO - return NULL; + return GetPlugins().CreateStorageCommitment( + jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); } #endif diff -r 25117919a36b -r d8371b4302ff OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Mon Feb 10 17:54:40 2020 +0100 +++ b/OrthancServer/ServerContext.h Tue Feb 11 19:59:53 2020 +0100 @@ -432,6 +432,8 @@ CreateStorageCommitment(const std::string& jobId, const std::string& transactionUid, const std::vector& sopClassUids, - const std::vector& sopInstanceUids) ORTHANC_OVERRIDE; + const std::vector& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; }; } diff -r 25117919a36b -r d8371b4302ff OrthancServer/ServerJobs/IStorageCommitmentFactory.h --- a/OrthancServer/ServerJobs/IStorageCommitmentFactory.h Mon Feb 10 17:54:40 2020 +0100 +++ b/OrthancServer/ServerJobs/IStorageCommitmentFactory.h Tue Feb 11 19:59:53 2020 +0100 @@ -59,6 +59,8 @@ virtual ILookupHandler* CreateStorageCommitment(const std::string& jobId, const std::string& transactionUid, const std::vector& sopClassUids, - const std::vector& sopInstanceUids) = 0; + const std::vector& sopInstanceUids, + const std::string& remoteAet, + const std::string& calledAet) = 0; }; } diff -r 25117919a36b -r d8371b4302ff OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp --- a/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Mon Feb 10 17:54:40 2020 +0100 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Tue Feb 11 19:59:53 2020 +0100 @@ -265,7 +265,10 @@ void StorageCommitmentScpJob::Setup(const std::string& jobId) { CheckInvariants(); - lookupHandler_.reset(context_.CreateStorageCommitment(jobId, transactionUid_, sopClassUids_, sopInstanceUids_)); + + const std::string& remoteAet = remoteModality_.GetApplicationEntityTitle(); + lookupHandler_.reset(context_.CreateStorageCommitment(jobId, transactionUid_, sopClassUids_, + sopInstanceUids_, remoteAet, calledAet_)); } diff -r 25117919a36b -r d8371b4302ff Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Mon Feb 10 17:54:40 2020 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Tue Feb 11 19:59:53 2020 +0100 @@ -682,6 +682,102 @@ }; + + class StorageCommitmentScp : public IStorageCommitmentFactory + { + private: + class Handler : public IStorageCommitmentFactory::ILookupHandler + { + private: + _OrthancPluginRegisterStorageCommitmentScpCallback parameters_; + void* handler_; + + public: + Handler(_OrthancPluginRegisterStorageCommitmentScpCallback parameters, + void* handler) : + parameters_(parameters) + { + 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) + { + 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: + StorageCommitmentScp(_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 = parameters_.factory(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 (handler == NULL) + { + return NULL; + } + else + { + return new Handler(parameters_, handler); + } + } + }; + + class ServerContextLock { private: @@ -724,6 +820,7 @@ typedef std::list DecodeImageCallbacks; typedef std::list JobsUnserializers; typedef std::list RefreshMetricsCallbacks; + typedef std::list StorageCommitmentScpCallbacks; typedef std::map Properties; PluginsManager manager_; @@ -740,6 +837,7 @@ IncomingHttpRequestFilters incomingHttpRequestFilters_; IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; RefreshMetricsCallbacks refreshMetricsCallbacks_; + StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_; std::auto_ptr storageArea_; boost::recursive_mutex restCallbackMutex_; @@ -750,6 +848,7 @@ boost::mutex decodeImageCallbackMutex_; boost::mutex jobsUnserializersMutex_; boost::mutex refreshMetricsMutex_; + boost::mutex storageCommitmentScpMutex_; boost::recursive_mutex invokeServiceMutex_; Properties properties_; @@ -1260,6 +1359,7 @@ sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) || static_cast(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast(DicomToJsonFlags_IncludeBinary) || static_cast(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast(DicomToJsonFlags_IncludePrivateTags) || static_cast(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast(DicomToJsonFlags_IncludeUnknownTags) || @@ -1303,6 +1403,13 @@ { delete *it; } + + for (PImpl::StorageCommitmentScpCallbacks::iterator + it = pimpl_->storageCommitmentScpCallbacks_.begin(); + it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it) + { + delete *it; + } } @@ -1863,6 +1970,18 @@ } + void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters) + { + const _OrthancPluginRegisterStorageCommitmentScpCallback& p = + *reinterpret_cast(parameters); + + boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); + LOG(INFO) << "Plugin has registered a storage commitment callback"; + + pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p)); + } + + void OrthancPlugins::AnswerBuffer(const void* parameters) { const _OrthancPluginAnswerBuffer& p = @@ -3882,6 +4001,10 @@ RegisterRefreshMetricsCallback(parameters); return true; + case _OrthancPluginService_RegisterStorageCommitmentScpCallback: + RegisterStorageCommitmentScpCallback(parameters); + return true; + case _OrthancPluginService_RegisterStorageArea: { LOG(INFO) << "Plugin has registered a custom storage area"; @@ -4539,4 +4662,32 @@ } } } + + + IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::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) + { + boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); + + for (PImpl::StorageCommitmentScpCallbacks::iterator + it = pimpl_->storageCommitmentScpCallbacks_.begin(); + it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it) + { + assert(*it != NULL); + IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment + (jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); + + if (handler != NULL) + { + return handler; + } + } + + return NULL; + } } diff -r 25117919a36b -r d8371b4302ff Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Mon Feb 10 17:54:40 2020 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Tue Feb 11 19:59:53 2020 +0100 @@ -62,6 +62,7 @@ #include "../../Core/JobsEngine/IJob.h" #include "../../OrthancServer/IDicomImageDecoder.h" #include "../../OrthancServer/IServerListener.h" +#include "../../OrthancServer/ServerJobs/IStorageCommitmentFactory.h" #include "OrthancPluginDatabase.h" #include "PluginsManager.h" @@ -80,7 +81,8 @@ public IDicomImageDecoder, public IIncomingHttpRequestFilter, public IFindRequestHandlerFactory, - public IMoveRequestHandlerFactory + public IMoveRequestHandlerFactory, + public IStorageCommitmentFactory { private: class PImpl; @@ -124,6 +126,8 @@ void RegisterRefreshMetricsCallback(const void* parameters); + void RegisterStorageCommitmentScpCallback(const void* parameters); + void AnswerBuffer(const void* parameters); void Redirect(const void* parameters); @@ -341,6 +345,15 @@ HttpMethod method, const UriComponents& uri, const Arguments& headers); + + // New in Orthanc 1.6.0 + IStorageCommitmentFactory::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; }; } diff -r 25117919a36b -r d8371b4302ff Plugins/Engine/PluginsEnumerations.cpp --- a/Plugins/Engine/PluginsEnumerations.cpp Mon Feb 10 17:54:40 2020 +0100 +++ b/Plugins/Engine/PluginsEnumerations.cpp Tue Feb 11 19:59:53 2020 +0100 @@ -549,5 +549,36 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason) + { + switch (reason) + { + case OrthancPluginStorageCommitmentFailureReason_Success: + return StorageCommitmentFailureReason_Success; + + case OrthancPluginStorageCommitmentFailureReason_ProcessingFailure: + return StorageCommitmentFailureReason_ProcessingFailure; + + case OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance: + return StorageCommitmentFailureReason_NoSuchObjectInstance; + + case OrthancPluginStorageCommitmentFailureReason_ResourceLimitation: + return StorageCommitmentFailureReason_ResourceLimitation; + + case OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported: + return StorageCommitmentFailureReason_ReferencedSOPClassNotSupported; + + case OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict: + return StorageCommitmentFailureReason_ClassInstanceConflict; + + case OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID: + return StorageCommitmentFailureReason_DuplicateTransactionUID; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } } diff -r 25117919a36b -r d8371b4302ff Plugins/Engine/PluginsEnumerations.h --- a/Plugins/Engine/PluginsEnumerations.h Mon Feb 10 17:54:40 2020 +0100 +++ b/Plugins/Engine/PluginsEnumerations.h Tue Feb 11 19:59:53 2020 +0100 @@ -79,6 +79,8 @@ OrthancPluginJobStepStatus Convert(JobStepCode step); JobStepCode Convert(OrthancPluginJobStepStatus step); + + StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason); } } diff -r 25117919a36b -r d8371b4302ff Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Mon Feb 10 17:54:40 2020 +0100 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Tue Feb 11 19:59:53 2020 +0100 @@ -26,6 +26,7 @@ * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). * -# void OrthancPluginFinalize(): * This function is invoked by Orthanc during its shutdown. The plugin * must free all its memory. @@ -451,6 +452,7 @@ _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, _OrthancPluginService_RegisterChunkedRestCallback = 1012, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013, /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -910,14 +912,14 @@ **/ typedef enum { - OrthancPluginMetricsType_Default, /*!< Default metrics */ + OrthancPluginMetricsType_Default = 0, /*!< Default metrics */ /** * This metrics represents a time duration. Orthanc will keep the * maximum value of the metrics over a sliding window of ten * seconds, which is useful if the metrics is sampled frequently. **/ - OrthancPluginMetricsType_Timer + OrthancPluginMetricsType_Timer = 1 } OrthancPluginMetricsType; @@ -927,11 +929,47 @@ **/ typedef enum { - OrthancPluginDicomWebBinaryMode_Ignore, /*!< Don't include binary tags */ - OrthancPluginDicomWebBinaryMode_InlineBinary, /*!< Inline encoding using Base64 */ - OrthancPluginDicomWebBinaryMode_BulkDataUri /*!< Use a bulk data URI field */ + OrthancPluginDicomWebBinaryMode_Ignore = 0, /*!< Don't include binary tags */ + OrthancPluginDicomWebBinaryMode_InlineBinary = 1, /*!< Inline encoding using Base64 */ + OrthancPluginDicomWebBinaryMode_BulkDataUri = 2 /*!< Use a bulk data URI field */ } OrthancPluginDicomWebBinaryMode; + + /** + * The available values for the Failure Reason (0008,1197) during + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + **/ + typedef enum + { + OrthancPluginStorageCommitmentFailureReason_Success = 0, + + /* 0110H: A general failure in processing the operation was + * encountered */ + OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1, + + /* 0112H: One or more of the elements in the Referenced SOP + Instance Sequence was not available */ + OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2, + + /* 0213H: The SCP does not currently have enough resources to + store the requested SOP Instance(s) */ + OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3, + + /* 0122H: Storage Commitment has been requested for a SOP Instance + with a SOP Class that is not supported by the SCP */ + OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4, + + /* 0119H: The SOP Class of an element in the Referenced SOP + Instance Sequence did not correspond to the SOP class + registered for this SOP Instance at the SCP */ + OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5, + + /* 0131H: The Transaction UID of the Storage Commitment Request is + already in use */ + OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6 + } OrthancPluginStorageCommitmentFailureReason; + /** @@ -1659,7 +1697,8 @@ sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) || sizeof(int32_t) != sizeof(OrthancPluginConstraintType) || sizeof(int32_t) != sizeof(OrthancPluginMetricsType) || - sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode)) + sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) || + sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason)) { /* Mismatch in the size of the enumerations */ return 0; @@ -7260,6 +7299,44 @@ } + + typedef void* (*OrthancPluginStorageCommitmentFactory) ( + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstancesUids, + uint32_t countInstances, + const char* remoteAet, + const char* calledAet); + + typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler); + + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) ( + OrthancPluginStorageCommitmentFailureReason* target, + void* handler, + const char* sopClassUid, + const char* sopInstanceUid); + + + typedef struct + { + OrthancPluginStorageCommitmentFactory factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback; + + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageCommitmentScpCallback( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, ¶ms); + } #ifdef __cplusplus }