changeset 294:ca7f14c91abf refactoring

change in orthanc sdk api
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Jun 2019 19:44:29 +0200
parents 7e3989d2ea5a
children a0e72a40f5d5
files CMakeLists.txt Plugin/Plugin.cpp Plugin/StowRs.cpp Plugin/StowRs.h
diffstat 4 files changed, 236 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sat Jun 08 18:10:32 2019 +0200
+++ b/CMakeLists.txt	Tue Jun 11 19:44:29 2019 +0200
@@ -113,11 +113,7 @@
 
 add_definitions(
   -DHAS_ORTHANC_EXCEPTION=1
-  -DHAS_ORTHANC_FRAMEWORK=1
   -DORTHANC_ENABLE_LOGGING_PLUGIN=1
-  -DORTHANC_FRAMEWORK_VERSION_MAJOR=${ORTHANC_FRAMEWORK_MAJOR}
-  -DORTHANC_FRAMEWORK_VERSION_MINOR=${ORTHANC_FRAMEWORK_MINOR}
-  -DORTHANC_FRAMEWORK_VERSION_REVISION=${ORTHANC_FRAMEWORK_REVISION}
   )
 
 set(CORE_SOURCES
--- a/Plugin/Plugin.cpp	Sat Jun 08 18:10:32 2019 +0200
+++ b/Plugin/Plugin.cpp	Tue Jun 11 19:44:29 2019 +0200
@@ -31,47 +31,56 @@
 #include <Core/Toolbox.h>
 
 
-void SwitchStudies(OrthancPluginRestOutput* output,
-                   const char* url,
-                   const OrthancPluginHttpRequest* request)
+OrthancPlugins::IChunkedRequestReader* SwitchStudies(OrthancPluginRestOutput* output,
+                                                     const char* url,
+                                                     const OrthancPluginHttpRequest* request)
 {
   switch (request->method)
   {
     case OrthancPluginHttpMethod_Get:
       // This is QIDO-RS
       SearchForStudies(output, url, request);
-      break;
+      return NULL;
 
     case OrthancPluginHttpMethod_Post:
-      // This is STOW-RS: This should have been processed by the MultipartRestCallback
+      // This is STOW-RS
+      return OrthancPlugins::StowServer::PostCallback(url, request);
 
     default:
-      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
-      break;
+      if (output != NULL)
+      {
+        OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");  // TODO
+      }
+      return NULL;
   }
 }
 
 
-void SwitchIndividualStudy(OrthancPluginRestOutput* output,
-                           const char* url,
-                           const OrthancPluginHttpRequest* request)
+OrthancPlugins::IChunkedRequestReader* SwitchIndividualStudy(OrthancPluginRestOutput* output,
+                                                             const char* url,
+                                                             const OrthancPluginHttpRequest* request)
 {
   switch (request->method)
   {
     case OrthancPluginHttpMethod_Get:
       // This is WADO-RS
       RetrieveDicomStudy(output, url, request);
-      break;
+      return NULL;
 
     case OrthancPluginHttpMethod_Post:
-      // This is STOW-RS: This should have been processed by the MultipartRestCallback
+      // This is STOW-RS
+      return OrthancPlugins::StowServer::PostCallback(url, request);
 
     default:
-      OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");
-      break;
+      if (output != NULL)
+      {
+        OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST");  // TODO
+      }
+      return NULL;
   }
 }
 
+
 bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key)
 {
   for (uint32_t i = 0; i < request->getCount; i++)
@@ -167,9 +176,6 @@
 
 
 
-static OrthancPlugins::StowServer stowServer_;
-
-
 #include <boost/filesystem.hpp>
 #include <Core/SystemToolbox.h>
 
@@ -319,13 +325,11 @@
 
         OrthancPlugins::LogWarning("URI to the DICOMweb REST API: " + root);
 
-        stowServer_.Register(root + "studies");
-        stowServer_.Register(root + "studies/([^/]*)");
+        OrthancPlugins::RegisterChunkedRestCallback<SwitchStudies>(root + "studies");
+        OrthancPlugins::RegisterChunkedRestCallback<SwitchIndividualStudy>(root + "studies/([^/]*)");
 
         OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true);
         OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", true);    
-        OrthancPlugins::RegisterRestCallback<SwitchStudies>(root + "studies", true);
-        OrthancPlugins::RegisterRestCallback<SwitchIndividualStudy>(root + "studies/([^/]*)", true);
         OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "studies/([^/]*)/instances", true);    
         OrthancPlugins::RegisterRestCallback<RetrieveStudyMetadata>(root + "studies/([^/]*)/metadata", true);
         OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "studies/([^/]*)/series", true);    
--- a/Plugin/StowRs.cpp	Sat Jun 08 18:10:32 2019 +0200
+++ b/Plugin/StowRs.cpp	Tue Jun 11 19:44:29 2019 +0200
@@ -27,181 +27,24 @@
 
 namespace OrthancPlugins
 {
-  class StowServer::Handler : public IHandler
-  {
-  private:
-    OrthancPluginContext*  context_;
-    bool                   xml_;
-    std::string            wadoBase_;
-    std::string            expectedStudy_;
-    bool                   isFirst_;
-    Json::Value            result_;
-    Json::Value            success_;
-    Json::Value            failed_;
-
-  public:
-    Handler(OrthancPluginContext* context,
-            bool xml,
-            const std::string& wadoBase,
-            const std::string& expectedStudy) :
-      context_(context),
-      xml_(xml),
-      wadoBase_(wadoBase),
-      expectedStudy_(expectedStudy),
-      isFirst_(true),
-      result_(Json::objectValue),
-      success_(Json::arrayValue),
-      failed_(Json::arrayValue)
-    {
-    }
-
-    virtual OrthancPluginErrorCode AddPart(const std::string& contentType,
-                                           const std::map<std::string, std::string>& headers,
-                                           const void* data,
-                                           size_t size)
+  StowServer::StowServer(OrthancPluginContext* context,
+                         const std::map<std::string, std::string>& headers,
+                         const std::string& expectedStudy) :
+    context_(context),
+    xml_(Configuration::IsXmlExpected(headers)),
+    wadoBase_(Configuration::GetBaseUrl(headers)),
+    expectedStudy_(expectedStudy),
+    isFirst_(true),
+    result_(Json::objectValue),
+    success_(Json::arrayValue),
+    failed_(Json::arrayValue)
+  { 
+    std::string tmp, contentType, subType, boundary;
+    if (!Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers) ||
+        !Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp))
     {
-      if (contentType != "application/dicom")
-      {
-        throw Orthanc::OrthancException(
-          Orthanc::ErrorCode_UnsupportedMediaType,
-          "The STOW-RS request contains a part that is not "
-          "\"application/dicom\" (it is: \"" + contentType + "\")");
-      }
-
-      Json::Value dicom;
-
-      try
-      {
-        OrthancString s;
-        s.Assign(OrthancPluginDicomBufferToJson(context_, data, size,
-                                                OrthancPluginDicomToJsonFormat_Short,
-                                                OrthancPluginDicomToJsonFlags_None, 256));
-        s.ToJson(dicom);
-      }
-      catch (Orthanc::OrthancException&)
-      {
-        // Bad DICOM file => TODO add to error
-        LogWarning("STOW-RS cannot parse an incoming DICOM file");
-        return OrthancPluginErrorCode_Success;
-      }           
-
-      if (dicom.type() != Json::objectValue ||
-          !dicom.isMember(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()) ||
-          !dicom.isMember(Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()) ||
-          !dicom.isMember(Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()) ||
-          !dicom.isMember(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()) ||
-          dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].type() != Json::stringValue ||
-          dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].type() != Json::stringValue ||
-          dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue ||
-          dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue)
-      {
-        LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
-        return OrthancPluginErrorCode_Success;
-      }
-
-      const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString();
-      const std::string sopClassUid = dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].asString();
-      const std::string sopInstanceUid = dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].asString();
-      const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString();
-
-      Json::Value item = Json::objectValue;
-      item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
-      item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
-      
-      if (!expectedStudy_.empty() &&
-          studyInstanceUid != expectedStudy_)
-      {
-        LogInfo("STOW-RS request restricted to study [" + expectedStudy_ + 
-                "]: Ignoring instance from study [" + studyInstanceUid + "]");
-
-        /*item[DICOM_TAG_WARNING_REASON.Format()] =
-          boost::lexical_cast<std::string>(0xB006);  // Elements discarded
-          success.append(item);*/
-      }
-      else
-      {
-        if (isFirst_)
-        {
-          std::string url = wadoBase_ + "studies/" + studyInstanceUid;
-          result_[DICOM_TAG_RETRIEVE_URL.Format()] = url;
-          isFirst_ = false;
-        }
-
-        MemoryBuffer tmp;
-        bool ok = tmp.RestApiPost("/instances", data, size, false);
-        tmp.Clear();
-
-        if (ok)
-        {
-          std::string url = (wadoBase_ + 
-                             "studies/" + studyInstanceUid +
-                             "/series/" + seriesInstanceUid +
-                             "/instances/" + sopInstanceUid);
-
-          item[DICOM_TAG_RETRIEVE_URL.Format()] = url;
-          success_.append(item);      
-        }
-        else
-        {
-          LogError("Orthanc was unable to store one instance in a STOW-RS request");
-          item[DICOM_TAG_FAILURE_REASON.Format()] =
-            boost::lexical_cast<std::string>(0x0110);  // Processing failure
-          failed_.append(item);
-        }
-      }
-      
-      return OrthancPluginErrorCode_Success;
-    }
-
-    virtual OrthancPluginErrorCode Execute(OrthancPluginRestOutput* output)
-    {
-      result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_;
-      result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_;
-
-      std::string answer;
-  
-      {
-        DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
-        locker.Apply(answer, context_, result_, xml_);
-      }
-      
-      OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(),
-                                xml_ ? "application/dicom+xml" : "application/dicom+json");
-
-      return OrthancPluginErrorCode_Success;
-    }
-  };
-
-  
-  StowServer::IHandler* StowServer::CreateHandler(OrthancPluginHttpMethod method,
-                                                  const std::string& url,
-                                                  const std::string& contentType,
-                                                  const std::string& subType,
-                                                  const std::vector<std::string>& groups,
-                                                  const std::map<std::string, std::string>& headers)
-  {
-    OrthancPluginContext* context = GetGlobalContext();
-  
-    if (method != OrthancPluginHttpMethod_Post)
-    {
-      return NULL;
-    }
-
-    const std::string wadoBase = Configuration::GetBaseUrl(headers);
-  
-    std::string expectedStudy;
-    if (groups.size() == 1)
-    {
-      expectedStudy = groups[0];
-    }
-
-    if (expectedStudy.empty())
-    {
-      LogInfo("STOW-RS request without study");
-    }
-    else
-    {
-      LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
+                                      "The STOW-RS server expects a multipart body in its request");
     }
 
     if (contentType != "multipart/related")
