# HG changeset patch # User Sebastien Jodogne # Date 1581342816 -3600 # Node ID 115f82775c460e2386980578fbf6ad3aa5c9dd58 # Parent cccd97333e3d15a030d1fefb6f8e0cb17dacf9df handling of storage commitment failure reasons diff -r cccd97333e3d -r 115f82775c46 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Mon Feb 10 10:37:10 2020 +0100 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Mon Feb 10 14:53:36 2020 +0100 @@ -1387,12 +1387,15 @@ static void FillSopSequence(DcmDataset& dataset, const DcmTagKey& tag, - const std::list& sopClassUids, - const std::list& sopInstanceUids, - bool hasFailureReason, - Uint16 failureReason) + const std::vector& sopClassUids, + const std::vector& sopInstanceUids, + const std::vector& failureReasons, + bool hasFailureReasons) { - assert(sopClassUids.size() == sopInstanceUids.size()); + assert(sopClassUids.size() == sopInstanceUids.size() && + (hasFailureReasons ? + failureReasons.size() == sopClassUids.size() : + failureReasons.empty())); if (sopInstanceUids.empty()) { @@ -1404,27 +1407,17 @@ } else { - std::list::const_iterator currentClass = sopClassUids.begin(); - std::list::const_iterator currentInstance = sopInstanceUids.begin(); - - while (currentClass != sopClassUids.end()) + for (size_t i = 0; i < sopClassUids.size(); i++) { std::auto_ptr item(new DcmItem); - if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, currentClass->c_str()).good() || - !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, currentInstance->c_str()).good() || - (hasFailureReason && - !item->putAndInsertUint16(DCM_FailureReason, failureReason).good()) || + if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || + !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || + (hasFailureReasons && + !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || !dataset.insertSequenceItem(tag, item.release()).good()) { throw OrthancException(ErrorCode_InternalError); } - - ++currentClass; - ++currentInstance; - } - - for (size_t i = 0; i < sopClassUids.size(); i++) - { } } } @@ -1434,13 +1427,12 @@ void DicomUserConnection::ReportStorageCommitment( const std::string& transactionUid, - const std::list& successSopClassUids, - const std::list& successSopInstanceUids, - const std::list& failureSopClassUids, - const std::list& failureSopInstanceUids) + const std::vector& sopClassUids, + const std::vector& sopInstanceUids, + const std::vector& failureReasons) { - if (successSopClassUids.size() != successSopInstanceUids.size() || - failureSopClassUids.size() != failureSopInstanceUids.size()) + if (sopClassUids.size() != sopInstanceUids.size() || + sopClassUids.size() != failureReasons.size()) { throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -1450,6 +1442,45 @@ Close(); } + std::vector successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; + std::vector failedReasons; + + successSopClassUids.reserve(sopClassUids.size()); + successSopInstanceUids.reserve(sopClassUids.size()); + failedSopClassUids.reserve(sopClassUids.size()); + failedSopInstanceUids.reserve(sopClassUids.size()); + failedReasons.reserve(sopClassUids.size()); + + for (size_t i = 0; i < sopClassUids.size(); i++) + { + switch (failureReasons[i]) + { + case StorageCommitmentFailureReason_Success: + successSopClassUids.push_back(sopClassUids[i]); + successSopInstanceUids.push_back(sopInstanceUids[i]); + break; + + case StorageCommitmentFailureReason_ProcessingFailure: + case StorageCommitmentFailureReason_NoSuchObjectInstance: + case StorageCommitmentFailureReason_ResourceLimitation: + case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: + case StorageCommitmentFailureReason_ClassInstanceConflict: + case StorageCommitmentFailureReason_DuplicateTransactionUID: + failedSopClassUids.push_back(sopClassUids[i]); + failedSopInstanceUids.push_back(sopInstanceUids[i]); + failedReasons.push_back(failureReasons[i]); + break; + + default: + { + char buf[16]; + sprintf(buf, "%04xH", failureReasons[i]); + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unsupported failure reason for storage commitment: " + std::string(buf)); + } + } + } + try { OpenInternal(Mode_ReportStorageCommitment); @@ -1470,7 +1501,7 @@ LOG(INFO) << "Reporting modality \"" << remoteAet_ << "\" about storage commitment transaction: " << transactionUid << " (" << successSopClassUids.size() << " successes, " - << failureSopClassUids.size() << " failures)"; + << failedSopClassUids.size() << " failures)"; const DIC_US messageId = pimpl_->assoc_->nextMsgID++; { @@ -1490,11 +1521,14 @@ throw OrthancException(ErrorCode_InternalError); } - FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, - successSopInstanceUids, false, 0); + { + std::vector empty; + FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, + successSopInstanceUids, empty, false); + } // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - if (failureSopClassUids.empty()) + if (failedSopClassUids.empty()) { content.EventTypeID = 1; // "Storage Commitment Request Successful" } @@ -1504,8 +1538,8 @@ // Failure reason // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 - FillSopSequence(dataset, DCM_FailedSOPSequence, failureSopClassUids, - failureSopInstanceUids, true, 0x0112 /* No such object instance == 274 */); + FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, + failedSopInstanceUids, failedReasons, true); } int presID = ASC_findAcceptedPresentationContextID( @@ -1574,8 +1608,8 @@ void DicomUserConnection::RequestStorageCommitment( const std::string& transactionUid, - const std::list& sopClassUids, - const std::list& sopInstanceUids) + const std::vector& sopClassUids, + const std::vector& sopInstanceUids) { if (sopClassUids.size() != sopInstanceUids.size()) { @@ -1633,8 +1667,11 @@ throw OrthancException(ErrorCode_InternalError); } - FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, false, 0); - + { + std::vector empty; + FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); + } + int presID = ASC_findAcceptedPresentationContextID( pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); if (presID == 0) diff -r cccd97333e3d -r 115f82775c46 Core/DicomNetworking/DicomUserConnection.h --- a/Core/DicomNetworking/DicomUserConnection.h Mon Feb 10 10:37:10 2020 +0100 +++ b/Core/DicomNetworking/DicomUserConnection.h Mon Feb 10 14:53:36 2020 +0100 @@ -228,15 +228,14 @@ void ReportStorageCommitment( const std::string& transactionUid, - const std::list& successSopClassUids, - const std::list& successSopInstanceUids, - const std::list& failureSopClassUids, - const std::list& failureSopInstanceUids); + const std::vector& sopClassUids, + const std::vector& sopInstanceUids, + const std::vector& failureReasons); // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier() void RequestStorageCommitment( const std::string& transactionUid, - const std::list& sopClassUids, - const std::list& sopInstanceUids); + const std::vector& sopClassUids, + const std::vector& sopInstanceUids); }; } diff -r cccd97333e3d -r 115f82775c46 Core/Enumerations.h --- a/Core/Enumerations.h Mon Feb 10 10:37:10 2020 +0100 +++ b/Core/Enumerations.h Mon Feb 10 14:53:36 2020 +0100 @@ -667,6 +667,36 @@ JobStopReason_Retry }; + + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + enum StorageCommitmentFailureReason + { + StorageCommitmentFailureReason_Success = 0, + + // A general failure in processing the operation was encountered + StorageCommitmentFailureReason_ProcessingFailure = 0x0110, + + // One or more of the elements in the Referenced SOP Instance + // Sequence was not available + StorageCommitmentFailureReason_NoSuchObjectInstance = 0x0112, + + // The SCP does not currently have enough resources to store the + // requested SOP Instance(s) + StorageCommitmentFailureReason_ResourceLimitation = 0x0213, + + // Storage Commitment has been requested for a SOP Instance with a + // SOP Class that is not supported by the SCP + StorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 0x0122, + + // 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 + StorageCommitmentFailureReason_ClassInstanceConflict = 0x0119, + + // The Transaction UID of the Storage Commitment Request is already in use + StorageCommitmentFailureReason_DuplicateTransactionUID = 0x0131 + }; + /** * WARNING: Do not change the explicit values in the enumerations diff -r cccd97333e3d -r 115f82775c46 NEWS --- a/NEWS Mon Feb 10 10:37:10 2020 +0100 +++ b/NEWS Mon Feb 10 14:53:36 2020 +0100 @@ -1,6 +1,11 @@ Pending changes in the mainline =============================== +General +------- + +* Support of DICOM storage commitment + REST API -------- diff -r cccd97333e3d -r 115f82775c46 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Feb 10 10:37:10 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Feb 10 14:53:36 2020 +0100 @@ -1312,7 +1312,7 @@ { DicomUserConnection scu(localAet, remote); - std::list sopClassUids, sopInstanceUids; + std::vector sopClassUids, sopInstanceUids; sopClassUids.push_back("a"); sopInstanceUids.push_back("b"); sopClassUids.push_back("1.2.840.10008.5.1.4.1.1.6.1"); diff -r cccd97333e3d -r 115f82775c46 OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp --- a/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Mon Feb 10 10:37:10 2020 +0100 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp Mon Feb 10 14:53:36 2020 +0100 @@ -44,14 +44,10 @@ static const char* ANSWER = "Answer"; static const char* CALLED_AET = "CalledAet"; -static const char* FAILED_SOP_CLASS_UIDS = "FailedSopClassUids"; -static const char* FAILED_SOP_INSTANCE_UIDS = "FailedSopInstanceUids"; static const char* LOOKUP = "Lookup"; static const char* REMOTE_MODALITY = "RemoteModality"; static const char* SOP_CLASS_UID = "SopClassUid"; static const char* SOP_INSTANCE_UID = "SopInstanceUid"; -static const char* SUCCESS_SOP_CLASS_UIDS = "SuccessSopClassUids"; -static const char* SUCCESS_SOP_INSTANCE_UIDS = "SuccessSopInstanceUids"; static const char* TRANSACTION_UID = "TransactionUid"; static const char* TYPE = "Type"; @@ -59,29 +55,106 @@ namespace Orthanc { - class StorageCommitmentScpJob::LookupCommand : public SetOfCommandsJob::ICommand + class StorageCommitmentScpJob::StorageCommitmentCommand : public SetOfCommandsJob::ICommand + { + public: + virtual bool IsAnswer() const = 0; + }; + + + class StorageCommitmentScpJob::LookupCommand : public StorageCommitmentCommand { private: - StorageCommitmentScpJob& that_; - std::string sopClassUid_; - std::string sopInstanceUid_; + ServerContext& context_; + bool hasFailureReason_; + std::string sopClassUid_; + std::string sopInstanceUid_; + StorageCommitmentFailureReason failureReason_; public: - LookupCommand(StorageCommitmentScpJob& that, + LookupCommand(ServerContext& context, const std::string& sopClassUid, const std::string& sopInstanceUid) : - that_(that), + context_(context), + hasFailureReason_(false), sopClassUid_(sopClassUid), sopInstanceUid_(sopInstanceUid) { } + virtual bool IsAnswer() const + { + return false; + } + virtual bool Execute() { - that_.LookupInstance(sopClassUid_, sopInstanceUid_); + if (hasFailureReason_) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + bool success = false; + + try + { + std::vector orthancId; + context_.GetIndex().LookupIdentifierExact(orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid_); + + if (orthancId.size() == 1) + { + std::string a, b; + + // Make sure that the DICOM file can be re-read by DCMTK + // from the file storage, and that the actual SOP + // class/instance UIDs do match + ServerContext::DicomCacheLocker locker(context_, orthancId[0]); + if (locker.GetDicom().GetTagValue(a, DICOM_TAG_SOP_CLASS_UID) && + locker.GetDicom().GetTagValue(b, DICOM_TAG_SOP_INSTANCE_UID) && + a == sopClassUid_ && + b == sopInstanceUid_) + { + success = true; + } + } + } + catch (OrthancException&) + { + } + + LOG(INFO) << " Storage commitment SCP job: " << (success ? "Success" : "Failure") + << " while looking for " << sopClassUid_ << " / " << sopInstanceUid_; + + failureReason_ = (success ? + StorageCommitmentFailureReason_Success : + StorageCommitmentFailureReason_NoSuchObjectInstance /* 0x0112 == 274 */); + hasFailureReason_ = true; + return true; } + const std::string& GetSopClassUid() const + { + return sopClassUid_; + } + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + StorageCommitmentFailureReason GetFailureReason() const + { + if (hasFailureReason_) + { + return failureReason_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + virtual void Serialize(Json::Value& target) const { target = Json::objectValue; @@ -92,7 +165,7 @@ }; - class StorageCommitmentScpJob::AnswerCommand : public SetOfCommandsJob::ICommand + class StorageCommitmentScpJob::AnswerCommand : public StorageCommitmentCommand { private: StorageCommitmentScpJob& that_; @@ -111,6 +184,11 @@ } } + virtual bool IsAnswer() const + { + return true; + } + virtual bool Execute() { that_.Answer(); @@ -128,11 +206,14 @@ class StorageCommitmentScpJob::Unserializer : public SetOfCommandsJob::ICommandUnserializer { private: - StorageCommitmentScpJob& that_; + StorageCommitmentScpJob& that_; + ServerContext& context_; public: - Unserializer(StorageCommitmentScpJob& that) : - that_(that) + Unserializer(StorageCommitmentScpJob& that, + ServerContext& context) : + that_(that), + context_(context) { that_.ready_ = false; } @@ -143,7 +224,7 @@ if (type == LOOKUP) { - return new LookupCommand(that_, + return new LookupCommand(context_, SerializationToolbox::ReadString(source, SOP_CLASS_UID), SerializationToolbox::ReadString(source, SOP_INSTANCE_UID)); } @@ -159,60 +240,52 @@ }; - void StorageCommitmentScpJob::LookupInstance(const std::string& sopClassUid, - const std::string& sopInstanceUid) - { - bool success = false; - - try + void StorageCommitmentScpJob::Answer() + { + LOG(INFO) << " Storage commitment SCP job: Sending answer"; + + const size_t n = GetCommandsCount(); + + if (n == 0) { - std::vector orthancId; - context_.GetIndex().LookupIdentifierExact(orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid); + throw OrthancException(ErrorCode_InternalError); + } + + std::vector sopClassUids, sopInstanceUids; + std::vector failureReasons; - if (orthancId.size() == 1) + sopClassUids.reserve(n); + sopInstanceUids.reserve(n); + failureReasons.reserve(n); + + for (size_t i = 0; i < n; i++) + { + const StorageCommitmentCommand& command = dynamic_cast(GetCommand(i)); + + if (i == n - 1) { - std::string a, b; + if (!command.IsAnswer()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + else + { + if (command.IsAnswer()) + { + throw OrthancException(ErrorCode_InternalError); + } - // Make sure that the DICOM file can be re-read by DCMTK - // from the file storage, and that the actual SOP - // class/instance UIDs do match - ServerContext::DicomCacheLocker locker(context_, orthancId[0]); - if (locker.GetDicom().GetTagValue(a, DICOM_TAG_SOP_CLASS_UID) && - locker.GetDicom().GetTagValue(b, DICOM_TAG_SOP_INSTANCE_UID) && - a == sopClassUid && - b == sopInstanceUid) - { - success = true; - } + const LookupCommand& lookup = dynamic_cast(command); + + sopClassUids.push_back(lookup.GetSopClassUid()); + sopInstanceUids.push_back(lookup.GetSopInstanceUid()); + failureReasons.push_back(lookup.GetFailureReason()); } } - catch (OrthancException&) - { - } - - LOG(INFO) << " Storage commitment SCP job: " << (success ? "Success" : "Failure") - << " while looking for " << sopClassUid << " / " << sopInstanceUid; - - if (success) - { - successSopClassUids_.push_back(sopClassUid); - successSopInstanceUids_.push_back(sopInstanceUid); - } - else - { - failedSopClassUids_.push_back(sopClassUid); - failedSopInstanceUids_.push_back(sopInstanceUid); - } - } - - - void StorageCommitmentScpJob::Answer() - { - LOG(INFO) << " Storage commitment SCP job: Sending answer"; DicomUserConnection scu(calledAet_, remoteModality_); - scu.ReportStorageCommitment(transactionUid_, successSopClassUids_, successSopInstanceUids_, - failedSopClassUids_, failedSopInstanceUids_); + scu.ReportStorageCommitment(transactionUid_, sopClassUids, sopInstanceUids, failureReasons); } @@ -245,7 +318,7 @@ } else { - AddCommand(new LookupCommand(*this, sopClassUid, sopInstanceUid)); + AddCommand(new LookupCommand(context_, sopClassUid, sopInstanceUid)); } } @@ -266,19 +339,14 @@ } - StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context, const Json::Value& serialized) : - SetOfCommandsJob(new Unserializer(*this), serialized), + SetOfCommandsJob(new Unserializer(*this, context), serialized), context_(context) { transactionUid_ = SerializationToolbox::ReadString(serialized, TRANSACTION_UID); remoteModality_ = RemoteModalityParameters(serialized[REMOTE_MODALITY]); calledAet_ = SerializationToolbox::ReadString(serialized, CALLED_AET); - SerializationToolbox::ReadListOfStrings(successSopClassUids_, serialized, SUCCESS_SOP_CLASS_UIDS); - SerializationToolbox::ReadListOfStrings(successSopInstanceUids_, serialized, SUCCESS_SOP_INSTANCE_UIDS); - SerializationToolbox::ReadListOfStrings(failedSopClassUids_, serialized, FAILED_SOP_CLASS_UIDS); - SerializationToolbox::ReadListOfStrings(failedSopInstanceUids_, serialized, FAILED_SOP_INSTANCE_UIDS); } @@ -293,10 +361,6 @@ target[TRANSACTION_UID] = transactionUid_; remoteModality_.Serialize(target[REMOTE_MODALITY], true /* force advanced format */); target[CALLED_AET] = calledAet_; - SerializationToolbox::WriteListOfStrings(target, successSopClassUids_, SUCCESS_SOP_CLASS_UIDS); - SerializationToolbox::WriteListOfStrings(target, successSopInstanceUids_, SUCCESS_SOP_INSTANCE_UIDS); - SerializationToolbox::WriteListOfStrings(target, failedSopClassUids_, FAILED_SOP_CLASS_UIDS); - SerializationToolbox::WriteListOfStrings(target, failedSopInstanceUids_, FAILED_SOP_INSTANCE_UIDS); return true; } } diff -r cccd97333e3d -r 115f82775c46 OrthancServer/ServerJobs/StorageCommitmentScpJob.h --- a/OrthancServer/ServerJobs/StorageCommitmentScpJob.h Mon Feb 10 10:37:10 2020 +0100 +++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.h Mon Feb 10 14:53:36 2020 +0100 @@ -36,7 +36,7 @@ #include "../../Core/DicomNetworking/RemoteModalityParameters.h" #include "../../Core/JobsEngine/SetOfCommandsJob.h" -#include +#include namespace Orthanc { @@ -45,7 +45,8 @@ class StorageCommitmentScpJob : public SetOfCommandsJob { private: - class LookupCommand; + class StorageCommitmentCommand; + class LookupCommand; class AnswerCommand; class Unserializer; @@ -54,13 +55,7 @@ std::string transactionUid_; RemoteModalityParameters remoteModality_; std::string calledAet_; - std::list successSopClassUids_; - std::list successSopInstanceUids_; - std::list failedSopClassUids_; - std::list failedSopInstanceUids_; - void LookupInstance(const std::string& sopClassUid, - const std::string& sopInstanceUid); void Answer(); public: