view Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp @ 1628:555870323ebc

fix
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 07 Nov 2020 00:08:45 +0100
parents 4fb8fdf03314
children 6fa05252b085
line wrap: on
line source

/**
 * 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/>.
 **/


#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
#  include "WebAssemblyOracle_Includes.h"
#else
// This is the case when using the WebAssembly side module, and this
// source file must be compiled within the WebAssembly main module
#  include <Oracle/WebAssemblyOracle_Includes.h>
#endif

#include <OrthancException.h>
#include <Toolbox.h>

#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/fetch.h>


#if ORTHANC_ENABLE_DCMTK == 1
static unsigned int BUCKET_SOP = 1;
#endif


namespace OrthancStone
{
  class WebAssemblyOracle::TimeoutContext
  {
  private:
    WebAssemblyOracle&                 oracle_;
    boost::weak_ptr<IObserver>         receiver_;
    std::unique_ptr<SleepOracleCommand>  command_;

  public:
    TimeoutContext(WebAssemblyOracle& oracle,
                   boost::weak_ptr<IObserver> receiver,
                   IOracleCommand* command) :
      oracle_(oracle),
      receiver_(receiver)
    {
      if (command == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
      else
      {
        command_.reset(dynamic_cast<SleepOracleCommand*>(command));
      }
    }

    void EmitMessage()
    {      
      assert(command_.get() != NULL);

      SleepOracleCommand::TimeoutMessage message(*command_);
      oracle_.EmitMessage(receiver_, message);
    }

    static void Callback(void *userData)
    {
      std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
      context->EmitMessage();
    }
  };
    

  /**
  This object is created on the heap for every http request.
  It is deleted in the success (or error) callbacks.

  This object references the receiver of the request. Since this is a raw
  reference, we need additional checks to make sure we send the response to
  the same object, for the object can be deleted and a new one recreated at the
  same address (it often happens in the [single-threaded] browser context).
  */
  class WebAssemblyOracle::FetchContext : public boost::noncopyable
  {
  private:
    WebAssemblyOracle&             oracle_;
    boost::weak_ptr<IObserver>     receiver_;
    std::unique_ptr<IOracleCommand>  command_;
    std::string                    expectedContentType_;

  public:
    FetchContext(WebAssemblyOracle& oracle,
                 boost::weak_ptr<IObserver> receiver,
                 IOracleCommand* command,
                 const std::string& expectedContentType) :
      oracle_(oracle),
      receiver_(receiver),
      command_(command),
      expectedContentType_(expectedContentType)
    {
      if (Orthanc::Logging::IsTraceLevelEnabled())
      {
        // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled
        LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | "
                   << "receiver address = " << std::hex << receiver.lock().get();
      }

      if (command == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
    }

    const std::string& GetExpectedContentType() const
    {
      return expectedContentType_;
    }

    IMessageEmitter& GetEmitter() const
    {
      return oracle_;
    }

    boost::weak_ptr<IObserver> GetReceiver() const
    {
      return receiver_;
    }

    void EmitMessage(const IMessage& message)
    {
      if (Orthanc::Logging::IsTraceLevelEnabled())
      {
        // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled
        LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = "
                   << std::hex << receiver_.lock().get() << std::dec;
      }
      
      oracle_.EmitMessage(receiver_, message);
    }

    IOracleCommand& GetCommand() const
    {
      return *command_;
    }

    template <typename T>
    const T& GetTypedCommand() const
    {
      return dynamic_cast<T&>(*command_);
    }

#if ORTHANC_ENABLE_DCMTK == 1
    void StoreInCache(const std::string& sopInstanceUid,
                      std::unique_ptr<Orthanc::ParsedDicomFile>& dicom,
                      size_t fileSize)
    {
      if (oracle_.dicomCache_.get())
      {
        // Store it into the cache for future use
        oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid,
                                     dicom.release(), fileSize, true);
      }              
    }
#endif

    static void SuccessCallback(emscripten_fetch_t *fetch)
    {
      /**
       * Firstly, make a local copy of the fetched information, and
       * free data associated with the fetch.
       **/
      
      if (fetch->userData == NULL)
      {
        LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!";
        return;
      }

      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));

      std::string answer;
      if (fetch->numBytes > 0)
      {
        answer.assign(fetch->data, fetch->numBytes);
      }


      /**
       * Retrieving the headers of the HTTP answer.
       **/
      HttpHeaders headers;

#if (__EMSCRIPTEN_major__ < 1 ||                                        \
     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) ||        \
     (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37))
#  warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API

      /**
       * HACK - If emscripten < 1.38.37, the fetch API does not
       * contain a way to retrieve the HTTP headers of the answer. We
       * make the assumption that the "Content-Type" header of the
       * response is the same as the "Accept" header of the
       * query. This is fixed thanks to the
       * "emscripten_fetch_get_response_headers()" function that was
       * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26.
       *
       * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h
       * https://github.com/emscripten-core/emscripten/pull/8486
       **/
      if (fetch->userData != NULL)
      {
        if (!context->GetExpectedContentType().empty())
        {
          headers["Content-Type"] = context->GetExpectedContentType();
        }
      }
#else
      {
        size_t size = emscripten_fetch_get_response_headers_length(fetch);

        std::string plainHeaders(size + 1, '\0');
        emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1);

        std::vector<std::string> tokens;
        Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n');

        for (size_t i = 0; i < tokens.size(); i++)
        {
          size_t p = tokens[i].find(':');
          if (p != std::string::npos)
          {
            std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p));
            std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1));
            headers[key] = value;
          }
        }
      }
#endif
      
      LOG(TRACE) << "About to call emscripten_fetch_close";
      emscripten_fetch_close(fetch);
      LOG(TRACE) << "Successfully called emscripten_fetch_close";

      /**
       * Secondly, use the retrieved data.
       * IMPORTANT NOTE: the receiver might be dead. This is prevented 
       * by the object responsible for zombie check, later on.
       **/
      try
      {
        if (context.get() == NULL)
        {
          LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)";
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
        }
        else
        {
          switch (context->GetCommand().GetType())
          {
            case IOracleCommand::Type_Http:
            {
              HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer);
              context->EmitMessage(message);
              break;
            }

            case IOracleCommand::Type_OrthancRestApi:
            {
              LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);";
              OrthancRestApiCommand::SuccessMessage message
                (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer);
              context->EmitMessage(message);
              break;
            }

            case IOracleCommand::Type_GetOrthancImage:
            {
              context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer
                (context->GetReceiver(), context->GetEmitter(), answer, headers);
              break;
            }

            case IOracleCommand::Type_GetOrthancWebViewerJpeg:
            {
              context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer
                (context->GetReceiver(), context->GetEmitter(), answer);
              break;
            }

            case IOracleCommand::Type_ParseDicomFromWado:
            {
#if ORTHANC_ENABLE_DCMTK == 1
              const ParseDicomFromWadoCommand& command =
                context->GetTypedCommand<ParseDicomFromWadoCommand>();
              
              size_t fileSize;
              std::unique_ptr<Orthanc::ParsedDicomFile> dicom
                (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));

              {
                ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true);
                context->EmitMessage(message);
              }

              context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize);
#else
              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
#endif
              break;
            }

            default:
              LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
                         << context->GetCommand().GetType();
              throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
          }
        }
      }
      catch (Orthanc::OrthancException& e)
      {
        LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What();

        {
          OracleCommandExceptionMessage message(context->GetCommand(), e);
          context->EmitMessage(message);
        }
      }
    }

    static void FailureCallback(emscripten_fetch_t *fetch)
    {
      std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));

#if 0
      {
        const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText);
        char message[kEmscriptenStatusTextSize + 1];
        memcpy(message, fetch->statusText, kEmscriptenStatusTextSize);
        message[kEmscriptenStatusTextSize] = 0;

        LOG(ERROR) << "Fetching " << fetch->url
                   << " failed, HTTP failure status code: " << fetch->status
                   << " | statusText = " << message
                   << " | numBytes = " << fetch->numBytes
                   << " | totalBytes = " << fetch->totalBytes
                   << " | readyState = " << fetch->readyState;
      }
#endif

      {
        OracleCommandExceptionMessage message
          (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol));
        context->EmitMessage(message);
      }
      
      /**
       * TODO - The following code leads to an infinite recursion, at
       * least with Firefox running on incognito mode => WHY?
       **/      
      emscripten_fetch_close(fetch); // Also free data on failure.
    }
  };
    


  class WebAssemblyOracle::FetchCommand : public boost::noncopyable
  {
  private:
    WebAssemblyOracle&             oracle_;
    boost::weak_ptr<IObserver>     receiver_;
    std::unique_ptr<IOracleCommand>  command_;
    Orthanc::HttpMethod            method_;
    std::string                    url_;
    std::string                    body_;
    HttpHeaders                    headers_;
    unsigned int                   timeout_;
    std::string                    expectedContentType_;
    bool                           hasCredentials_;
    std::string                    username_;
    std::string                    password_;

  public:
    FetchCommand(WebAssemblyOracle& oracle,
                 boost::weak_ptr<IObserver> receiver,
                 IOracleCommand* command) :
      oracle_(oracle),
      receiver_(receiver),
      command_(command),
      method_(Orthanc::HttpMethod_Get),
      timeout_(0),
      hasCredentials_(false)
    {
      if (command == NULL)
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
      }
    }

    void SetMethod(Orthanc::HttpMethod method)
    {
      method_ = method;
    }

    void SetUrl(const std::string& url)
    {
      url_ = url;
    }

    void SetBody(std::string& body /* will be swapped */)
    {
      body_.swap(body);
    }

    void AddHttpHeaders(const HttpHeaders& headers)
    {
      for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
      {
        headers_[it->first] = it->second;
      }
    }

    void SetTimeout(unsigned int timeout)
    {
      timeout_ = timeout;
    }

    void SetCredentials(const std::string& username,
                        const std::string& password)
    {
      hasCredentials_ = true;
      username_ = username;
      password_ = password;
    }

    void Execute()
    {
      if (command_.get() == NULL)
      {
        // Cannot call Execute() twice
        LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)";
        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
      }

      emscripten_fetch_attr_t attr;
      emscripten_fetch_attr_init(&attr);

      const char* method;
      
      switch (method_)
      {
        case Orthanc::HttpMethod_Get:
          method = "GET";
          break;

        case Orthanc::HttpMethod_Post:
          method = "POST";
          break;

        case Orthanc::HttpMethod_Delete:
          method = "DELETE";
          break;

        case Orthanc::HttpMethod_Put:
          method = "PUT";
          break;

        default:
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
      }

      strcpy(attr.requestMethod, method);

      attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
      attr.onsuccess = FetchContext::SuccessCallback;
      attr.onerror = FetchContext::FailureCallback;
      attr.timeoutMSecs = timeout_ * 1000;

      if (hasCredentials_)
      {
        attr.withCredentials = EM_TRUE;
        attr.userName = username_.c_str();
        attr.password = password_.c_str();
      }
      
      std::vector<const char*> headers;
      headers.reserve(2 * headers_.size() + 1);

      std::string expectedContentType;
        
      for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
      {
        std::string key;
        Orthanc::Toolbox::ToLowerCase(key, it->first);
          
        if (key == "accept")
        {
          expectedContentType = it->second;
        }

        if (key != "accept-encoding")  // Web browsers forbid the modification of this HTTP header
        {
          headers.push_back(it->first.c_str());
          headers.push_back(it->second.c_str());
        }
      }
        
      headers.push_back(NULL);  // Termination of the array of HTTP headers

      attr.requestHeaders = &headers[0];

      char* requestData = NULL;
      if (!body_.empty())
        requestData = reinterpret_cast<char*>(malloc(body_.size()));
        
      try 
      {
        if (!body_.empty())
        {
          memcpy(requestData, &(body_[0]), body_.size());
          attr.requestDataSize = body_.size();
          attr.requestData = requestData;
        }
        attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);

        // Must be the last call to prevent memory leak on error
        emscripten_fetch(&attr, url_.c_str());
      }        
      catch(...)
      {
        if(requestData != NULL)
          free(requestData);
        throw;
      }
    }
  };


  void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
                                        const std::string& uri) const
  {
    if (isLocalOrthanc_)
    {
      command.SetUrl(localOrthancRoot_ + uri);
    }
    else
    {
      command.SetUrl(remoteOrthanc_.GetUrl() + uri);
      command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
      
      if (!remoteOrthanc_.GetUsername().empty())
      {
        command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
      }
    }
  }
    

  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
                                  HttpCommand* command)
  {
    FetchCommand fetch(*this, receiver, command);
    
    fetch.SetMethod(command->GetMethod());
    fetch.SetUrl(command->GetUrl());
    fetch.AddHttpHeaders(command->GetHttpHeaders());
    fetch.SetTimeout(command->GetTimeout());
    
    if (command->GetMethod() == Orthanc::HttpMethod_Post ||
        command->GetMethod() == Orthanc::HttpMethod_Put)
    {
      std::string body;
      command->SwapBody(body);
      fetch.SetBody(body);
    }
    
    fetch.Execute();
  }
  

  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
                                  OrthancRestApiCommand* command)
  {
    try
    {
      //LOG(TRACE) << "*********** WebAssemblyOracle::Execute.";
      //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command;
      FetchCommand fetch(*this, receiver, command);

      fetch.SetMethod(command->GetMethod());
      SetOrthancUrl(fetch, command->GetUri());
      fetch.AddHttpHeaders(command->GetHttpHeaders());
      fetch.SetTimeout(command->GetTimeout());

      if (command->GetMethod() == Orthanc::HttpMethod_Post ||
        command->GetMethod() == Orthanc::HttpMethod_Put)
      {
        std::string body;
        command->SwapBody(body);
        fetch.SetBody(body);
      }

      fetch.Execute();
      //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute.";
    }
    catch (const Orthanc::OrthancException& e)
    {
      if (e.HasDetails())
      {
        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails();
      }
      else
      {
        LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What();
      }
      //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
      throw;
    }
    catch (const std::exception& e)
    {
      LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what();
//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
      throw;
    }
    catch (...)
    {
      LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute";
//       LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
      throw;
    }
  }
    
    
  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
                                  GetOrthancImageCommand* command)
  {
    FetchCommand fetch(*this, receiver, command);

    SetOrthancUrl(fetch, command->GetUri());
    fetch.AddHttpHeaders(command->GetHttpHeaders());
    fetch.SetTimeout(command->GetTimeout());
      
    fetch.Execute();
  }
    
    
  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
                                  GetOrthancWebViewerJpegCommand* command)
  {
    FetchCommand fetch(*this, receiver, command);

    SetOrthancUrl(fetch, command->GetUri());
    fetch.AddHttpHeaders(command->GetHttpHeaders());
    fetch.SetTimeout(command->GetTimeout());
      
    fetch.Execute();
  }


  void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
                                  ParseDicomFromWadoCommand* command)
  {
    std::unique_ptr<ParseDicomFromWadoCommand> protection(command);
    
#if ORTHANC_ENABLE_DCMTK == 1
    if (dicomCache_.get())
    {
      ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid());
      if (reader.IsValid() &&
          reader.HasPixelData())
      {
        // Reuse the DICOM file from the cache
        ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
                                         reader.GetFileSize(), reader.HasPixelData());
        EmitMessage(receiver, message);
        return;
      }
    }
#endif

    switch (command->GetRestCommand().GetType())
    {
      case IOracleCommand::Type_Http:
      {
        const HttpCommand& rest =
          dynamic_cast<const HttpCommand&>(protection->GetRestCommand());
        
        FetchCommand fetch(*this, receiver, protection.release());
    
        fetch.SetMethod(rest.GetMethod());
        fetch.SetUrl(rest.GetUrl());
        fetch.AddHttpHeaders(rest.GetHttpHeaders());
        fetch.SetTimeout(rest.GetTimeout());
    
        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
            rest.GetMethod() == Orthanc::HttpMethod_Put)
        {
          std::string body = rest.GetBody();
          fetch.SetBody(body);
        }
    
        fetch.Execute();
        break;
      }

      case IOracleCommand::Type_OrthancRestApi:
      {
        const OrthancRestApiCommand& rest =
          dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand());
        
        FetchCommand fetch(*this, receiver, protection.release());

        fetch.SetMethod(rest.GetMethod());
        SetOrthancUrl(fetch, rest.GetUri());
        fetch.AddHttpHeaders(rest.GetHttpHeaders());
        fetch.SetTimeout(rest.GetTimeout());

        if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
            rest.GetMethod() == Orthanc::HttpMethod_Put)
        {
          std::string body = rest.GetBody();
          fetch.SetBody(body);
        }

        fetch.Execute();
        break;
      }

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }
  }


  bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver,
                                   IOracleCommand* command)
  {
    LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
               << std::hex << receiver.get();
    
    std::unique_ptr<IOracleCommand> protection(command);

    if (command == NULL)
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
    }

    switch (command->GetType())
    {
      case IOracleCommand::Type_Http:
        Execute(receiver, dynamic_cast<HttpCommand*>(protection.release()));
        break;
        
      case IOracleCommand::Type_OrthancRestApi:
        Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
        break;
        
      case IOracleCommand::Type_GetOrthancImage:
        Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
        break;

      case IOracleCommand::Type_GetOrthancWebViewerJpeg:
        Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
        break;          
            
      case IOracleCommand::Type_Sleep:
      {
        unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
        emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
                               new TimeoutContext(*this, receiver, protection.release()));
        break;
      }
            
      case IOracleCommand::Type_ParseDicomFromWado:
#if ORTHANC_ENABLE_DCMTK == 1
        Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release()));
#else
          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
                                          "DCMTK must be enabled to parse DICOM files");
#endif
        break;
            
      default:
        LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
                   << command->GetType();
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }

    return true;
  }


  void WebAssemblyOracle::SetDicomCacheSize(size_t size)
  {
#if ORTHANC_ENABLE_DCMTK == 1
    if (size == 0)
    {
      dicomCache_.reset();
    }
    else
    {
      dicomCache_.reset(new ParsedDicomCache(size));
    }
#else
    LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
#endif
  }
}