@@ -216,6 +59,169 @@
                                       "The STOW-RS plugin currently only supports \"application/dicom\" subtype");
     }
 
-    return new Handler(context, Configuration::IsXmlExpected(headers), wadoBase, expectedStudy);
+    parser_.reset(new Orthanc::MultipartStreamReader(boundary));
+    parser_->SetHandler(*this);
+  }
+
+
+  void StowServer::HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
+                              const void* part,
+                              size_t size)
+  {
+    std::string contentType;
+
+    if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers) ||
+        contentType != "application/dicom")
+    {
+      throw Orthanc::OrthancException(
+        Orthanc::ErrorCode_UnsupportedMediaType,
+        "The STOW-RS request contains a part that is not "
+        "\"application/dicom\" (it is: \"" + contentType + "\")");
+    }
+
+    Json::Value dicom;
+
+    try
+    {
+      OrthancString s;
+      s.Assign(OrthancPluginDicomBufferToJson(context_, part, size,
+                                              OrthancPluginDicomToJsonFormat_Short,
+                                              OrthancPluginDicomToJsonFlags_None, 256));
+      s.ToJson(dicom);
+    }
+    catch (Orthanc::OrthancException&)
+    {
+      // Bad DICOM file => TODO add to error
+      LogWarning("STOW-RS cannot parse an incoming DICOM file");
+      return;
+    }           
+
+    if (dicom.type() != Json::objectValue ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()) ||
+        !dicom.isMember(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()) ||
+        dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue ||
+        dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue)
+    {
+      LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
+      return;
+    }
+
+    const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString();
+    const std::string sopClassUid = dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].asString();
+    const std::string sopInstanceUid = dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].asString();
+    const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString();
+
+    Json::Value item = Json::objectValue;
+    item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
+    item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
+      
+    if (!expectedStudy_.empty() &&
+        studyInstanceUid != expectedStudy_)
+    {
+      LogInfo("STOW-RS request restricted to study [" + expectedStudy_ + 
+              "]: Ignoring instance from study [" + studyInstanceUid + "]");
+
+      /*item[DICOM_TAG_WARNING_REASON.Format()] =
+        boost::lexical_cast<std::string>(0xB006);  // Elements discarded
+        success.append(item);*/
+    }
+    else
+    {
+      if (isFirst_)
+      {
+        std::string url = wadoBase_ + "studies/" + studyInstanceUid;
+        result_[DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        isFirst_ = false;
+      }
+
+      MemoryBuffer tmp;
+      bool ok = tmp.RestApiPost("/instances", part, size, false);
+      tmp.Clear();
+
+      if (ok)
+      {
+        std::string url = (wadoBase_ + 
+                           "studies/" + studyInstanceUid +
+                           "/series/" + seriesInstanceUid +
+                           "/instances/" + sopInstanceUid);
+
+        item[DICOM_TAG_RETRIEVE_URL.Format()] = url;
+        success_.append(item);      
+      }
+      else
+      {
+        LogError("Orthanc was unable to store one instance in a STOW-RS request");
+        item[DICOM_TAG_FAILURE_REASON.Format()] =
+          boost::lexical_cast<std::string>(0x0110);  // Processing failure
+        failed_.append(item);
+      }
+    }
+  }
+
+
+  void StowServer::AddChunk(const void* data,
+                            size_t size)
+  {
+    assert(parser_.get() != NULL);
+    parser_->AddChunk(data, size);
+  }
+
+
+  void StowServer::Execute(OrthancPluginRestOutput* output)
+  {
+    assert(parser_.get() != NULL);
+    parser_->CloseStream();
+
+    result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_;
+    result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_;
+    
+    std::string answer;
+    
+    {
+      DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
+      locker.Apply(answer, context_, result_, xml_);
+    }
+      
+    OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(),
+                              xml_ ? "application/dicom+xml" : "application/dicom+json");
+  };
+
+  
+  IChunkedRequestReader* StowServer::PostCallback(const char* url,
+                                                  const OrthancPluginHttpRequest* request)
+  {
+    OrthancPluginContext* context = GetGlobalContext();
+  
+    if (request->method != OrthancPluginHttpMethod_Post)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    std::map<std::string, std::string> headers;
+    for (uint32_t i = 0; i < request->headersCount; i++)
+    {
+      headers[request->headersKeys[i]] = request->headersValues[i];
+    }
+
+    std::string expectedStudy;
+    if (request->groupsCount == 1)
+    {
+      expectedStudy = request->groups[0];
+    }
+
+    if (expectedStudy.empty())
+    {
+      LogInfo("STOW-RS request without study");
+    }
+    else
+    {
+      LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
+    }
+
+    return new StowServer(context, headers, expectedStudy);
   }
 }
