diff OrthancStone/Sources/Oracle/GenericOracleRunner.cpp @ 1512:244ad1e4e76a

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 Jul 2020 16:21:02 +0200
parents Framework/Oracle/GenericOracleRunner.cpp@121d01aa328e
children 85e117739eca
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Oracle/GenericOracleRunner.cpp	Tue Jul 07 16:21:02 2020 +0200
@@ -0,0 +1,524 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "GenericOracleRunner.h"
+
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#include "GetOrthancImageCommand.h"
+#include "GetOrthancWebViewerJpegCommand.h"
+#include "HttpCommand.h"
+#include "OracleCommandExceptionMessage.h"
+#include "OrthancRestApiCommand.h"
+#include "ParseDicomFromFileCommand.h"
+#include "ParseDicomFromWadoCommand.h"
+#include "ReadFileCommand.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  include "ParseDicomSuccessMessage.h"
+#  include <dcmtk/dcmdata/dcdeftag.h>
+#  include <dcmtk/dcmdata/dcfilefo.h>
+static unsigned int BUCKET_DICOMDIR = 0;
+static unsigned int BUCKET_SOP = 1;
+#endif
+
+#include <Compression/GzipCompressor.h>
+#include <HttpClient.h>
+#include <OrthancException.h>
+#include <Toolbox.h>
+#include <SystemToolbox.h>
+
+#include <boost/filesystem.hpp>
+
+
+
+namespace OrthancStone
+{
+  static void CopyHttpHeaders(Orthanc::HttpClient& client,
+                              const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator
+           it = headers.begin(); it != headers.end(); it++ )
+    {
+      client.AddHeader(it->first, it->second);
+    }
+  }
+
+
+  static void DecodeAnswer(std::string& answer,
+                           const Orthanc::HttpClient::HttpHeaders& headers)
+  {
+    Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
+
+    for (Orthanc::HttpClient::HttpHeaders::const_iterator it = headers.begin(); 
+         it != headers.end(); ++it)
+    {
+      std::string s;
+      Orthanc::Toolbox::ToLowerCase(s, it->first);
+
+      if (s == "content-encoding")
+      {
+        if (it->second == "gzip")
+        {
+          contentEncoding = Orthanc::HttpCompression_Gzip;
+        }
+        else 
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
+                                          "Unsupported HTTP Content-Encoding: " + it->second);
+        }
+
+        break;
+      }
+    }
+
+    if (contentEncoding == Orthanc::HttpCompression_Gzip)
+    {
+      std::string compressed;
+      answer.swap(compressed);
+          
+      Orthanc::GzipCompressor compressor;
+      compressor.Uncompress(answer, compressed.c_str(), compressed.size());
+
+      LOG(INFO) << "Uncompressing gzip Encoding: from " << compressed.size()
+                << " to " << answer.size() << " bytes";
+    }
+  }
+
+
+  static void RunHttpCommand(std::string& answer,
+                             Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                             const HttpCommand& command)
+  {
+    Orthanc::HttpClient client;
+    client.SetUrl(command.GetUrl());
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.HasCredentials())
+    {
+      client.SetCredentials(command.GetUsername().c_str(), command.GetPassword().c_str());
+    }
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const HttpCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunHttpCommand(answer, answerHeaders, command);
+    
+    HttpCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+  
+  static void RunOrthancRestApiCommand(std::string& answer,
+                                       Orthanc::HttpClient::HttpHeaders& answerHeaders,
+                                       const Orthanc::WebServiceParameters& orthanc,
+                                       const OrthancRestApiCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetMethod(command.GetMethod());
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    if (command.GetMethod() == Orthanc::HttpMethod_Post ||
+        command.GetMethod() == Orthanc::HttpMethod_Put)
+    {
+      client.SetBody(command.GetBody());
+    }
+
+    client.ApplyAndThrowException(answer, answerHeaders);
+    DecodeAnswer(answer, answerHeaders);
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const OrthancRestApiCommand& command)
+  {
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command);
+
+    OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
+    emitter.EmitMessage(receiver, message);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancImageCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+    
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(receiver, emitter, answer, answerHeaders);
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const GetOrthancWebViewerJpegCommand& command)
+  {
+    Orthanc::HttpClient client(orthanc, command.GetUri());
+    client.SetRedirectionFollowed(false);
+    client.SetTimeout(command.GetTimeout());
+
+    CopyHttpHeaders(client, command.GetHttpHeaders());
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+    client.ApplyAndThrowException(answer, answerHeaders);
+
+    DecodeAnswer(answer, answerHeaders);
+
+    command.ProcessHttpAnswer(receiver, emitter, answer);
+  }
+
+
+  static std::string GetPath(const std::string& root,
+                             const std::string& file)
+  {
+    boost::filesystem::path a(root);
+    boost::filesystem::path b(file);
+
+    boost::filesystem::path c;
+    if (b.is_absolute())
+    {
+      c = b;
+    }
+    else
+    {
+      c = a / b;
+    }
+
+    return c.string();
+  }
+
+
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          const std::string& root,
+                          const ReadFileCommand& command)
+  {
+    std::string path = GetPath(root, command.GetPath());
+    LOG(TRACE) << "Oracle reading file: " << path;
+
+    std::string content;
+    Orthanc::SystemToolbox::ReadFile(content, path, true /* log */);
+
+    ReadFileCommand::SuccessMessage message(command, content);
+    emitter.EmitMessage(receiver, message);
+  }
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+  static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize,  /* OUT */
+                                              const std::string& path,
+                                              bool isPixelData)
+  {
+    if (!Orthanc::SystemToolbox::IsRegularFile(path))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile);
+    }
+    
+    LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without")
+               << " pixel data: " << path;
+    
+    boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time();
+    
+    fileSize = Orthanc::SystemToolbox::GetFileSize(path);
+    
+    // Check for 32bit systems
+    if (fileSize != static_cast<uint64_t>(static_cast<size_t>(fileSize)))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    DcmFileFormat dicom;
+    bool ok;
+    
+    if (isPixelData)
+    {
+      ok = dicom.loadFile(path.c_str()).good();
+    }
+    else
+    {
+#if DCMTK_VERSION_NUMBER >= 362
+      /**
+       * NB : We could stop at (0x3007, 0x0000) instead of
+       * DCM_PixelData as the Stone framework does not use further
+       * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still
+       * use "PixelData" as this does not change the runtime much, and
+       * as it is more explicit.
+       **/
+      static const DcmTagKey STOP = DCM_PixelData;
+      //static const DcmTagKey STOP(0x3007, 0x0000);
+
+      ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange,
+                                  DCM_MaxReadLength, ERM_autoDetect, STOP).good();
+#else
+      // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2
+      ok = dicom.loadFile(path.c_str()).good();
+#endif
+    }
+
+    if (ok)
+    {
+      std::unique_ptr<Orthanc::ParsedDicomFile> result(new Orthanc::ParsedDicomFile(dicom));
+
+      boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
+      LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms";
+
+      return result.release();
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Cannot parse file: " + path);
+    }
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const std::string& root,
+                          const ParseDicomFromFileCommand& command)
+  {
+    const std::string path = GetPath(root, command.GetPath());
+
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path);
+      if (reader.IsValid() &&
+          (!command.IsPixelDataIncluded() ||
+           reader.HasPixelData()))
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
+                                         reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+
+    uint64_t fileSize;
+    std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded()));
+
+    if (fileSize != static_cast<size_t>(fileSize))
+    {
+      // Cannot load such a large file on 32-bit architecture
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+    
+    {
+      ParseDicomSuccessMessage message
+        (command, command.GetSource(), *parsed,
+         static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
+      
+      // Invalidate to overwrite DICOM instance that would already
+      // be stored without pixel data
+      cache->Invalidate(BUCKET_DICOMDIR, path);
+      
+      cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(),
+                     static_cast<size_t>(fileSize), command.IsPixelDataIncluded());
+    }
+  }
+
+  
+  static void RunInternal(boost::weak_ptr<IObserver> receiver,
+                          IMessageEmitter& emitter,
+                          boost::shared_ptr<ParsedDicomCache> cache,
+                          const Orthanc::WebServiceParameters& orthanc,
+                          const ParseDicomFromWadoCommand& command)
+  {
+    if (cache)
+    {
+      ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid());
+      if (reader.IsValid() &&
+          reader.HasPixelData())
+      {
+        // Reuse the DICOM file from the cache
+        ParseDicomSuccessMessage message(command, command.GetSource(), reader.GetDicom(),
+                                         reader.GetFileSize(), reader.HasPixelData());
+        emitter.EmitMessage(receiver, message);
+        return;
+      }
+    }
+
+    std::string answer;
+    Orthanc::HttpClient::HttpHeaders answerHeaders;
+
+    switch (command.GetRestCommand().GetType())
+    {
+      case IOracleCommand::Type_Http:
+        RunHttpCommand(answer, answerHeaders, dynamic_cast<const HttpCommand&>(command.GetRestCommand()));
+        break;
+        
+      case IOracleCommand::Type_OrthancRestApi:
+        RunOrthancRestApiCommand(answer, answerHeaders, orthanc,
+                                 dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand()));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    size_t fileSize;
+    std::unique_ptr<Orthanc::ParsedDicomFile> parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders));
+
+    {
+      ParseDicomSuccessMessage message(command, command.GetSource(), *parsed, fileSize,
+                                       true /* pixel data always is included in WADO-RS */);
+      emitter.EmitMessage(receiver, message);
+    }
+
+    if (cache)
+    {
+      // Store it into the cache for future use
+      cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true);
+    }
+  }
+#endif
+
+
+  void GenericOracleRunner::Run(boost::weak_ptr<IObserver> receiver,
+                                IMessageEmitter& emitter,
+                                const IOracleCommand& command)
+  {
+    Orthanc::ErrorCode error = Orthanc::ErrorCode_Success;
+    
+    try
+    {
+      switch (command.GetType())
+      {
+        case IOracleCommand::Type_Sleep:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
+                                          "Sleep command cannot be executed by the runner");
+
+        case IOracleCommand::Type_Http:
+          RunInternal(receiver, emitter, dynamic_cast<const HttpCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_OrthancRestApi:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const OrthancRestApiCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_GetOrthancImage:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const GetOrthancImageCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_GetOrthancWebViewerJpeg:
+          RunInternal(receiver, emitter, orthanc_,
+                      dynamic_cast<const GetOrthancWebViewerJpegCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_ReadFile:
+          RunInternal(receiver, emitter, rootDirectory_,
+                      dynamic_cast<const ReadFileCommand&>(command));
+          break;
+
+        case IOracleCommand::Type_ParseDicomFromFile:
+        case IOracleCommand::Type_ParseDicomFromWado:
+#if ORTHANC_ENABLE_DCMTK == 1
+          switch (command.GetType())
+          {
+            case IOracleCommand::Type_ParseDicomFromFile:
+              RunInternal(receiver, emitter, dicomCache_, rootDirectory_,
+                          dynamic_cast<const ParseDicomFromFileCommand&>(command));
+              break;
+
+            case IOracleCommand::Type_ParseDicomFromWado:
+              RunInternal(receiver, emitter, dicomCache_, orthanc_,
+                          dynamic_cast<const ParseDicomFromWadoCommand&>(command));
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }            
+          break;
+#else
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
+                                          "DCMTK must be enabled to parse DICOM files");
+#endif
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception within the oracle: " << e.What();
+      error = e.GetErrorCode();
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Threaded exception within the oracle";
+      error = Orthanc::ErrorCode_InternalError;
+    }
+
+    if (error != Orthanc::ErrorCode_Success)
+    {
+      OracleCommandExceptionMessage message(command, error);
+      emitter.EmitMessage(receiver, message);
+    }
+  }
+}