changeset 3635:8c0ef729d5a8 storage-commitment

StorageCommitmentScpJob
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 29 Jan 2020 17:39:31 +0100
parents 103ee029982e
children bce6ee64f2a4
files Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Core/JobsEngine/JobsEngine.h OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/main.cpp
diffstat 5 files changed, 274 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/DicomUserConnection.cpp	Wed Jan 29 08:51:31 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.cpp	Wed Jan 29 17:39:31 2020 +0100
@@ -1387,23 +1387,46 @@
 
   static void FillSopSequence(DcmDataset& dataset,
                               const DcmTagKey& tag,
-                              const std::vector<std::string>& sopClassUids,
-                              const std::vector<std::string>& sopInstanceUids,
+                              const std::list<std::string>& sopClassUids,
+                              const std::list<std::string>& sopInstanceUids,
                               bool hasFailureReason,
                               Uint16 failureReason)
   {
-    for (size_t i = 0; i < sopClassUids.size(); i++)
+    assert(sopClassUids.size() == sopInstanceUids.size());
+
+    if (sopInstanceUids.empty())
     {
-      std::auto_ptr<DcmItem> item(new DcmItem);
-      if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() ||
-          !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() ||
-          (hasFailureReason &&
-           !item->putAndInsertUint16(DCM_FailureReason, failureReason).good()) ||
-          !dataset.insertSequenceItem(tag, item.release()).good())
+      // Add an empty sequence
+      if (!dataset.insertEmptyElement(tag).good())
       {
         throw OrthancException(ErrorCode_InternalError);
       }
     }
+    else
+    {
+      std::list<std::string>::const_iterator currentClass = sopClassUids.begin();
+      std::list<std::string>::const_iterator currentInstance = sopInstanceUids.begin();
+
+      while (currentClass != sopClassUids.end())
+      {
+        std::auto_ptr<DcmItem> 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()) ||
+            !dataset.insertSequenceItem(tag, item.release()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        ++currentClass;
+        ++currentInstance;
+      }
+      
+      for (size_t i = 0; i < sopClassUids.size(); i++)
+      {
+      }
+    }
   }                              
 
 
@@ -1411,10 +1434,10 @@
 
   void DicomUserConnection::ReportStorageCommitment(
     const std::string& transactionUid,
-    const std::vector<std::string>& successSopClassUids,
-    const std::vector<std::string>& successSopInstanceUids,
-    const std::vector<std::string>& failureSopClassUids,
-    const std::vector<std::string>& failureSopInstanceUids)
+    const std::list<std::string>& successSopClassUids,
+    const std::list<std::string>& successSopInstanceUids,
+    const std::list<std::string>& failureSopClassUids,
+    const std::list<std::string>& failureSopInstanceUids)
   {
     if (successSopClassUids.size() != successSopInstanceUids.size() ||
         failureSopClassUids.size() != failureSopInstanceUids.size())
@@ -1551,8 +1574,8 @@
   
   void DicomUserConnection::RequestStorageCommitment(
     const std::string& transactionUid,
-    const std::vector<std::string>& sopClassUids,
-    const std::vector<std::string>& sopInstanceUids)
+    const std::list<std::string>& sopClassUids,
+    const std::list<std::string>& sopInstanceUids)
   {
     if (sopClassUids.size() != sopInstanceUids.size())
     {
--- a/Core/DicomNetworking/DicomUserConnection.h	Wed Jan 29 08:51:31 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.h	Wed Jan 29 17:39:31 2020 +0100
@@ -228,15 +228,15 @@
 
     void ReportStorageCommitment(
       const std::string& transactionUid,
-      const std::vector<std::string>& successSopClassUids,
-      const std::vector<std::string>& successSopInstanceUids,
-      const std::vector<std::string>& failureSopClassUids,
-      const std::vector<std::string>& failureSopInstanceUids);      
+      const std::list<std::string>& successSopClassUids,
+      const std::list<std::string>& successSopInstanceUids,
+      const std::list<std::string>& failureSopClassUids,
+      const std::list<std::string>& failureSopInstanceUids);      
 
     // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier()
     void RequestStorageCommitment(
       const std::string& transactionUid,
-      const std::vector<std::string>& sopClassUids,
-      const std::vector<std::string>& sopInstanceUids);
+      const std::list<std::string>& sopClassUids,
+      const std::list<std::string>& sopInstanceUids);
   };
 }
--- a/Core/JobsEngine/JobsEngine.h	Wed Jan 29 08:51:31 2020 +0100
+++ b/Core/JobsEngine/JobsEngine.h	Wed Jan 29 17:39:31 2020 +0100
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class JobsEngine
+  class JobsEngine : public boost::noncopyable
   {
   private:
     enum State
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Jan 29 08:51:31 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Jan 29 17:39:31 2020 +0100
@@ -1312,7 +1312,7 @@
       {
         DicomUserConnection scu(localAet, remote);
 
-        std::vector<std::string> sopClassUids, sopInstanceUids;
+        std::list<std::string> sopClassUids, sopInstanceUids;
         sopClassUids.push_back("a");
         sopInstanceUids.push_back("b");
         sopClassUids.push_back("1.2.840.10008.5.1.4.1.1.6.1");
--- a/OrthancServer/main.cpp	Wed Jan 29 08:51:31 2020 +0100
+++ b/OrthancServer/main.cpp	Wed Jan 29 17:39:31 2020 +0100
@@ -58,11 +58,11 @@
 class OrthancStoreRequestHandler : public IStoreRequestHandler
 {
 private:
-  ServerContext& server_;
+  ServerContext& context_;
 
 public:
   OrthancStoreRequestHandler(ServerContext& context) :
-    server_(context)
+    context_(context)
   {
   }
 
@@ -84,66 +84,242 @@
       toStore.SetJson(dicomJson);
 
       std::string id;
-      server_.Store(id, toStore);
+      context_.Store(id, toStore);
     }
   }
 };
 
 
 