--- a/Plugin/StowRs.h	Sat Jun 08 18:10:32 2019 +0200
+++ b/Plugin/StowRs.h	Tue Jun 11 19:44:29 2019 +0200
@@ -21,21 +21,42 @@
 
 #pragma once
 
+#include <Core/HttpServer/MultipartStreamReader.h>
 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 
 namespace OrthancPlugins
 {
-  class StowServer : public MultipartRestCallback
+  class StowServer : 
+    public IChunkedRequestReader,
+    private Orthanc::MultipartStreamReader::IHandler
   {
   private:
-    class Handler;
-    
+    OrthancPluginContext*  context_;
+    bool                   xml_;
+    std::string            wadoBase_;
+    std::string            expectedStudy_;
+    bool                   isFirst_;
+    Json::Value            result_;
+    Json::Value            success_;
+    Json::Value            failed_;
+
+    std::auto_ptr<Orthanc::MultipartStreamReader>  parser_;
+
+    virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
+                            const void* part,
+                            size_t size);
+
   public:
-    virtual IHandler* CreateHandler(OrthancPluginHttpMethod method,
-                                    const std::string& url,
-                                    const std::string& contentType,
-                                    const std::string& subType,
-                                    const std::vector<std::string>& groups,
-                                    const std::map<std::string, std::string>& headers);
+    StowServer(OrthancPluginContext* context,
+               const std::map<std::string, std::string>& headers,
+               const std::string& expectedStudy);
+
+    virtual void AddChunk(const void* data,
+                          size_t size);
+
+    virtual void Execute(OrthancPluginRestOutput* output);
+
+    static IChunkedRequestReader* PostCallback(const char* url,
+                                               const OrthancPluginHttpRequest* request);
   };
 }