-class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler
+namespace Orthanc
 {
-private:
-  ServerContext& server_;
+  class StorageCommitmentScpJob : public SetOfCommandsJob
+  {
+  private:
+    class LookupCommand : public SetOfCommandsJob::ICommand
+    {
+    private:
+      StorageCommitmentScpJob&  that_;
+      std::string               sopClassUid_;
+      std::string               sopInstanceUid_;
+
+    public:
+      LookupCommand(StorageCommitmentScpJob& that,
+                    const std::string& sopClassUid,
+                    const std::string& sopInstanceUid) :
+        that_(that),
+        sopClassUid_(sopClassUid),
+        sopInstanceUid_(sopInstanceUid)
+      {
+      }
+
+      virtual bool Execute()
+      {
+        that_.LookupInstance(sopClassUid_, sopInstanceUid_);
+        return true;
+      }
 
-  // TODO - Remove this
-  static void Toto(std::string* t, std::string* remotec)
-  {
-    try
+      virtual void Serialize(Json::Value& target) const
+      {
+        target = Json::objectValue;
+        target["Type"] = "Lookup";
+        target["SopClassUid"] = sopClassUid_;
+        target["SopInstanceUid"] = sopInstanceUid_;
+      }
+    };
+    
+    class AnswerCommand : public SetOfCommandsJob::ICommand
+    {
+    private:
+      StorageCommitmentScpJob&  that_;
+
+    public:
+      AnswerCommand(StorageCommitmentScpJob& that) :
+        that_(that)
+      {
+      }
+
+      virtual bool Execute()
+      {
+        that_.Answer();
+        return true;
+      }
+
+      virtual void Serialize(Json::Value& target) const
+      {
+        target = Json::objectValue;
+        target["Type"] = "Answer";
+      }
+    };
+    
+    class Unserializer : public SetOfCommandsJob::ICommandUnserializer
     {
-      std::auto_ptr<std::string> tt(t);
-      std::auto_ptr<std::string> remote(remotec);
-    
-      printf("Sleeping\n");
-      boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-      printf("Connect back\n");
+    private:
+      StorageCommitmentScpJob&   that_;
+
+    public:
+      Unserializer(StorageCommitmentScpJob&  that) :
+        that_(that)
+      {
+      }
+
+      virtual ICommand* Unserialize(const Json::Value& source) const
+      {
+        std::cout << "===================================\n";
+        std::cout << source.toStyledString();
+        
+        /*DicomMap findAnswer;
+        findAnswer.Unserialize(source);
+        return new Command(that_, findAnswer);*/
+
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+    };
+
+    ServerContext&            context_;
+    bool                      ready_;
+    std::string               transactionUid_;
+    RemoteModalityParameters  remoteModality_;
+    std::string               calledAet_;
+    std::list<std::string>    successSopClassUids_;
+    std::list<std::string>    successSopInstanceUids_;
+    std::list<std::string>    failedSopClassUids_;
+    std::list<std::string>    failedSopInstanceUids_;
 
-      RemoteModalityParameters p;
+    void LookupInstance(const std::string& sopClassUid,
+                        const std::string& sopInstanceUid)
+    {
+      bool success = false;
+      
+      try
+      {
+        std::vector<std::string> orthancId;
+        context_.GetIndex().LookupIdentifierExact(orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUid);
 
-      if (*remote == "ORTHANC")
+        if (orthancId.size() == 1)
+        {
+          std::string a, b;
+          
+          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&)
       {
-        p = RemoteModalityParameters("ORTHANC", "localhost", 4242, ModalityManufacturer_Generic);
+      }
+
+      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
       {
-        p = RemoteModalityParameters("STGCMTSCU", "localhost", 11114, ModalityManufacturer_Generic);
+        failedSopClassUids_.push_back(sopClassUid);
+        failedSopInstanceUids_.push_back(sopInstanceUid);
       }
-        
-      DicomUserConnection scu("ORTHANC", p);
+    }
+
+    void Answer()
+    {
+      LOG(INFO) << "  Storage commitment SCP job: Sending answer";
+      
+      DicomUserConnection scu(calledAet_, remoteModality_);
+      scu.ReportStorageCommitment(transactionUid_, successSopClassUids_, successSopInstanceUids_,
+                                  failedSopClassUids_, failedSopInstanceUids_);
 
-      std::vector<std::string> a, b, c, d;
-      a.push_back("a");  b.push_back("b");
-      a.push_back("c");  b.push_back("d");
+      /**
+       * "After the N-EVENT-REPORT has been sent, the Transaction UID is
+       * no longer active and shall not be reused for other
+       * transactions."
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
+       **/
+    }
     
-      scu.ReportStorageCommitment(tt->c_str(), a, b, c, d);
-      //scu.ReportStorageCommitment(tt->c_str(), a, b, a, b);
-    }
-    catch (OrthancException& e)
+  public:
+    StorageCommitmentScpJob(ServerContext& context,
+                            const std::string& transactionUid,
+                            const std::string& remoteAet,
+                            const std::string& calledAet) :
+      context_(context),
+      ready_(false),
+      transactionUid_(transactionUid),
+      calledAet_(calledAet)
     {
-      LOG(ERROR) << "EXCEPTION: " << e.What();
+      {
+        OrthancConfiguration::ReaderLock lock;
+        if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, remoteAet))
+        {
+          throw OrthancException(ErrorCode_InexistentItem,
+                                 "Unknown remote modality for storage commitment SCP: " + remoteAet);
+        }
+      }
     }
 
-    /**
-     * "After the N-EVENT-REPORT has been sent, the Transaction UID is
-     * no longer active and shall not be reused for other
-     * transactions."
-     * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
-     **/
-  }
+    void AddInstance(const std::string& sopClassUid,
+                     const std::string& sopInstanceUid)
+    {
+      if (ready_)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        AddCommand(new LookupCommand(*this, sopClassUid, sopInstanceUid));        
+      }
+    }
+
+    void MarkAsReady()
+    {
+      if (ready_)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        AddCommand(new AnswerCommand(*this));
+        ready_ = true;
+      }
+    }
+
+    virtual void Stop(JobStopReason reason)
+    {
+    }
+
+    virtual void GetJobType(std::string& target)
+    {
+      target = "StorageCommitmentScp";
+    }
+
+    virtual void GetPublicContent(Json::Value& value)
+    {
+      SetOfCommandsJob::GetPublicContent(value);
+      
+      value["LocalAet"] = calledAet_;
+      value["RemoteAet"] = remoteModality_.GetApplicationEntityTitle();
+      value["TransactionUid"] = transactionUid_;
+    }
+  };
+}
+
+
+class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler
+{
+private:
+  ServerContext& context_;
   
 public:
   OrthancStorageCommitmentRequestHandler(ServerContext& context) :
-    server_(context)
+    context_(context)
   {
   }
 
@@ -154,11 +330,22 @@
                              const std::string& remoteAet,
                              const std::string& calledAet)
   {
-    // TODO - Enqueue a Storage commitment job
+    if (referencedSopClassUids.size() != referencedSopInstanceUids.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    std::auto_ptr<StorageCommitmentScpJob> job(
+      new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet));
 
-    boost::thread t(Toto, new std::string(transactionUid), new std::string(remoteAet));
+    for (size_t i = 0; i < referencedSopClassUids.size(); i++)
+    {
+      job->AddInstance(referencedSopClassUids[i], referencedSopInstanceUids[i]);
+    }
 
-    printf("HANDLE REQUEST\n");
+    job->MarkAsReady();
+
+    context_.GetJobsEngine().GetRegistry().Submit(job.release(), 0 /* default priority */);
   }
 
   virtual void HandleReport(const std::string& transactionUid,