diff OrthancServer/Plugins/Engine/OrthancPlugins.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Plugins/Engine/OrthancPlugins.cpp@e42f5445d20d
children 05b8fd21089c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Wed Jun 10 20:30:34 2020 +0200
@@ -0,0 +1,5256 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * 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 General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../../OrthancServer/PrecompiledHeadersServer.h"
+#include "OrthancPlugins.h"
+
+#if ORTHANC_ENABLE_PLUGINS != 1
+#error The plugin support is disabled
+#endif
+
+#if !defined(DCMTK_VERSION_NUMBER)
+#  error The macro DCMTK_VERSION_NUMBER must be defined
+#endif
+
+
+#include "../../Core/Compression/GzipCompressor.h"
+#include "../../Core/Compression/ZlibCompressor.h"
+#include "../../Core/DicomFormat/DicomArray.h"
+#include "../../Core/DicomParsing/DicomWebJsonVisitor.h"
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
+#include "../../Core/DicomParsing/ToDcmtkBridge.h"
+#include "../../Core/HttpServer/HttpToolbox.h"
+#include "../../Core/Images/Image.h"
+#include "../../Core/Images/ImageProcessing.h"
+#include "../../Core/Images/JpegReader.h"
+#include "../../Core/Images/JpegWriter.h"
+#include "../../Core/Images/PngReader.h"
+#include "../../Core/Images/PngWriter.h"
+#include "../../Core/Logging.h"
+#include "../../Core/MetricsRegistry.h"
+#include "../../Core/OrthancException.h"
+#include "../../Core/SerializationToolbox.h"
+#include "../../Core/Toolbox.h"
+#include "../../OrthancServer/OrthancConfiguration.h"
+#include "../../OrthancServer/OrthancFindRequestHandler.h"
+#include "../../OrthancServer/Search/HierarchicalMatcher.h"
+#include "../../OrthancServer/ServerContext.h"
+#include "../../OrthancServer/ServerToolbox.h"
+#include "PluginsEnumerations.h"
+#include "PluginsJob.h"
+
+#include <boost/regex.hpp>
+#include <dcmtk/dcmdata/dcdict.h>
+#include <dcmtk/dcmdata/dcdicent.h>
+
+#define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc API is necessary"
+
+
+namespace Orthanc
+{
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const void* data,
+                                 size_t size)
+  {
+    if (static_cast<uint32_t>(size) != size)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+    }
+
+    target.size = size;
+
+    if (size == 0)
+    {
+      target.data = NULL;
+    }
+    else
+    {
+      target.data = malloc(size);
+      if (target.data != NULL)
+      {
+        memcpy(target.data, data, size);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const std::string& str)
+  {
+    if (str.size() == 0)
+    {
+      target.size = 0;
+      target.data = NULL;
+    }
+    else
+    {
+      CopyToMemoryBuffer(target, str.c_str(), str.size());
+    }
+  }
+
+
+  static char* CopyString(const std::string& str)
+  {
+    if (static_cast<uint32_t>(str.size()) != str.size())
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+    }
+
+    char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
+    if (result == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    if (str.size() == 0)
+    {
+      result[0] = '\0';
+    }
+    else
+    {
+      memcpy(result, &str[0], str.size() + 1);
+    }
+
+    return result;
+  }
+
+
+  namespace
+  {
+    class PluginStorageArea : public IStorageArea
+    {
+    private:
+      _OrthancPluginRegisterStorageArea callbacks_;
+      PluginsErrorDictionary&  errorDictionary_;
+
+      void Free(void* buffer) const
+      {
+        if (buffer != NULL)
+        {
+          callbacks_.free(buffer);
+        }
+      }
+
+    public:
+      PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks,
+                        PluginsErrorDictionary&  errorDictionary) : 
+        callbacks_(callbacks),
+        errorDictionary_(errorDictionary)
+      {
+      }
+
+
+      virtual void Create(const std::string& uuid,
+                          const void* content, 
+                          size_t size,
+                          FileContentType type)
+      {
+        OrthancPluginErrorCode error = callbacks_.create
+          (uuid.c_str(), content, size, Plugins::Convert(type));
+
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
+        }
+      }
+
+
+      virtual void Read(std::string& content,
+                        const std::string& uuid,
+                        FileContentType type)
+      {
+        void* buffer = NULL;
+        int64_t size = 0;
+
+        OrthancPluginErrorCode error = callbacks_.read
+          (&buffer, &size, uuid.c_str(), Plugins::Convert(type));
+
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
+        }
+
+        try
+        {
+          content.resize(static_cast<size_t>(size));
+        }
+        catch (...)
+        {
+          Free(buffer);
+          throw OrthancException(ErrorCode_NotEnoughMemory);
+        }
+
+        if (size > 0)
+        {
+          memcpy(&content[0], buffer, static_cast<size_t>(size));
+        }
+
+        Free(buffer);
+      }
+
+
+      virtual void Remove(const std::string& uuid,
+                          FileContentType type) 
+      {
+        OrthancPluginErrorCode error = callbacks_.remove
+          (uuid.c_str(), Plugins::Convert(type));
+
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
+        }
+      }
+    };
+
+
+    class StorageAreaFactory : public boost::noncopyable
+    {
+    private:
+      SharedLibrary&   sharedLibrary_;
+      _OrthancPluginRegisterStorageArea  callbacks_;
+      PluginsErrorDictionary&  errorDictionary_;
+
+    public:
+      StorageAreaFactory(SharedLibrary& sharedLibrary,
+                         const _OrthancPluginRegisterStorageArea& callbacks,
+                         PluginsErrorDictionary&  errorDictionary) :
+        sharedLibrary_(sharedLibrary),
+        callbacks_(callbacks),
+        errorDictionary_(errorDictionary)
+      {
+      }
+
+      SharedLibrary&  GetSharedLibrary()
+      {
+        return sharedLibrary_;
+      }
+
+      IStorageArea* Create() const
+      {
+        return new PluginStorageArea(callbacks_, errorDictionary_);
+      }
+    };
+
+
+    class OrthancPeers : public boost::noncopyable
+    {
+    private:
+      std::vector<std::string>           names_;
+      std::vector<WebServiceParameters>  parameters_;
+
+      void CheckIndex(size_t i) const
+      {
+        assert(names_.size() == parameters_.size());
+        if (i >= names_.size())
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+      
+    public:
+      OrthancPeers()
+      {
+        OrthancConfiguration::ReaderLock lock;
+
+        std::set<std::string> peers;
+        lock.GetConfiguration().GetListOfOrthancPeers(peers);
+
+        names_.reserve(peers.size());
+        parameters_.reserve(peers.size());
+        
+        for (std::set<std::string>::const_iterator
+               it = peers.begin(); it != peers.end(); ++it)
+        {
+          WebServiceParameters peer;
+          if (lock.GetConfiguration().LookupOrthancPeer(peer, *it))
+          {
+            names_.push_back(*it);
+            parameters_.push_back(peer);
+          }
+        }
+      }
+
+      size_t GetPeersCount() const
+      {
+        return names_.size();
+      }
+
+      const std::string& GetPeerName(size_t i) const
+      {
+        CheckIndex(i);
+        return names_[i];
+      }
+
+      const WebServiceParameters& GetPeerParameters(size_t i) const
+      {
+        CheckIndex(i);
+        return parameters_[i];
+      }
+    };
+
+
+    class DicomWebBinaryFormatter : public DicomWebJsonVisitor::IBinaryFormatter
+    {
+    private:
+      OrthancPluginDicomWebBinaryCallback   oldCallback_;
+      OrthancPluginDicomWebBinaryCallback2  newCallback_;  // New in Orthanc 1.7.0
+      void*                                 newPayload_;   // New in Orthanc 1.7.0
+      DicomWebJsonVisitor::BinaryMode       currentMode_;
+      std::string                           currentBulkDataUri_;
+
+      static void Setter(OrthancPluginDicomWebNode*       node,
+                         OrthancPluginDicomWebBinaryMode  mode,
+                         const char*                      bulkDataUri)
+      {
+        DicomWebBinaryFormatter& that = *reinterpret_cast<DicomWebBinaryFormatter*>(node);
+
+        switch (mode)
+        {
+          case OrthancPluginDicomWebBinaryMode_Ignore:
+            that.currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
+            break;
+              
+          case OrthancPluginDicomWebBinaryMode_InlineBinary:
+            that.currentMode_ = DicomWebJsonVisitor::BinaryMode_InlineBinary;
+            break;
+              
+          case OrthancPluginDicomWebBinaryMode_BulkDataUri:
+            if (bulkDataUri == NULL)
+            {
+              throw OrthancException(ErrorCode_NullPointer);
+            }              
+            
+            that.currentBulkDataUri_ = bulkDataUri;
+            that.currentMode_ = DicomWebJsonVisitor::BinaryMode_BulkDataUri;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+      
+    public:
+      DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback callback) :
+        oldCallback_(callback),
+        newCallback_(NULL),
+        newPayload_(NULL)
+      {
+      }
+      
+      DicomWebBinaryFormatter(OrthancPluginDicomWebBinaryCallback2 callback,
+                              void* payload) :
+        oldCallback_(NULL),
+        newCallback_(callback),
+        newPayload_(payload)
+      {
+      }
+      
+      virtual DicomWebJsonVisitor::BinaryMode Format(std::string& bulkDataUri,
+                                                     const std::vector<DicomTag>& parentTags,
+                                                     const std::vector<size_t>& parentIndexes,
+                                                     const DicomTag& tag,
+                                                     ValueRepresentation vr)
+      {
+        if (oldCallback_ == NULL &&
+            newCallback_ == NULL)
+        {
+          return DicomWebJsonVisitor::BinaryMode_InlineBinary;
+        }
+        else
+        {
+          assert(parentTags.size() == parentIndexes.size());
+          std::vector<uint16_t> groups(parentTags.size());
+          std::vector<uint16_t> elements(parentTags.size());
+          std::vector<uint32_t> indexes(parentTags.size());
+
+          for (size_t i = 0; i < parentTags.size(); i++)
+          {
+            groups[i] = parentTags[i].GetGroup();
+            elements[i] = parentTags[i].GetElement();
+            indexes[i] = static_cast<uint32_t>(parentIndexes[i]);
+          }
+          bool empty = parentTags.empty();
+
+          currentMode_ = DicomWebJsonVisitor::BinaryMode_Ignore;
+
+          if (oldCallback_ != NULL)
+          {
+            oldCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
+                         DicomWebBinaryFormatter::Setter,
+                         static_cast<uint32_t>(parentTags.size()),
+                         (empty ? NULL : &groups[0]),
+                         (empty ? NULL : &elements[0]),
+                         (empty ? NULL : &indexes[0]),
+                         tag.GetGroup(),
+                         tag.GetElement(),
+                         Plugins::Convert(vr));
+          }
+          else
+          {
+            assert(newCallback_ != NULL);
+            newCallback_(reinterpret_cast<OrthancPluginDicomWebNode*>(this),
+                         DicomWebBinaryFormatter::Setter,
+                         static_cast<uint32_t>(parentTags.size()),
+                         (empty ? NULL : &groups[0]),
+                         (empty ? NULL : &elements[0]),
+                         (empty ? NULL : &indexes[0]),
+                         tag.GetGroup(),
+                         tag.GetElement(),
+                         Plugins::Convert(vr),
+                         newPayload_);
+          }          
+
+          bulkDataUri = currentBulkDataUri_;          
+          return currentMode_;
+        }
+      }
+
+      void Apply(char** target,
+                 bool isJson,
+                 ParsedDicomFile& dicom)
+      {
+        DicomWebJsonVisitor visitor;
+        visitor.SetFormatter(*this);
+
+        dicom.Apply(visitor);
+
+        std::string s;
+
+        if (isJson)
+        {
+          s = visitor.GetResult().toStyledString();
+        }
+        else
+        {
+          visitor.FormatXml(s);
+        }
+
+        *target = CopyString(s);
+      }
+
+  
+      void Apply(char** target,
+                 bool isJson,
+                 const void* dicom,
+                 size_t dicomSize) 
+      {
+        ParsedDicomFile parsed(dicom, dicomSize);
+        Apply(target, isJson, parsed);
+      }
+    };
+  }
+
+
+  class OrthancPlugins::PImpl
+  {
+  private:
+    boost::mutex   contextMutex_;
+    ServerContext* context_;
+    
+  public:
+    class PluginHttpOutput : public boost::noncopyable
+    {
+    private:
+      enum MultipartState
+      {
+        MultipartState_None,
+        MultipartState_FirstPart,
+        MultipartState_SecondPart,
+        MultipartState_NextParts
+      };
+
+      HttpOutput&                 output_;
+      std::unique_ptr<std::string>  errorDetails_;
+      bool                        logDetails_;
+      MultipartState              multipartState_;
+      std::string                 multipartSubType_;
+      std::string                 multipartContentType_;
+      std::string                 multipartFirstPart_;
+      std::map<std::string, std::string>  multipartFirstHeaders_;
+
+    public:
+      PluginHttpOutput(HttpOutput& output) :
+        output_(output),
+        logDetails_(false),
+        multipartState_(MultipartState_None)
+      {
+      }
+
+      HttpOutput& GetOutput()
+      {
+        if (multipartState_ == MultipartState_None)
+        {
+          return output_;
+        }
+        else
+        {
+          // Must use "SendMultipartItem()" on multipart streams
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+      }
+
+      void SetErrorDetails(const std::string& details,
+                           bool logDetails)
+      {
+        errorDetails_.reset(new std::string(details));
+        logDetails_ = logDetails;
+      }
+
+      bool HasErrorDetails() const
+      {
+        return errorDetails_.get() != NULL;
+      }
+
+      bool IsLogDetails() const
+      {
+        return logDetails_;
+      }
+
+      const std::string& GetErrorDetails() const
+      {
+        if (errorDetails_.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          return *errorDetails_;
+        }
+      }
+
+      void StartMultipart(const char* subType,
+                          const char* contentType)
+      {
+        if (multipartState_ != MultipartState_None)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          multipartState_ = MultipartState_FirstPart;
+          multipartSubType_ = subType;
+          multipartContentType_ = contentType;
+        }
+      }
+
+      void SendMultipartItem(const void* data,
+                             size_t size,
+                             const std::map<std::string, std::string>& headers)
+      {
+        if (size != 0 && data == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        switch (multipartState_)
+        {
+          case MultipartState_None:
+            // Must call "StartMultipart()" before
+            throw OrthancException(ErrorCode_BadSequenceOfCalls);
+
+          case MultipartState_FirstPart:
+            multipartFirstPart_.assign(reinterpret_cast<const char*>(data), size);
+            multipartFirstHeaders_ = headers;
+            multipartState_ = MultipartState_SecondPart;
+            break;
+
+          case MultipartState_SecondPart:
+            // Start an actual stream for chunked transfer as soon as
+            // there are more than 2 elements in the multipart stream
+            output_.StartMultipart(multipartSubType_, multipartContentType_);
+            output_.SendMultipartItem(multipartFirstPart_.c_str(), multipartFirstPart_.size(), 
+                                      multipartFirstHeaders_);
+            multipartFirstPart_.clear();  // Release memory
+
+            output_.SendMultipartItem(data, size, headers);
+            multipartState_ = MultipartState_NextParts;
+            break;
+
+          case MultipartState_NextParts:
+            output_.SendMultipartItem(data, size, headers);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+
+      void Close(OrthancPluginErrorCode error,
+                 PluginsErrorDictionary& dictionary)
+      {
+        if (error == OrthancPluginErrorCode_Success)
+        {
+          switch (multipartState_)
+          {
+            case MultipartState_None:
+              assert(!output_.IsWritingMultipart());
+              break;
+
+            case MultipartState_FirstPart:   // Multipart started, but no part was sent
+            case MultipartState_SecondPart:  // Multipart started, first part is pending
+            {
+              assert(!output_.IsWritingMultipart());
+              std::vector<const void*> parts;
+              std::vector<size_t> sizes;
+              std::vector<const std::map<std::string, std::string>*> headers;
+
+              if (multipartState_ == MultipartState_SecondPart)
+              {
+                parts.push_back(multipartFirstPart_.c_str());
+                sizes.push_back(multipartFirstPart_.size());
+                headers.push_back(&multipartFirstHeaders_);
+              }
+
+              output_.AnswerMultipartWithoutChunkedTransfer(multipartSubType_, multipartContentType_,
+                                                            parts, sizes, headers);
+              break;
+            }
+
+            case MultipartState_NextParts:
+              assert(output_.IsWritingMultipart());
+              output_.CloseMultipart();
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+        }
+        else
+        {
+          dictionary.LogError(error, false);
+
+          if (HasErrorDetails())
+          {
+            throw OrthancException(static_cast<ErrorCode>(error),
+                                   GetErrorDetails(),
+                                   IsLogDetails());
+          }
+          else
+          {
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+      }
+    };
+
+    
+    class RestCallback : public boost::noncopyable
+    {
+    private:
+      boost::regex              regex_;
+      OrthancPluginRestCallback callback_;
+      bool                      lock_;
+
+      OrthancPluginErrorCode InvokeInternal(PluginHttpOutput& output,
+                                            const std::string& flatUri,
+                                            const OrthancPluginHttpRequest& request)
+      {
+        return callback_(reinterpret_cast<OrthancPluginRestOutput*>(&output), 
+                         flatUri.c_str(), 
+                         &request);
+      }
+
+    public:
+      RestCallback(const char* regex,
+                   OrthancPluginRestCallback callback,
+                   bool lockRestCallbacks) :
+        regex_(regex),
+        callback_(callback),
+        lock_(lockRestCallbacks)
+      {
+      }
+
+      const boost::regex& GetRegularExpression() const
+      {
+        return regex_;
+      }
+
+      OrthancPluginErrorCode Invoke(boost::recursive_mutex& restCallbackMutex,
+                                    PluginHttpOutput& output,
+                                    const std::string& flatUri,
+                                    const OrthancPluginHttpRequest& request)
+      {
+        if (lock_)
+        {
+          boost::recursive_mutex::scoped_lock lock(restCallbackMutex);
+          return InvokeInternal(output, flatUri, request);
+        }
+        else
+        {
+          return InvokeInternal(output, flatUri, request);
+        }
+      }
+    };
+
+
+    class ChunkedRestCallback : public boost::noncopyable
+    {
+    private:
+      _OrthancPluginChunkedRestCallback parameters_;
+      boost::regex                      regex_;
+
+    public:
+      ChunkedRestCallback(_OrthancPluginChunkedRestCallback parameters) :
+        parameters_(parameters),
+        regex_(parameters.pathRegularExpression)
+      {
+      }
+
+      const boost::regex& GetRegularExpression() const
+      {
+        return regex_;
+      }
+
+      const _OrthancPluginChunkedRestCallback& GetParameters() const
+      {
+        return parameters_;
+      }
+    };
+
+
+
+    class StorageCommitmentScp : public IStorageCommitmentFactory
+    {
+    private:
+      class Handler : public IStorageCommitmentFactory::ILookupHandler
+      {
+      private:
+        _OrthancPluginRegisterStorageCommitmentScpCallback  parameters_;
+        void*    handler_;
+
+      public:
+        Handler(_OrthancPluginRegisterStorageCommitmentScpCallback  parameters,
+                void* handler) :
+          parameters_(parameters),
+          handler_(handler)
+        {
+          if (handler == NULL)
+          {
+            throw OrthancException(ErrorCode_NullPointer);
+          }
+        }
+
+        virtual ~Handler()
+        {
+          assert(handler_ != NULL);
+          parameters_.destructor(handler_);
+          handler_ = NULL;
+        }
+
+        virtual StorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                      const std::string& sopInstanceUid)
+        {
+          assert(handler_ != NULL);
+          OrthancPluginStorageCommitmentFailureReason reason =
+            OrthancPluginStorageCommitmentFailureReason_Success;
+          OrthancPluginErrorCode error = parameters_.lookup(
+            &reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str());
+          if (error == OrthancPluginErrorCode_Success)
+          {
+            return Plugins::Convert(reason);
+          }
+          else
+          {
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+      };
+      
+      _OrthancPluginRegisterStorageCommitmentScpCallback  parameters_;
+
+    public:
+      StorageCommitmentScp(_OrthancPluginRegisterStorageCommitmentScpCallback parameters) :
+        parameters_(parameters)
+      {
+      }
+
+      virtual ILookupHandler* CreateStorageCommitment(
+        const std::string& jobId,
+        const std::string& transactionUid,
+        const std::vector<std::string>& sopClassUids,
+        const std::vector<std::string>& sopInstanceUids,
+        const std::string& remoteAet,
+        const std::string& calledAet) ORTHANC_OVERRIDE
+      {
+        const size_t n = sopClassUids.size();
+        
+        if (sopInstanceUids.size() != n)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+        
+        std::vector<const char*> a, b;
+        a.resize(n);
+        b.resize(n);
+
+        for (size_t i = 0; i < n; i++)
+        {
+          a[i] = sopClassUids[i].c_str();
+          b[i] = sopInstanceUids[i].c_str();
+        }
+
+        void* handler = NULL;
+        OrthancPluginErrorCode error = parameters_.factory(
+          &handler, jobId.c_str(), transactionUid.c_str(),
+          a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n),
+          remoteAet.c_str(), calledAet.c_str());
+
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          throw OrthancException(static_cast<ErrorCode>(error));          
+        }
+        else if (handler == NULL)
+        {
+          // This plugin won't handle this storage commitment request
+          return NULL;
+        }
+        else
+        {
+          return new Handler(parameters_, handler);
+        }
+      }
+    };
+
+
+    class ServerContextLock
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      ServerContext* context_;
+
+    public:
+      ServerContextLock(PImpl& that) : 
+        lock_(that.contextMutex_),
+        context_(that.context_)
+      {
+        if (context_ == NULL)
+        {
+          throw OrthancException(ErrorCode_DatabaseNotInitialized);
+        }
+      }
+
+      ServerContext& GetContext()
+      {
+        assert(context_ != NULL);
+        return *context_;
+      }
+    };
+
+
+    void SetServerContext(ServerContext* context)
+    {
+      boost::mutex::scoped_lock lock(contextMutex_);
+      context_ = context;
+    }
+
+
+    typedef std::pair<std::string, _OrthancPluginProperty>  Property;
+    typedef std::list<RestCallback*>  RestCallbacks;
+    typedef std::list<ChunkedRestCallback*>  ChunkedRestCallbacks;
+    typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
+    typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
+    typedef std::list<OrthancPluginIncomingHttpRequestFilter>  IncomingHttpRequestFilters;
+    typedef std::list<OrthancPluginIncomingHttpRequestFilter2>  IncomingHttpRequestFilters2;
+    typedef std::list<OrthancPluginIncomingDicomInstanceFilter>  IncomingDicomInstanceFilters;
+    typedef std::list<OrthancPluginDecodeImageCallback>  DecodeImageCallbacks;
+    typedef std::list<OrthancPluginTranscoderCallback>  TranscoderCallbacks;
+    typedef std::list<OrthancPluginJobsUnserializer>  JobsUnserializers;
+    typedef std::list<OrthancPluginRefreshMetricsCallback>  RefreshMetricsCallbacks;
+    typedef std::list<StorageCommitmentScp*>  StorageCommitmentScpCallbacks;
+    typedef std::map<Property, std::string>  Properties;
+
+    PluginsManager manager_;
+
+    RestCallbacks restCallbacks_;
+    ChunkedRestCallbacks chunkedRestCallbacks_;
+    OnStoredCallbacks  onStoredCallbacks_;
+    OnChangeCallbacks  onChangeCallbacks_;
+    OrthancPluginFindCallback  findCallback_;
+    OrthancPluginWorklistCallback  worklistCallback_;
+    DecodeImageCallbacks  decodeImageCallbacks_;
+    TranscoderCallbacks  transcoderCallbacks_;
+    JobsUnserializers  jobsUnserializers_;
+    _OrthancPluginMoveCallback moveCallbacks_;
+    IncomingHttpRequestFilters  incomingHttpRequestFilters_;
+    IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
+    IncomingDicomInstanceFilters  incomingDicomInstanceFilters_;
+    RefreshMetricsCallbacks refreshMetricsCallbacks_;
+    StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
+    std::unique_ptr<StorageAreaFactory>  storageArea_;
+
+    boost::recursive_mutex restCallbackMutex_;
+    boost::recursive_mutex storedCallbackMutex_;
+    boost::recursive_mutex changeCallbackMutex_;
+    boost::mutex findCallbackMutex_;
+    boost::mutex worklistCallbackMutex_;
+    boost::shared_mutex decoderTranscoderMutex_;  // Changed from "boost::mutex" in Orthanc 1.7.0
+    boost::mutex jobsUnserializersMutex_;
+    boost::mutex refreshMetricsMutex_;
+    boost::mutex storageCommitmentScpMutex_;
+    boost::recursive_mutex invokeServiceMutex_;
+
+    Properties properties_;
+    int argc_;
+    char** argv_;
+    std::unique_ptr<OrthancPluginDatabase>  database_;
+    PluginsErrorDictionary  dictionary_;
+
+    PImpl() : 
+      context_(NULL), 
+      findCallback_(NULL),
+      worklistCallback_(NULL),
+      argc_(1),
+      argv_(NULL)
+    {
+      memset(&moveCallbacks_, 0, sizeof(moveCallbacks_));
+    }
+  };
+
+
+  
+  class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler
+  {
+  private:
+    OrthancPlugins&  that_;
+    std::unique_ptr<HierarchicalMatcher> matcher_;
+    std::unique_ptr<ParsedDicomFile>     filtered_;
+    ParsedDicomFile* currentQuery_;
+
+    void Reset()
+    {
+      matcher_.reset();
+      filtered_.reset();
+      currentQuery_ = NULL;
+    }
+
+  public:
+    WorklistHandler(OrthancPlugins& that) : that_(that)
+    {
+      Reset();
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet,
+                        ModalityManufacturer manufacturer)
+    {
+      static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter";
+
+      {
+        PImpl::ServerContextLock lock(*that_.pimpl_);
+        LuaScripting::Lock lua(lock.GetContext().GetLuaScripting());
+
+        if (!lua.GetLua().IsExistingFunction(LUA_CALLBACK))
+        {
+          currentQuery_ = &query;
+        }
+        else
+        {
+          Json::Value source, origin;
+          query.DatasetToJson(source, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+
+          OrthancFindRequestHandler::FormatOrigin
+            (origin, remoteIp, remoteAet, calledAet, manufacturer);
+
+          LuaFunctionCall call(lua.GetLua(), LUA_CALLBACK);
+          call.PushJson(source);
+          call.PushJson(origin);
+
+          Json::Value target;
+          call.ExecuteToJson(target, true);
+          
+          filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None,
+                                                          "" /* no private creator */));
+          currentQuery_ = filtered_.get();
+        }
+      }
+      
+      matcher_.reset(new HierarchicalMatcher(*currentQuery_));
+
+      {
+        boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_);
+
+        if (that_.pimpl_->worklistCallback_)
+        {
+          OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_
+            (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers),
+             reinterpret_cast<const OrthancPluginWorklistQuery*>(this),
+             remoteAet.c_str(),
+             calledAet.c_str());
+
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            Reset();
+            that_.GetErrorDictionary().LogError(error, true);
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+
+        Reset();
+      }
+    }
+
+    void GetDicomQuery(OrthancPluginMemoryBuffer& target) const
+    {
+      if (currentQuery_ == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      std::string dicom;
+      currentQuery_->SaveToMemoryBuffer(dicom);
+      CopyToMemoryBuffer(target, dicom.c_str(), dicom.size());
+    }
+
+    bool IsMatch(const void* dicom,
+                 size_t size) const
+    {
+      if (matcher_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      ParsedDicomFile f(dicom, size);
+      return matcher_->Match(f);
+    }
+
+    void AddAnswer(OrthancPluginWorklistAnswers* answers,
+                   const void* dicom,
+                   size_t size) const
+    {
+      if (matcher_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      ParsedDicomFile f(dicom, size);
+      std::unique_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
+      reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
+    }
+  };
+
+  
+  class OrthancPlugins::FindHandler : public IFindRequestHandler
+  {
+  private:
+    OrthancPlugins&            that_;
+    std::unique_ptr<DicomArray>  currentQuery_;
+
+    void Reset()
+    {
+      currentQuery_.reset(NULL);
+    }
+
+  public:
+    FindHandler(OrthancPlugins& that) : that_(that)
+    {
+      Reset();
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        const DicomMap& input,
+                        const std::list<DicomTag>& sequencesToReturn,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet,
+                        ModalityManufacturer manufacturer)
+    {
+      DicomMap tmp;
+      tmp.Assign(input);
+
+      for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); 
+           it != sequencesToReturn.end(); ++it)
+      {
+        if (!input.HasTag(*it))
+        {
+          tmp.SetValue(*it, "", false);
+        }
+      }      
+
+      {
+        boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_);
+        currentQuery_.reset(new DicomArray(tmp));
+
+        if (that_.pimpl_->findCallback_)
+        {
+          OrthancPluginErrorCode error = that_.pimpl_->findCallback_
+            (reinterpret_cast<OrthancPluginFindAnswers*>(&answers),
+             reinterpret_cast<const OrthancPluginFindQuery*>(this),
+             remoteAet.c_str(),
+             calledAet.c_str());
+
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            Reset();
+            that_.GetErrorDictionary().LogError(error, true);
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+
+        Reset();
+      }
+    }
+
+    void Invoke(_OrthancPluginService service,
+                const _OrthancPluginFindOperation& operation) const
+    {
+      if (currentQuery_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      switch (service)
+      {
+        case _OrthancPluginService_GetFindQuerySize:
+          *operation.resultUint32 = currentQuery_->GetSize();
+          break;
+
+        case _OrthancPluginService_GetFindQueryTag:
+        {
+          const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag();
+          *operation.resultGroup = tag.GetGroup();
+          *operation.resultElement = tag.GetElement();
+          break;
+        }
+
+        case _OrthancPluginService_GetFindQueryTagName:
+        {
+          const DicomElement& element = currentQuery_->GetElement(operation.index);
+          *operation.resultString = CopyString(FromDcmtkBridge::GetTagName(element));
+          break;
+        }
+
+        case _OrthancPluginService_GetFindQueryValue:
+        {
+          *operation.resultString = CopyString(currentQuery_->GetElement(operation.index).GetValue().GetContent());
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  };
+  
+
+
+  class OrthancPlugins::MoveHandler : public IMoveRequestHandler
+  {
+  private:
+    class Driver : public IMoveRequestIterator
+    {
+    private:
+      void*                   driver_;
+      unsigned int            count_;
+      unsigned int            pos_;
+      OrthancPluginApplyMove  apply_;
+      OrthancPluginFreeMove   free_;
+
+    public:
+      Driver(void* driver,
+             unsigned int count,
+             OrthancPluginApplyMove apply,
+             OrthancPluginFreeMove free) :
+        driver_(driver),
+        count_(count),
+        pos_(0),
+        apply_(apply),
+        free_(free)
+      {
+        if (driver_ == NULL)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+      }
+
+      virtual ~Driver()
+      {
+        if (driver_ != NULL)
+        {
+          free_(driver_);
+          driver_ = NULL;
+        }
+      }
+
+      virtual unsigned int GetSubOperationCount() const
+      {
+        return count_;
+      }
+
+      virtual Status DoNext()
+      {
+        if (pos_ >= count_)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          OrthancPluginErrorCode error = apply_(driver_);
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            LOG(ERROR) << "Error while doing C-Move from plugin: "
+                       << EnumerationToString(static_cast<ErrorCode>(error));
+            return Status_Failure;
+          }
+          else
+          {
+            pos_++;
+            return Status_Success;
+          }
+        }
+      }
+    };
+
+
+    _OrthancPluginMoveCallback  params_;
+
+
+    static std::string ReadTag(const DicomMap& input,
+                               const DicomTag& tag)
+    {
+      const DicomValue* value = input.TestAndGetValue(tag);
+      if (value != NULL &&
+          !value->IsBinary() &&
+          !value->IsNull())
+      {
+        return value->GetContent();
+      }
+      else
+      {
+        return std::string();
+      }
+    }
+                        
+
+
+  public:
+    MoveHandler(OrthancPlugins& that)
+    {
+      boost::recursive_mutex::scoped_lock lock(that.pimpl_->invokeServiceMutex_);
+      params_ = that.pimpl_->moveCallbacks_;
+      
+      if (params_.callback == NULL ||
+          params_.getMoveSize == NULL ||
+          params_.applyMove == NULL ||
+          params_.freeMove == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+
+    virtual IMoveRequestIterator* Handle(const std::string& targetAet,
+                                         const DicomMap& input,
+                                         const std::string& originatorIp,
+                                         const std::string& originatorAet,
+                                         const std::string& calledAet,
+                                         uint16_t originatorId)
+    {
+      std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+      std::string patientId = ReadTag(input, DICOM_TAG_PATIENT_ID);
+      std::string accessionNumber = ReadTag(input, DICOM_TAG_ACCESSION_NUMBER);
+      std::string studyInstanceUid = ReadTag(input, DICOM_TAG_STUDY_INSTANCE_UID);
+      std::string seriesInstanceUid = ReadTag(input, DICOM_TAG_SERIES_INSTANCE_UID);
+      std::string sopInstanceUid = ReadTag(input, DICOM_TAG_SOP_INSTANCE_UID);
+
+      OrthancPluginResourceType level = OrthancPluginResourceType_None;
+
+      if (!levelString.empty())
+      {
+        level = Plugins::Convert(StringToResourceType(levelString.c_str()));
+      }
+
+      void* driver = params_.callback(level,
+                                      patientId.empty() ? NULL : patientId.c_str(),
+                                      accessionNumber.empty() ? NULL : accessionNumber.c_str(),
+                                      studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(),
+                                      seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(),
+                                      sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(),
+                                      originatorAet.c_str(),
+                                      calledAet.c_str(),
+                                      targetAet.c_str(),
+                                      originatorId);
+
+      if (driver == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin,
+                               "Plugin cannot create a driver for an incoming C-MOVE request");
+      }
+
+      unsigned int size = params_.getMoveSize(driver);
+
+      return new Driver(driver, size, params_.applyMove, params_.freeMove);
+    }
+  };
+
+
+
+  class OrthancPlugins::HttpClientChunkedRequest : public HttpClient::IRequestBody
+  {
+  private:
+    const _OrthancPluginChunkedHttpClient&  params_;
+    PluginsErrorDictionary&                 errorDictionary_;
+
+  public:
+    HttpClientChunkedRequest(const _OrthancPluginChunkedHttpClient& params,
+                             PluginsErrorDictionary&  errorDictionary) :
+      params_(params),
+      errorDictionary_(errorDictionary)
+    {
+    }
+
+    virtual bool ReadNextChunk(std::string& chunk)
+    {
+      if (params_.requestIsDone(params_.request))
+      {
+        return false;
+      }
+      else
+      {
+        size_t size = params_.requestChunkSize(params_.request);
+
+        chunk.resize(size);
+        
+        if (size != 0)
+        {
+          const void* data = params_.requestChunkData(params_.request);
+          memcpy(&chunk[0], data, size);
+        }
+
+        OrthancPluginErrorCode error = params_.requestNext(params_.request);
+        
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
+        }
+        else
+        {
+          return true;
+        }
+      }
+    }
+  };
+
+
+  class OrthancPlugins::HttpClientChunkedAnswer : public HttpClient::IAnswer
+  {
+  private:
+    const _OrthancPluginChunkedHttpClient&  params_;
+    PluginsErrorDictionary&                 errorDictionary_;
+
+  public:
+    HttpClientChunkedAnswer(const _OrthancPluginChunkedHttpClient& params,
+                            PluginsErrorDictionary&  errorDictionary) :
+      params_(params),
+      errorDictionary_(errorDictionary)
+    {
+    }
+
+    virtual void AddHeader(const std::string& key,
+                           const std::string& value)
+    {
+      OrthancPluginErrorCode error = params_.answerAddHeader(params_.answer, key.c_str(), value.c_str());
+        
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        errorDictionary_.LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+      
+    virtual void AddChunk(const void* data,
+                          size_t size)
+    {
+      OrthancPluginErrorCode error = params_.answerAddChunk(params_.answer, data, size);
+        
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        errorDictionary_.LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+  };
+
+
+  OrthancPlugins::OrthancPlugins()
+  {
+    /* Sanity check of the compiler */
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin) ||
+        sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
+        sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii) ||
+        static_cast<int>(OrthancPluginCreateDicomFlags_DecodeDataUriScheme) != static_cast<int>(DicomFromJsonFlags_DecodeDataUriScheme) ||
+        static_cast<int>(OrthancPluginCreateDicomFlags_GenerateIdentifiers) != static_cast<int>(DicomFromJsonFlags_GenerateIdentifiers))
+
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    pimpl_.reset(new PImpl());
+    pimpl_->manager_.RegisterServiceProvider(*this);
+  }
+
+  
+  void OrthancPlugins::SetServerContext(ServerContext& context)
+  {
+    pimpl_->SetServerContext(&context);
+  }
+
+
+  void OrthancPlugins::ResetServerContext()
+  {
+    pimpl_->SetServerContext(NULL);
+  }
+
+  
+  OrthancPlugins::~OrthancPlugins()
+  {
+    for (PImpl::RestCallbacks::iterator it = pimpl_->restCallbacks_.begin(); 
+         it != pimpl_->restCallbacks_.end(); ++it)
+    {
+      delete *it;
+    }
+
+    for (PImpl::ChunkedRestCallbacks::iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+         it != pimpl_->chunkedRestCallbacks_.end(); ++it)
+    {
+      delete *it;
+    }
+
+    for (PImpl::StorageCommitmentScpCallbacks::iterator
+           it = pimpl_->storageCommitmentScpCallbacks_.begin(); 
+         it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
+    {
+      delete *it;
+    } 
+  }
+
+
+  static void ArgumentsToPlugin(std::vector<const char*>& keys,
+                                std::vector<const char*>& values,
+                                const IHttpHandler::Arguments& arguments)
+  {
+    keys.resize(arguments.size());
+    values.resize(arguments.size());
+
+    size_t pos = 0;
+    for (IHttpHandler::Arguments::const_iterator 
+           it = arguments.begin(); it != arguments.end(); ++it)
+    {
+      keys[pos] = it->first.c_str();
+      values[pos] = it->second.c_str();
+      pos++;
+    }
+  }
+
+
+  static void ArgumentsToPlugin(std::vector<const char*>& keys,
+                                std::vector<const char*>& values,
+                                const IHttpHandler::GetArguments& arguments)
+  {
+    keys.resize(arguments.size());
+    values.resize(arguments.size());
+
+    for (size_t i = 0; i < arguments.size(); i++)
+    {
+      keys[i] = arguments[i].first.c_str();
+      values[i] = arguments[i].second.c_str();
+    }
+  }
+
+
+  namespace
+  {
+    class RestCallbackMatcher : public boost::noncopyable
+    {
+    private:
+      std::string               flatUri_;
+      std::vector<std::string>  groups_;
+      std::vector<const char*>  cgroups_;
+      
+    public:
+      RestCallbackMatcher(const UriComponents& uri) :
+        flatUri_(Toolbox::FlattenUri(uri))
+      {
+      }
+
+      bool IsMatch(const boost::regex& re)
+      {
+        // Check whether the regular expression associated to this
+        // callback matches the URI
+        boost::cmatch what;
+
+        if (boost::regex_match(flatUri_.c_str(), what, re))
+        {
+          // Extract the value of the free parameters of the regular expression
+          if (what.size() > 1)
+          {
+            groups_.resize(what.size() - 1);
+            cgroups_.resize(what.size() - 1);
+            for (size_t i = 1; i < what.size(); i++)
+            {
+              groups_[i - 1] = what[i];
+              cgroups_[i - 1] = groups_[i - 1].c_str();
+            }
+          }
+
+          return true;
+        }
+        else
+        {
+          // Not a match
+          return false;
+        }
+      }
+
+      uint32_t GetGroupsCount() const
+      {
+        return cgroups_.size();
+      }
+
+      const char* const* GetGroups() const
+      {
+        return cgroups_.empty() ? NULL : &cgroups_[0];
+      }
+
+      const std::string& GetFlatUri() const
+      {
+        return flatUri_;
+      }
+    };
+
+
+    // WARNING - The lifetime of this internal object must be smaller
+    // than "matcher", "headers" and "getArguments" objects
+    class HttpRequestConverter
+    {
+    private:
+      std::vector<const char*>    getKeys_;
+      std::vector<const char*>    getValues_;
+      std::vector<const char*>    headersKeys_;
+      std::vector<const char*>    headersValues_;
+      OrthancPluginHttpRequest    converted_;
+
+    public:
+      HttpRequestConverter(const RestCallbackMatcher& matcher,
+                           HttpMethod method,
+                           const IHttpHandler::Arguments& headers)
+      {
+        memset(&converted_, 0, sizeof(OrthancPluginHttpRequest));
+
+        ArgumentsToPlugin(headersKeys_, headersValues_, headers);
+        assert(headersKeys_.size() == headersValues_.size());
+
+        switch (method)
+        {
+          case HttpMethod_Get:
+            converted_.method = OrthancPluginHttpMethod_Get;
+            break;
+
+          case HttpMethod_Post:
+            converted_.method = OrthancPluginHttpMethod_Post;
+            break;
+
+          case HttpMethod_Delete:
+            converted_.method = OrthancPluginHttpMethod_Delete;
+            break;
+
+          case HttpMethod_Put:
+            converted_.method = OrthancPluginHttpMethod_Put;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        converted_.groups = matcher.GetGroups();
+        converted_.groupsCount = matcher.GetGroupsCount();
+        converted_.getCount = 0;
+        converted_.getKeys = NULL;
+        converted_.getValues = NULL;
+        converted_.body = NULL;
+        converted_.bodySize = 0;
+        converted_.headersCount = headers.size();
+       
+        if (headers.size() > 0)
+        {
+          converted_.headersKeys = &headersKeys_[0];
+          converted_.headersValues = &headersValues_[0];
+        }
+      }
+
+      void SetGetArguments(const IHttpHandler::GetArguments& getArguments)
+      {
+        ArgumentsToPlugin(getKeys_, getValues_, getArguments);
+        assert(getKeys_.size() == getValues_.size());
+
+        converted_.getCount = getArguments.size();
+
+        if (getArguments.size() > 0)
+        {
+          converted_.getKeys = &getKeys_[0];
+          converted_.getValues = &getValues_[0];
+        }
+      }
+
+      OrthancPluginHttpRequest& GetRequest()
+      {
+        return converted_;
+      }
+    };
+  }
+
+
+  static std::string GetAllowedMethods(_OrthancPluginChunkedRestCallback parameters)
+  {
+    std::string s;
+
+    if (parameters.getHandler != NULL)
+    {
+      s += "GET";
+    }
+
+    if (parameters.postHandler != NULL)
+    {
+      if (!s.empty())
+      {
+        s+= ",";
+      }
+      
+      s += "POST";
+    }
+
+    if (parameters.deleteHandler != NULL)
+    {
+      if (!s.empty())
+      {
+        s+= ",";
+      }
+      
+      s += "DELETE";
+    }
+
+    if (parameters.putHandler != NULL)
+    {
+      if (!s.empty())
+      {
+        s+= ",";
+      }
+      
+      s += "PUT";
+    }
+
+    return s;
+  }
+
+
+  bool OrthancPlugins::HandleChunkedGetDelete(HttpOutput& output,
+                                              HttpMethod method,
+                                              const UriComponents& uri,
+                                              const Arguments& headers,
+                                              const GetArguments& getArguments)
+  {
+    RestCallbackMatcher matcher(uri);
+
+    PImpl::ChunkedRestCallback* callback = NULL;
+
+    // Loop over the callbacks registered by the plugins
+    for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+         it != pimpl_->chunkedRestCallbacks_.end(); ++it)
+    {
+      if (matcher.IsMatch((*it)->GetRegularExpression()))
+      {
+        callback = *it;
+        break;
+      }
+    }
+
+    if (callback == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      LOG(INFO) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
+
+      OrthancPluginRestCallback handler;
+
+      switch (method)
+      {
+        case HttpMethod_Get:
+          handler = callback->GetParameters().getHandler;
+          break;
+
+        case HttpMethod_Delete:
+          handler = callback->GetParameters().deleteHandler;
+          break;
+
+        default:
+          handler = NULL;
+          break;
+      }
+
+      if (handler == NULL)
+      {
+        output.SendMethodNotAllowed(GetAllowedMethods(callback->GetParameters()));
+      }
+      else
+      {
+        HttpRequestConverter converter(matcher, method, headers);
+        converter.SetGetArguments(getArguments);
+      
+        PImpl::PluginHttpOutput pluginOutput(output);
+        
+        OrthancPluginErrorCode error = handler(
+          reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput), 
+          matcher.GetFlatUri().c_str(), &converter.GetRequest());
+        
+        pluginOutput.Close(error, GetErrorDictionary());
+      }
+      
+      return true;
+    }
+  }
+
+
+  bool OrthancPlugins::Handle(HttpOutput& output,
+                              RequestOrigin /*origin*/,
+                              const char* /*remoteIp*/,
+                              const char* /*username*/,
+                              HttpMethod method,
+                              const UriComponents& uri,
+                              const Arguments& headers,
+                              const GetArguments& getArguments,
+                              const void* bodyData,
+                              size_t bodySize)
+  {
+    RestCallbackMatcher matcher(uri);
+
+    PImpl::RestCallback* callback = NULL;
+
+    // Loop over the callbacks registered by the plugins
+    for (PImpl::RestCallbacks::const_iterator it = pimpl_->restCallbacks_.begin(); 
+         it != pimpl_->restCallbacks_.end(); ++it)
+    {
+      if (matcher.IsMatch((*it)->GetRegularExpression()))
+      {
+        callback = *it;
+        break;
+      }
+    }
+
+    if (callback == NULL)
+    {
+      // Callback not found, try to find a chunked callback
+      return HandleChunkedGetDelete(output, method, uri, headers, getArguments);
+    }
+
+    LOG(INFO) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
+
+    HttpRequestConverter converter(matcher, method, headers);
+    converter.SetGetArguments(getArguments);
+    converter.GetRequest().body = bodyData;
+    converter.GetRequest().bodySize = bodySize;
+
+    PImpl::PluginHttpOutput pluginOutput(output);
+
+    assert(callback != NULL);
+    OrthancPluginErrorCode error = callback->Invoke
+      (pimpl_->restCallbackMutex_, pluginOutput, matcher.GetFlatUri(), converter.GetRequest());
+
+    pluginOutput.Close(error, GetErrorDictionary());
+    return true;
+  }
+
+
+  class OrthancPlugins::IDicomInstance : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomInstance()
+    {
+    }
+
+    virtual bool CanBeFreed() const = 0;
+
+    virtual const DicomInstanceToStore& GetInstance() const = 0;
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromCallback : public IDicomInstance
+  {
+  private:
+    const DicomInstanceToStore&  instance_;
+
+  public:
+    DicomInstanceFromCallback(const DicomInstanceToStore& instance) :
+      instance_(instance)
+    {
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromBuffer : public IDicomInstance
+  {
+  private:
+    std::string           buffer_;
+    DicomInstanceToStore  instance_;
+
+  public:
+    DicomInstanceFromBuffer(const void* buffer,
+                            size_t size)
+    {
+      buffer_.assign(reinterpret_cast<const char*>(buffer), size);
+      instance_.SetBuffer(buffer_.empty() ? NULL : buffer_.c_str(), buffer_.size());
+      instance_.SetOrigin(DicomInstanceOrigin::FromPlugins());
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  class OrthancPlugins::DicomInstanceFromTranscoded : public IDicomInstance
+  {
+  private:
+    std::unique_ptr<ParsedDicomFile>  parsed_;
+    DicomInstanceToStore              instance_;
+
+  public:
+    DicomInstanceFromTranscoded(IDicomTranscoder::DicomImage& transcoded) :
+      parsed_(transcoded.ReleaseAsParsedDicomFile())
+    {
+      instance_.SetParsedDicomFile(*parsed_);
+      instance_.SetOrigin(DicomInstanceOrigin::FromPlugins());
+    }
+
+    virtual bool CanBeFreed() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual const DicomInstanceToStore& GetInstance() const ORTHANC_OVERRIDE
+    {
+      return instance_;
+    };
+  };
+
+
+  void OrthancPlugins::SignalStoredInstance(const std::string& instanceId,
+                                            const DicomInstanceToStore& instance,
+                                            const Json::Value& simplifiedTags)
+  {
+    DicomInstanceFromCallback wrapped(instance);
+    
+    boost::recursive_mutex::scoped_lock lock(pimpl_->storedCallbackMutex_);
+
+    for (PImpl::OnStoredCallbacks::const_iterator
+           callback = pimpl_->onStoredCallbacks_.begin(); 
+         callback != pimpl_->onStoredCallbacks_.end(); ++callback)
+    {
+      OrthancPluginErrorCode error = (*callback) (
+        reinterpret_cast<OrthancPluginDicomInstance*>(&wrapped),
+        instanceId.c_str());
+
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        GetErrorDictionary().LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+  }
+
+
+  bool OrthancPlugins::FilterIncomingInstance(const DicomInstanceToStore& instance,
+                                              const Json::Value& simplified)
+  {
+    DicomInstanceFromCallback wrapped(instance);
+    
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    
+    for (PImpl::IncomingDicomInstanceFilters::const_iterator
+           filter = pimpl_->incomingDicomInstanceFilters_.begin();
+         filter != pimpl_->incomingDicomInstanceFilters_.end(); ++filter)
+    {
+      int32_t allowed = (*filter) (reinterpret_cast<const OrthancPluginDicomInstance*>(&wrapped));
+
+      if (allowed == 0)
+      {
+        return false;
+      }
+      else if (allowed != 1)
+      {
+        // The callback is only allowed to answer 0 or 1
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+
+    return true;
+  }
+
+  
+  void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType,
+                                            OrthancPluginResourceType resourceType,
+                                            const char* resource)
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
+
+    for (std::list<OrthancPluginOnChangeCallback>::const_iterator 
+           callback = pimpl_->onChangeCallbacks_.begin(); 
+         callback != pimpl_->onChangeCallbacks_.end(); ++callback)
+    {
+      OrthancPluginErrorCode error = (*callback) (changeType, resourceType, resource);
+
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        GetErrorDictionary().LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+  }
+
+
+
+  void OrthancPlugins::SignalChange(const ServerIndexChange& change)
+  {
+    SignalChangeInternal(Plugins::Convert(change.GetChangeType()),
+                         Plugins::Convert(change.GetResourceType()),
+                         change.GetPublicId().c_str());
+  }
+
+
+
+  void OrthancPlugins::RegisterRestCallback(const void* parameters,
+                                            bool lock)
+  {
+    const _OrthancPluginRestCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a REST callback "
+              << (lock ? "with" : "without")
+              << " mutual exclusion on: " 
+              << p.pathRegularExpression;
+
+    pimpl_->restCallbacks_.push_back(new PImpl::RestCallback(p.pathRegularExpression, p.callback, lock));
+  }
+
+
+  void OrthancPlugins::RegisterChunkedRestCallback(const void* parameters)
+  {
+    const _OrthancPluginChunkedRestCallback& p = 
+      *reinterpret_cast<const _OrthancPluginChunkedRestCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a REST callback for chunked streams on: " 
+              << p.pathRegularExpression;
+
+    pimpl_->chunkedRestCallbacks_.push_back(new PImpl::ChunkedRestCallback(p));
+  }
+
+
+  void OrthancPlugins::RegisterOnStoredInstanceCallback(const void* parameters)
+  {
+    const _OrthancPluginOnStoredInstanceCallback& p = 
+      *reinterpret_cast<const _OrthancPluginOnStoredInstanceCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered an OnStoredInstance callback";
+    pimpl_->onStoredCallbacks_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterOnChangeCallback(const void* parameters)
+  {
+    const _OrthancPluginOnChangeCallback& p = 
+      *reinterpret_cast<const _OrthancPluginOnChangeCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered an OnChange callback";
+    pimpl_->onChangeCallbacks_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterWorklistCallback(const void* parameters)
+  {
+    const _OrthancPluginWorklistCallback& p = 
+      *reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+
+    if (pimpl_->worklistCallback_ != NULL)
+    {
+      throw OrthancException(ErrorCode_Plugin,
+                             "Can only register one plugin to handle modality worklists");
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle modality worklists";
+      pimpl_->worklistCallback_ = p.callback;
+    }
+  }
+
+
+  void OrthancPlugins::RegisterFindCallback(const void* parameters)
+  {
+    const _OrthancPluginFindCallback& p = 
+      *reinterpret_cast<const _OrthancPluginFindCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
+
+    if (pimpl_->findCallback_ != NULL)
+    {
+      throw OrthancException(ErrorCode_Plugin,
+                             "Can only register one plugin to handle C-FIND requests");
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle C-FIND requests";
+      pimpl_->findCallback_ = p.callback;
+    }
+  }
+
+
+  void OrthancPlugins::RegisterMoveCallback(const void* parameters)
+  {
+    // invokeServiceMutex_ is assumed to be locked
+
+    const _OrthancPluginMoveCallback& p = 
+      *reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters);
+
+    if (pimpl_->moveCallbacks_.callback != NULL)
+    {
+      throw OrthancException(ErrorCode_Plugin,
+                             "Can only register one plugin to handle C-MOVE requests");
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle C-MOVE requests";
+      pimpl_->moveCallbacks_ = p;
+    }
+  }
+
+
+  void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
+  {
+    const _OrthancPluginDecodeImageCallback& p = 
+      *reinterpret_cast<const _OrthancPluginDecodeImageCallback*>(parameters);
+
+    boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    pimpl_->decodeImageCallbacks_.push_back(p.callback);
+    LOG(INFO) << "Plugin has registered a callback to decode DICOM images (" 
+              << pimpl_->decodeImageCallbacks_.size() << " decoder(s) now active)";
+  }
+
+
+  void OrthancPlugins::RegisterTranscoderCallback(const void* parameters)
+  {
+    const _OrthancPluginTranscoderCallback& p = 
+      *reinterpret_cast<const _OrthancPluginTranscoderCallback*>(parameters);
+
+    boost::unique_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    pimpl_->transcoderCallbacks_.push_back(p.callback);
+    LOG(INFO) << "Plugin has registered a callback to transcode DICOM images (" 
+              << pimpl_->transcoderCallbacks_.size() << " transcoder(s) now active)";
+  }
+
+
+  void OrthancPlugins::RegisterJobsUnserializer(const void* parameters)
+  {
+    const _OrthancPluginJobsUnserializer& p = 
+      *reinterpret_cast<const _OrthancPluginJobsUnserializer*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
+
+    pimpl_->jobsUnserializers_.push_back(p.unserializer);
+    LOG(INFO) << "Plugin has registered a callback to unserialize jobs (" 
+              << pimpl_->jobsUnserializers_.size() << " unserializer(s) now active)";
+  }
+
+
+  void OrthancPlugins::RegisterIncomingHttpRequestFilter(const void* parameters)
+  {
+    const _OrthancPluginIncomingHttpRequestFilter& p = 
+      *reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a callback to filter incoming HTTP requests";
+    pimpl_->incomingHttpRequestFilters_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterIncomingHttpRequestFilter2(const void* parameters)
+  {
+    const _OrthancPluginIncomingHttpRequestFilter2& p = 
+      *reinterpret_cast<const _OrthancPluginIncomingHttpRequestFilter2*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a callback to filter incoming HTTP requests";
+    pimpl_->incomingHttpRequestFilters2_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterIncomingDicomInstanceFilter(const void* parameters)
+  {
+    const _OrthancPluginIncomingDicomInstanceFilter& p = 
+      *reinterpret_cast<const _OrthancPluginIncomingDicomInstanceFilter*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a callback to filter incoming DICOM instances";
+    pimpl_->incomingDicomInstanceFilters_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterRefreshMetricsCallback(const void* parameters)
+  {
+    const _OrthancPluginRegisterRefreshMetricsCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRegisterRefreshMetricsCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
+
+    LOG(INFO) << "Plugin has registered a callback to refresh its metrics";
+    pimpl_->refreshMetricsCallbacks_.push_back(p.callback);
+  }
+
+
+  void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters)
+  {
+    const _OrthancPluginRegisterStorageCommitmentScpCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
+    LOG(INFO) << "Plugin has registered a storage commitment callback";
+
+    pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p));
+  }
+
+
+  void OrthancPlugins::AnswerBuffer(const void* parameters)
+  {
+    const _OrthancPluginAnswerBuffer& p = 
+      *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SetContentType(p.mimeType);
+    translatedOutput.Answer(p.answer, p.answerSize);
+  }
+
+
+  void OrthancPlugins::Redirect(const void* parameters)
+  {
+    const _OrthancPluginOutputPlusArgument& p = 
+      *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.Redirect(p.argument);
+  }
+
+
+  void OrthancPlugins::SendHttpStatusCode(const void* parameters)
+  {
+    const _OrthancPluginSendHttpStatusCode& p = 
+      *reinterpret_cast<const _OrthancPluginSendHttpStatusCode*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendStatus(static_cast<HttpStatus>(p.status));
+  }
+
+
+  void OrthancPlugins::SendHttpStatus(const void* parameters)
+  {
+    const _OrthancPluginSendHttpStatus& p = 
+      *reinterpret_cast<const _OrthancPluginSendHttpStatus*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    HttpStatus status = static_cast<HttpStatus>(p.status);
+
+    if (p.bodySize > 0 && p.body != NULL)
+    {
+      translatedOutput.SendStatus(status, p.body, p.bodySize);
+    }
+    else
+    {
+      translatedOutput.SendStatus(status);
+    }
+  }
+
+
+  void OrthancPlugins::SendUnauthorized(const void* parameters)
+  {
+    const _OrthancPluginOutputPlusArgument& p = 
+      *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendUnauthorized(p.argument);
+  }
+
+
+  void OrthancPlugins::SendMethodNotAllowed(const void* parameters)
+  {
+    const _OrthancPluginOutputPlusArgument& p = 
+      *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendMethodNotAllowed(p.argument);
+  }
+
+
+  void OrthancPlugins::SetCookie(const void* parameters)
+  {
+    const _OrthancPluginSetHttpHeader& p = 
+      *reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SetCookie(p.key, p.value);
+  }
+
+
+  void OrthancPlugins::SetHttpHeader(const void* parameters)
+  {
+    const _OrthancPluginSetHttpHeader& p = 
+      *reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.AddHeader(p.key, p.value);
+  }
+
+
+  void OrthancPlugins::SetHttpErrorDetails(const void* parameters)
+  {
+    const _OrthancPluginSetHttpErrorDetails& p = 
+      *reinterpret_cast<const _OrthancPluginSetHttpErrorDetails*>(parameters);
+
+    PImpl::PluginHttpOutput* output =
+      reinterpret_cast<PImpl::PluginHttpOutput*>(p.output);
+    output->SetErrorDetails(p.details, p.log);
+  }
+
+
+  void OrthancPlugins::CompressAndAnswerPngImage(const void* parameters)
+  {
+    // Bridge for backward compatibility with Orthanc <= 0.9.3
+    const _OrthancPluginCompressAndAnswerPngImage& p = 
+      *reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters);
+
+    _OrthancPluginCompressAndAnswerImage p2;
+    p2.output = p.output;
+    p2.imageFormat = OrthancPluginImageFormat_Png;
+    p2.pixelFormat = p.format;
+    p2.width = p.width;
+    p2.height = p.height;
+    p2.pitch = p.height;
+    p2.buffer = p.buffer;
+    p2.quality = 0;
+
+    CompressAndAnswerImage(&p2);
+  }
+
+
+  void OrthancPlugins::CompressAndAnswerImage(const void* parameters)
+  {
+    const _OrthancPluginCompressAndAnswerImage& p = 
+      *reinterpret_cast<const _OrthancPluginCompressAndAnswerImage*>(parameters);
+
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
+
+    std::string compressed;
+
+    switch (p.imageFormat)
+    {
+      case OrthancPluginImageFormat_Png:
+      {
+        PngWriter writer;
+        writer.WriteToMemory(compressed, accessor);
+        translatedOutput.SetContentType(MimeType_Png);
+        break;
+      }
+
+      case OrthancPluginImageFormat_Jpeg:
+      {
+        JpegWriter writer;
+        writer.SetQuality(p.quality);
+        writer.WriteToMemory(compressed, accessor);
+        translatedOutput.SetContentType(MimeType_Jpeg);
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    translatedOutput.Answer(compressed);
+  }
+
+
+  void OrthancPlugins::GetDicomForInstance(const void* parameters)
+  {
+    const _OrthancPluginGetDicomForInstance& p = 
+      *reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
+
+    std::string dicom;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      lock.GetContext().ReadDicom(dicom, p.instanceId);
+    }
+
+    CopyToMemoryBuffer(*p.target, dicom);
+  }
+
+
+  void OrthancPlugins::RestApiGet(const void* parameters,
+                                  bool afterPlugins)
+  {
+    const _OrthancPluginRestApiGet& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
+        
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri
+              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    IHttpHandler* handler;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
+    }
+
+    std::map<std::string, std::string> httpHeaders;
+
+    std::string result;
+    if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, httpHeaders))
+    {
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void OrthancPlugins::RestApiGet2(const void* parameters)
+  {
+    const _OrthancPluginRestApiGet2& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiGet2*>(parameters);
+        
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri
+              << (p.afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    IHttpHandler::Arguments headers;
+
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      std::string name(p.headersKeys[i]);
+      std::transform(name.begin(), name.end(), name.begin(), ::tolower);
+      headers[name] = p.headersValues[i];
+    }
+
+    IHttpHandler* handler;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
+    }
+      
+    std::string result;
+    if (HttpToolbox::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, headers))
+    {
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void OrthancPlugins::RestApiPostPut(bool isPost, 
+                                      const void* parameters,
+                                      bool afterPlugins)
+  {
+    const _OrthancPluginRestApiPostPut& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
+
+    LOG(INFO) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put)
+              << " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    IHttpHandler* handler;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
+    }
+      
+    std::map<std::string, std::string> httpHeaders;
+
+    std::string result;
+    if (isPost ? 
+        HttpToolbox::SimplePost(result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders) :
+        HttpToolbox::SimplePut (result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders))
+    {
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void OrthancPlugins::RestApiDelete(const void* parameters,
+                                     bool afterPlugins)
+  {
+    const char* uri = reinterpret_cast<const char*>(parameters);
+    LOG(INFO) << "Plugin making REST DELETE call on URI " << uri
+              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    IHttpHandler* handler;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
+    }
+      
+    std::map<std::string, std::string> httpHeaders;
+
+    if (!HttpToolbox::SimpleDelete(*handler, RequestOrigin_Plugins, uri, httpHeaders))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  void OrthancPlugins::LookupResource(_OrthancPluginService service,
+                                      const void* parameters)
+  {
+    const _OrthancPluginRetrieveDynamicString& p = 
+      *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters);
+
+    /**
+     * The enumeration below only uses the tags that are indexed in
+     * the Orthanc database. It reflects the
+     * "CandidateResources::ApplyFilter()" method of the
+     * "OrthancFindRequestHandler" class.
+     **/
+
+    DicomTag tag(0, 0);
+    ResourceType level;
+    switch (service)
+    {
+      case _OrthancPluginService_LookupPatient:
+        tag = DICOM_TAG_PATIENT_ID;
+        level = ResourceType_Patient;
+        break;
+
+      case _OrthancPluginService_LookupStudy:
+        tag = DICOM_TAG_STUDY_INSTANCE_UID;
+        level = ResourceType_Study;
+        break;
+
+      case _OrthancPluginService_LookupStudyWithAccessionNumber:
+        tag = DICOM_TAG_ACCESSION_NUMBER;
+        level = ResourceType_Study;
+        break;
+
+      case _OrthancPluginService_LookupSeries:
+        tag = DICOM_TAG_SERIES_INSTANCE_UID;
+        level = ResourceType_Series;
+        break;
+
+      case _OrthancPluginService_LookupInstance:
+        tag = DICOM_TAG_SOP_INSTANCE_UID;
+        level = ResourceType_Instance;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    std::vector<std::string> result;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      lock.GetContext().GetIndex().LookupIdentifierExact(result, level, tag, p.argument);
+    }
+
+    if (result.size() == 1)
+    {
+      *p.result = CopyString(result[0]);
+    }
+    else
+    {
+      if (result.size() > 1)
+      {
+        LOG(WARNING) << "LookupResource(): Multiple resources match the query (instead of 0 or 1), which indicates "
+                     << "your DICOM database breaks the DICOM model of the real world";
+      }
+      
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  static void AccessInstanceMetadataInternal(bool checkExistence,
+                                             const _OrthancPluginAccessDicomInstance& params,
+                                             const DicomInstanceToStore& instance)
+  {
+    MetadataType metadata;
+
+    try
+    {
+      metadata = StringToMetadata(params.key);
+    }
+    catch (OrthancException&)
+    {
+      // Unknown metadata
+      if (checkExistence)
+      {
+        *params.resultInt64 = -1;
+      }
+      else
+      {
+        *params.resultString = NULL;
+      }
+
+      return;
+    }
+
+    ServerIndex::MetadataMap::const_iterator it = 
+      instance.GetMetadata().find(std::make_pair(ResourceType_Instance, metadata));
+
+    if (checkExistence)
+    {
+      if (it != instance.GetMetadata().end())
+      {
+        *params.resultInt64 = 1;
+      }
+      else
+      {
+        *params.resultInt64 = 0;
+      }
+    }
+    else
+    {
+      if (it != instance.GetMetadata().end())
+      {      
+        *params.resultString = it->second.c_str();
+      }
+      else
+      {
+        // Error: Missing metadata
+        *params.resultString = NULL;
+      }
+    }
+  }
+
+
+  void OrthancPlugins::AccessDicomInstance(_OrthancPluginService service,
+                                           const void* parameters)
+  {
+    const _OrthancPluginAccessDicomInstance& p = 
+      *reinterpret_cast<const _OrthancPluginAccessDicomInstance*>(parameters);
+
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    const DicomInstanceToStore& instance =
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
+
+    switch (service)
+    {
+      case _OrthancPluginService_GetInstanceRemoteAet:
+        *p.resultString = instance.GetOrigin().GetRemoteAetC();
+        return;
+
+      case _OrthancPluginService_GetInstanceSize:
+        *p.resultInt64 = instance.GetBufferSize();
+        return;
+
+      case _OrthancPluginService_GetInstanceData:
+        *p.resultString = reinterpret_cast<const char*>(instance.GetBufferData());
+        return;
+
+      case _OrthancPluginService_HasInstanceMetadata:
+        AccessInstanceMetadataInternal(true, p, instance);
+        return;
+
+      case _OrthancPluginService_GetInstanceMetadata:
+        AccessInstanceMetadataInternal(false, p, instance);
+        return;
+
+      case _OrthancPluginService_GetInstanceJson:
+      case _OrthancPluginService_GetInstanceSimplifiedJson:
+      {
+        Json::StyledWriter writer;
+        std::string s;
+
+        if (service == _OrthancPluginService_GetInstanceJson)
+        {
+          s = writer.write(instance.GetJson());
+        }
+        else
+        {
+          Json::Value simplified;
+          ServerToolbox::SimplifyTags(simplified, instance.GetJson(), DicomToJsonFormat_Human);
+          s = writer.write(simplified);
+        }
+
+        *p.resultStringToFree = CopyString(s);
+        return;
+      }
+
+      case _OrthancPluginService_GetInstanceOrigin:   // New in Orthanc 0.9.5
+        *p.resultOrigin = Plugins::Convert(instance.GetOrigin().GetRequestOrigin());
+        return;
+
+      case _OrthancPluginService_GetInstanceTransferSyntaxUid:   // New in Orthanc 1.6.1
+      {
+        std::string s;
+        if (!instance.LookupTransferSyntax(s))
+        {
+          s.clear();
+        }
+        
+        *p.resultStringToFree = CopyString(s);
+        return;
+      }
+
+      case _OrthancPluginService_HasInstancePixelData:   // New in Orthanc 1.6.1
+        *p.resultInt64 = instance.HasPixelData();
+        return;
+
+      case _OrthancPluginService_GetInstanceFramesCount:  // New in Orthanc 1.7.0
+        *p.resultInt64 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void OrthancPlugins::BufferCompression(const void* parameters)
+  {
+    const _OrthancPluginBufferCompression& p = 
+      *reinterpret_cast<const _OrthancPluginBufferCompression*>(parameters);
+
+    std::string result;
+
+    {
+      std::unique_ptr<DeflateBaseCompressor> compressor;
+
+      switch (p.compression)
+      {
+        case OrthancPluginCompressionType_Zlib:
+        {
+          compressor.reset(new ZlibCompressor);
+          compressor->SetPrefixWithUncompressedSize(false);
+          break;
+        }
+
+        case OrthancPluginCompressionType_ZlibWithSize:
+        {
+          compressor.reset(new ZlibCompressor);
+          compressor->SetPrefixWithUncompressedSize(true);
+          break;
+        }
+
+        case OrthancPluginCompressionType_Gzip:
+        {
+          compressor.reset(new GzipCompressor);
+          compressor->SetPrefixWithUncompressedSize(false);
+          break;
+        }
+
+        case OrthancPluginCompressionType_GzipWithSize:
+        {
+          compressor.reset(new GzipCompressor);
+          compressor->SetPrefixWithUncompressedSize(true);
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      if (p.uncompress)
+      {
+        compressor->Uncompress(result, p.source, p.size);
+      }
+      else
+      {
+        compressor->Compress(result, p.source, p.size);
+      }
+    }
+
+    CopyToMemoryBuffer(*p.target, result);
+  }
+
+
+  static OrthancPluginImage* ReturnImage(std::unique_ptr<ImageAccessor>& image)
+  {
+    // Images returned to plugins are assumed to be writeable. If the
+    // input image is read-only, we return a copy so that it can be modified.
+
+    if (image.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    
+    if (image->IsReadOnly())
+    {
+      std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
+      ImageProcessing::Copy(*copy, *image);
+      image.reset(NULL);
+      return reinterpret_cast<OrthancPluginImage*>(copy.release());
+    }
+    else
+    {
+      return reinterpret_cast<OrthancPluginImage*>(image.release());
+    }
+  }
+
+
+  void OrthancPlugins::AccessDicomInstance2(_OrthancPluginService service,
+                                            const void* parameters)
+  {
+    const _OrthancPluginAccessDicomInstance2& p = 
+      *reinterpret_cast<const _OrthancPluginAccessDicomInstance2*>(parameters);
+
+    if (p.instance == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    const DicomInstanceToStore& instance =
+      reinterpret_cast<const IDicomInstance*>(p.instance)->GetInstance();
+
+    switch (service)
+    {
+      case _OrthancPluginService_GetInstanceFramesCount:
+        *p.targetUint32 = instance.GetParsedDicomFile().GetFramesCount();
+        return;
+        
+      case _OrthancPluginService_GetInstanceRawFrame:
+      {
+        if (p.targetBuffer == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        p.targetBuffer->data = NULL;
+        p.targetBuffer->size = 0;
+        
+        MimeType mime;
+        std::string frame;
+        instance.GetParsedDicomFile().GetRawFrame(frame, mime, p.frameIndex);
+        CopyToMemoryBuffer(*p.targetBuffer, frame);
+        return;
+      }
+        
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+      {
+        if (p.targetImage == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        std::unique_ptr<ImageAccessor> decoded;
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          decoded.reset(lock.GetContext().DecodeDicomFrame(instance, p.frameIndex));
+        }
+        
+        *(p.targetImage) = ReturnImage(decoded);
+        return;
+      }
+        
+      case _OrthancPluginService_SerializeDicomInstance:
+      {
+        if (p.targetBuffer == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        p.targetBuffer->data = NULL;
+        p.targetBuffer->size = 0;
+        
+        std::string serialized;
+        instance.GetParsedDicomFile().SaveToMemoryBuffer(serialized);
+        CopyToMemoryBuffer(*p.targetBuffer, serialized);
+        return;
+      }
+
+      case _OrthancPluginService_GetInstanceAdvancedJson:
+      {
+        if (p.targetStringToFree == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        
+        Json::Value json;
+        instance.GetParsedDicomFile().DatasetToJson(
+          json, Plugins::Convert(p.format), 
+          static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
+
+        Json::FastWriter writer;
+        *p.targetStringToFree = CopyString(writer.write(json));        
+        return;
+      }
+      
+      case _OrthancPluginService_GetInstanceDicomWebJson:
+      case _OrthancPluginService_GetInstanceDicomWebXml:
+      {
+        if (p.targetStringToFree == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+
+        DicomWebBinaryFormatter formatter(p.dicomWebCallback, p.dicomWebPayload);
+        formatter.Apply(p.targetStringToFree,
+                        (service == _OrthancPluginService_GetInstanceDicomWebJson),
+                        instance.GetParsedDicomFile());
+        return;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void OrthancPlugins::UncompressImage(const void* parameters)
+  {
+    const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
+
+    std::unique_ptr<ImageAccessor> image;
+
+    switch (p.format)
+    {
+      case OrthancPluginImageFormat_Png:
+      {
+        image.reset(new PngReader);
+        reinterpret_cast<PngReader&>(*image).ReadFromMemory(p.data, p.size);
+        break;
+      }
+
+      case OrthancPluginImageFormat_Jpeg:
+      {
+        image.reset(new JpegReader);
+        reinterpret_cast<JpegReader&>(*image).ReadFromMemory(p.data, p.size);
+        break;
+      }
+
+      case OrthancPluginImageFormat_Dicom:
+      {
+        PImpl::ServerContextLock lock(*pimpl_);
+        image.reset(lock.GetContext().DecodeDicomFrame(p.data, p.size, 0));
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    *(p.target) = ReturnImage(image);
+  }
+
+
+  void OrthancPlugins::CompressImage(const void* parameters)
+  {
+    const _OrthancPluginCompressImage& p = *reinterpret_cast<const _OrthancPluginCompressImage*>(parameters);
+
+    std::string compressed;
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
+
+    switch (p.imageFormat)
+    {
+      case OrthancPluginImageFormat_Png:
+      {
+        PngWriter writer;
+        writer.WriteToMemory(compressed, accessor);
+        break;
+      }
+
+      case OrthancPluginImageFormat_Jpeg:
+      {
+        JpegWriter writer;
+        writer.SetQuality(p.quality);
+        writer.WriteToMemory(compressed, accessor);
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    CopyToMemoryBuffer(*p.target, compressed.size() > 0 ? compressed.c_str() : NULL, compressed.size());
+  }
+
+
+  static void SetupHttpClient(HttpClient& client,
+                              const _OrthancPluginCallHttpClient2& parameters)
+  {
+    client.SetUrl(parameters.url);
+    client.SetConvertHeadersToLowerCase(false);
+
+    if (parameters.timeout != 0)
+    {
+      client.SetTimeout(parameters.timeout);
+    }
+
+    if (parameters.username != NULL && 
+        parameters.password != NULL)
+    {
+      client.SetCredentials(parameters.username, parameters.password);
+    }
+
+    if (parameters.certificateFile != NULL)
+    {
+      std::string certificate(parameters.certificateFile);
+      std::string key, password;
+
+      if (parameters.certificateKeyFile)
+      {
+        key.assign(parameters.certificateKeyFile);
+      }
+
+      if (parameters.certificateKeyPassword)
+      {
+        password.assign(parameters.certificateKeyPassword);
+      }
+
+      client.SetClientCertificate(certificate, key, password);
+    }
+
+    client.SetPkcs11Enabled(parameters.pkcs11 ? true : false);
+
+    for (uint32_t i = 0; i < parameters.headersCount; i++)
+    {
+      if (parameters.headersKeys[i] == NULL ||
+          parameters.headersValues[i] == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      client.AddHeader(parameters.headersKeys[i], parameters.headersValues[i]);
+    }
+
+    switch (parameters.method)
+    {
+      case OrthancPluginHttpMethod_Get:
+        client.SetMethod(HttpMethod_Get);
+        break;
+
+      case OrthancPluginHttpMethod_Post:
+        client.SetMethod(HttpMethod_Post);
+        client.GetBody().assign(reinterpret_cast<const char*>(parameters.body), parameters.bodySize);
+        break;
+
+      case OrthancPluginHttpMethod_Put:
+        client.SetMethod(HttpMethod_Put);
+        client.GetBody().assign(reinterpret_cast<const char*>(parameters.body), parameters.bodySize);
+        break;
+
+      case OrthancPluginHttpMethod_Delete:
+        client.SetMethod(HttpMethod_Delete);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void ExecuteHttpClientWithoutChunkedBody(uint16_t& httpStatus,
+                                                  OrthancPluginMemoryBuffer* answerBody,
+                                                  OrthancPluginMemoryBuffer* answerHeaders,
+                                                  HttpClient& client)
+  {
+    if (answerBody == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    std::string body;
+    HttpClient::HttpHeaders headers;
+
+    bool success = client.Apply(body, headers);
+
+    // The HTTP request has succeeded
+    httpStatus = static_cast<uint16_t>(client.GetLastStatus());
+
+    if (!success)
+    {
+      HttpClient::ThrowException(client.GetLastStatus());
+    }
+
+    // Copy the HTTP headers of the answer, if the plugin requested them
+    if (answerHeaders != NULL)
+    {
+      Json::Value json = Json::objectValue;
+
+      for (HttpClient::HttpHeaders::const_iterator 
+             it = headers.begin(); it != headers.end(); ++it)
+      {
+        json[it->first] = it->second;
+      }
+        
+      std::string s = json.toStyledString();
+      CopyToMemoryBuffer(*answerHeaders, s);
+    }
+
+    // Copy the body of the answer if it makes sense
+    if (client.GetMethod() != HttpMethod_Delete)
+    {
+      CopyToMemoryBuffer(*answerBody, body);
+    }
+  }
+
+
+  void OrthancPlugins::CallHttpClient(const void* parameters)
+  {
+    const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters);
+
+    HttpClient client;
+
+    {    
+      _OrthancPluginCallHttpClient2 converted;
+      memset(&converted, 0, sizeof(converted));
+
+      converted.answerBody = NULL;
+      converted.answerHeaders = NULL;
+      converted.httpStatus = NULL;
+      converted.method = p.method;
+      converted.url = p.url;
+      converted.headersCount = 0;
+      converted.headersKeys = NULL;
+      converted.headersValues = NULL;
+      converted.body = p.body;
+      converted.bodySize = p.bodySize;
+      converted.username = p.username;
+      converted.password = p.password;
+      converted.timeout = 0;  // Use default timeout
+      converted.certificateFile = NULL;
+      converted.certificateKeyFile = NULL;
+      converted.certificateKeyPassword = NULL;
+      converted.pkcs11 = false;
+
+      SetupHttpClient(client, converted);
+    }
+
+    uint16_t status;
+    ExecuteHttpClientWithoutChunkedBody(status, p.target, NULL, client);
+  }
+
+
+  void OrthancPlugins::CallHttpClient2(const void* parameters)
+  {
+    const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
+    
+    if (p.httpStatus == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    HttpClient client;
+
+    if (p.method == OrthancPluginHttpMethod_Post ||
+        p.method == OrthancPluginHttpMethod_Put)
+    {
+      client.GetBody().assign(reinterpret_cast<const char*>(p.body), p.bodySize);
+    }
+    
+    SetupHttpClient(client, p);
+    ExecuteHttpClientWithoutChunkedBody(*p.httpStatus, p.answerBody, p.answerHeaders, client);
+  }
+
+
+  void OrthancPlugins::ChunkedHttpClient(const void* parameters)
+  {
+    const _OrthancPluginChunkedHttpClient& p =
+      *reinterpret_cast<const _OrthancPluginChunkedHttpClient*>(parameters);
+        
+    if (p.httpStatus == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    HttpClient client;
+
+    {
+      _OrthancPluginCallHttpClient2 converted;
+      memset(&converted, 0, sizeof(converted));
+
+      converted.answerBody = NULL;
+      converted.answerHeaders = NULL;
+      converted.httpStatus = NULL;
+      converted.method = p.method;
+      converted.url = p.url;
+      converted.headersCount = p.headersCount;
+      converted.headersKeys = p.headersKeys;
+      converted.headersValues = p.headersValues;
+      converted.body = NULL;
+      converted.bodySize = 0;
+      converted.username = p.username;
+      converted.password = p.password;
+      converted.timeout = p.timeout;
+      converted.certificateFile = p.certificateFile;
+      converted.certificateKeyFile = p.certificateKeyFile;
+      converted.certificateKeyPassword = p.certificateKeyPassword;
+      converted.pkcs11 = p.pkcs11;
+
+      SetupHttpClient(client, converted);
+    }
+    
+    HttpClientChunkedRequest body(p, pimpl_->dictionary_);
+    client.SetBody(body);
+
+    HttpClientChunkedAnswer answer(p, pimpl_->dictionary_);
+
+    bool success = client.Apply(answer);
+
+    *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
+
+    if (!success)
+    {
+      HttpClient::ThrowException(client.GetLastStatus());
+    }
+  }
+
+
+  void OrthancPlugins::CallPeerApi(const void* parameters)
+  {
+    const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters);
+    const OrthancPeers& peers = *reinterpret_cast<const OrthancPeers*>(p.peers);
+
+    HttpClient client(peers.GetPeerParameters(p.peerIndex), p.uri);
+    client.SetConvertHeadersToLowerCase(false);
+
+    if (p.timeout != 0)
+    {
+      client.SetTimeout(p.timeout);
+    }
+
+    for (uint32_t i = 0; i < p.additionalHeadersCount; i++)
+    {
+      if (p.additionalHeadersKeys[i] == NULL ||
+          p.additionalHeadersValues[i] == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      client.AddHeader(p.additionalHeadersKeys[i], p.additionalHeadersValues[i]);
+    }
+
+    switch (p.method)
+    {
+      case OrthancPluginHttpMethod_Get:
+        client.SetMethod(HttpMethod_Get);
+        break;
+
+      case OrthancPluginHttpMethod_Post:
+        client.SetMethod(HttpMethod_Post);
+        client.GetBody().assign(reinterpret_cast<const char*>(p.body), p.bodySize);
+        break;
+
+      case OrthancPluginHttpMethod_Put:
+        client.SetMethod(HttpMethod_Put);
+        client.GetBody().assign(reinterpret_cast<const char*>(p.body), p.bodySize);
+        break;
+
+      case OrthancPluginHttpMethod_Delete:
+        client.SetMethod(HttpMethod_Delete);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string body;
+    HttpClient::HttpHeaders headers;
+
+    bool success = client.Apply(body, headers);
+
+    // The HTTP request has succeeded
+    *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
+
+    if (!success)
+    {
+      HttpClient::ThrowException(client.GetLastStatus());
+    }
+
+    // Copy the HTTP headers of the answer, if the plugin requested them
+    if (p.answerHeaders != NULL)
+    {
+      Json::Value json = Json::objectValue;
+
+      for (HttpClient::HttpHeaders::const_iterator 
+             it = headers.begin(); it != headers.end(); ++it)
+      {
+        json[it->first] = it->second;
+      }
+        
+      std::string s = json.toStyledString();
+      CopyToMemoryBuffer(*p.answerHeaders, s);
+    }
+
+    // Copy the body of the answer if it makes sense
+    if (p.method != OrthancPluginHttpMethod_Delete)
+    {
+      CopyToMemoryBuffer(*p.answerBody, body);
+    }
+  }
+
+
+  void OrthancPlugins::ConvertPixelFormat(const void* parameters)
+  {
+    const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters);
+    const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source);
+
+    std::unique_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false));
+    ImageProcessing::Convert(*target, source);
+
+    *(p.target) = ReturnImage(target);
+  }
+
+
+
+  void OrthancPlugins::GetFontInfo(const void* parameters)
+  {
+    const _OrthancPluginGetFontInfo& p = *reinterpret_cast<const _OrthancPluginGetFontInfo*>(parameters);
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+
+      const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
+
+      if (p.name != NULL)
+      {
+        *(p.name) = font.GetName().c_str();
+      }
+      else if (p.size != NULL)
+      {
+        *(p.size) = font.GetSize();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void OrthancPlugins::DrawText(const void* parameters)
+  {
+    const _OrthancPluginDrawText& p = *reinterpret_cast<const _OrthancPluginDrawText*>(parameters);
+
+    ImageAccessor& target = *reinterpret_cast<ImageAccessor*>(p.image);
+
+    {
+      OrthancConfiguration::ReaderLock lock;
+      const Font& font = lock.GetConfiguration().GetFontRegistry().GetFont(p.fontIndex);
+      font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b);
+    }
+  }
+
+
+  void OrthancPlugins::ApplyDicomToJson(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginDicomToJson& p =
+      *reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters);
+
+    std::unique_ptr<ParsedDicomFile> dicom;
+
+    if (service == _OrthancPluginService_DicomBufferToJson)
+    {
+      dicom.reset(new ParsedDicomFile(p.buffer, p.size));
+    }
+    else
+    {
+      if (p.instanceId == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      std::string content;
+
+      {
+        PImpl::ServerContextLock lock(*pimpl_);
+        lock.GetContext().ReadDicom(content, p.instanceId);
+      }
+
+      dicom.reset(new ParsedDicomFile(content));
+    }
+
+    Json::Value json;
+    dicom->DatasetToJson(json, Plugins::Convert(p.format), 
+                         static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
+
+    Json::FastWriter writer;
+    *p.result = CopyString(writer.write(json));
+  }
+        
+
+  void OrthancPlugins::ApplyCreateDicom(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginCreateDicom& p =
+      *reinterpret_cast<const _OrthancPluginCreateDicom*>(parameters);
+
+    Json::Value json;
+
+    if (p.json == NULL)
+    {
+      json = Json::objectValue;
+    }
+    else
+    {
+      Json::Reader reader;
+      if (!reader.parse(p.json, json))
+      {
+        throw OrthancException(ErrorCode_BadJson);
+      }
+    }
+
+    std::string dicom;
+
+    {
+      // Fix issue 168 (Plugins can't read private tags from the
+      // configuration file)
+      // https://bitbucket.org/sjodogne/orthanc/issues/168/
+      std::string privateCreator;
+      {
+        OrthancConfiguration::ReaderLock lock;
+        privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
+      }
+      
+      std::unique_ptr<ParsedDicomFile> file
+        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags),
+                                         privateCreator));
+
+      if (p.pixelData)
+      {
+        file->EmbedImage(*reinterpret_cast<const ImageAccessor*>(p.pixelData));
+      }
+
+      file->SaveToMemoryBuffer(dicom);
+    }
+
+    CopyToMemoryBuffer(*p.target, dicom);
+  }
+
+
+  void OrthancPlugins::ComputeHash(_OrthancPluginService service,
+                                   const void* parameters)
+  {
+    const _OrthancPluginComputeHash& p =
+      *reinterpret_cast<const _OrthancPluginComputeHash*>(parameters);
+ 
+    std::string hash;
+    switch (service)
+    {
+      case _OrthancPluginService_ComputeMd5:
+        Toolbox::ComputeMD5(hash, p.buffer, p.size);
+        break;
+
+      case _OrthancPluginService_ComputeSha1:
+        Toolbox::ComputeSHA1(hash, p.buffer, p.size);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+   
+    *p.result = CopyString(hash);
+  }
+
+
+  void OrthancPlugins::GetTagName(const void* parameters)
+  {
+    const _OrthancPluginGetTagName& p =
+      *reinterpret_cast<const _OrthancPluginGetTagName*>(parameters);
+
+    std::string privateCreator;
+    
+    if (p.privateCreator != NULL)
+    {
+      privateCreator = p.privateCreator;
+    }
+   
+    DicomTag tag(p.group, p.element);
+    *p.result = CopyString(FromDcmtkBridge::GetTagName(tag, privateCreator));
+  }
+
+
+  void OrthancPlugins::ApplyCreateImage(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginCreateImage& p =
+      *reinterpret_cast<const _OrthancPluginCreateImage*>(parameters);
+
+    std::unique_ptr<ImageAccessor> result;
+
+    switch (service)
+    {
+      case _OrthancPluginService_CreateImage:
+        result.reset(new Image(Plugins::Convert(p.format), p.width, p.height, false));
+        break;
+
+      case _OrthancPluginService_CreateImageAccessor:
+        result.reset(new ImageAccessor);
+        result->AssignWritable(Plugins::Convert(p.format), p.width, p.height, p.pitch, p.buffer);
+        break;
+
+      case _OrthancPluginService_DecodeDicomImage:
+      {
+        PImpl::ServerContextLock lock(*pimpl_);
+        result.reset(lock.GetContext().DecodeDicomFrame(p.constBuffer, p.bufferSize, p.frameIndex));
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    *(p.target) = ReturnImage(result);
+  }
+
+
+  void OrthancPlugins::ApplySendMultipartItem(const void* parameters)
+  {
+    // An exception might be raised in this function if the
+    // connection was closed by the HTTP client.
+    const _OrthancPluginAnswerBuffer& p =
+      *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+
+    std::map<std::string, std::string> headers;  // No custom headers
+    reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
+  }
+
+
+  void OrthancPlugins::ApplySendMultipartItem2(const void* parameters)
+  {
+    // An exception might be raised in this function if the
+    // connection was closed by the HTTP client.
+    const _OrthancPluginSendMultipartItem2& p =
+      *reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters);
+    
+    std::map<std::string, std::string> headers;
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      headers[p.headersKeys[i]] = p.headersValues[i];
+    }
+    
+    reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->SendMultipartItem(p.answer, p.answerSize, headers);
+  }
+      
+
+  void OrthancPlugins::DatabaseAnswer(const void* parameters)
+  {
+    const _OrthancPluginDatabaseAnswer& p =
+      *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
+
+    if (pimpl_->database_.get() != NULL)
+    {
+      pimpl_->database_->AnswerReceived(p);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest,
+                             "Cannot invoke this service without a custom database back-end");
+    }
+  }
+
+
+  namespace
+  {
+    class DictionaryReadLocker
+    {
+    private:
+      const DcmDataDictionary& dictionary_;
+
+    public:
+      DictionaryReadLocker() : dictionary_(dcmDataDict.rdlock())
+      {
+      }
+
+      ~DictionaryReadLocker()
+      {
+#if DCMTK_VERSION_NUMBER >= 364
+        dcmDataDict.rdunlock();
+#else
+        dcmDataDict.unlock();
+#endif
+      }
+
+      const DcmDataDictionary* operator->()
+      {
+        return &dictionary_;
+      }
+    };
+  }
+
+
+  void OrthancPlugins::ApplyLookupDictionary(const void* parameters)
+  {
+    const _OrthancPluginLookupDictionary& p =
+      *reinterpret_cast<const _OrthancPluginLookupDictionary*>(parameters);
+
+    DicomTag tag(FromDcmtkBridge::ParseTag(p.name));
+    DcmTagKey tag2(tag.GetGroup(), tag.GetElement());
+
+    DictionaryReadLocker locker;
+    const DcmDictEntry* entry = NULL;
+
+    if (tag.IsPrivate())
+    {
+      // Fix issue 168 (Plugins can't read private tags from the
+      // configuration file)
+      // https://bitbucket.org/sjodogne/orthanc/issues/168/
+      std::string privateCreator;
+      {
+        OrthancConfiguration::ReaderLock lock;
+        privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
+      }
+
+      entry = locker->findEntry(tag2, privateCreator.c_str());
+    }
+    else
+    {
+      entry = locker->findEntry(tag2, NULL);
+    }
+
+    if (entry == NULL)
+    {
+      throw OrthancException(ErrorCode_UnknownDicomTag);
+    }
+    else
+    {
+      p.target->group = entry->getKey().getGroup();
+      p.target->element = entry->getKey().getElement();
+      p.target->vr = Plugins::Convert(FromDcmtkBridge::Convert(entry->getEVR()));
+      p.target->minMultiplicity = static_cast<uint32_t>(entry->getVMMin());
+      p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast<uint32_t>(entry->getVMMax()));
+    }
+  }
+
+
+  bool OrthancPlugins::InvokeSafeService(SharedLibrary& plugin,
+                                         _OrthancPluginService service,
+                                         const void* parameters)
+  {
+    // Services that can be run without mutual exclusion
+
+    switch (service)
+    {
+      case _OrthancPluginService_GetOrthancPath:
+      {
+        std::string s = SystemToolbox::GetPathToExecutable();
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
+        return true;
+      }
+
+      case _OrthancPluginService_GetOrthancDirectory:
+      {
+        std::string s = SystemToolbox::GetDirectoryOfExecutable();
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
+        return true;
+      }
+
+      case _OrthancPluginService_GetConfigurationPath:
+      {
+        std::string s;
+
+        {
+          OrthancConfiguration::ReaderLock lock;
+          s = lock.GetConfiguration().GetConfigurationAbsolutePath();
+        }
+
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
+        return true;
+      }
+
+      case _OrthancPluginService_GetConfiguration:
+      {
+        std::string s;
+
+        {
+          OrthancConfiguration::ReaderLock lock;
+          lock.GetConfiguration().Format(s);
+        }
+
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = CopyString(s);
+        return true;
+      }
+
+      case _OrthancPluginService_BufferCompression:
+        BufferCompression(parameters);
+        return true;
+
+      case _OrthancPluginService_AnswerBuffer:
+        AnswerBuffer(parameters);
+        return true;
+
+      case _OrthancPluginService_CompressAndAnswerPngImage:
+        CompressAndAnswerPngImage(parameters);
+        return true;
+
+      case _OrthancPluginService_CompressAndAnswerImage:
+        CompressAndAnswerImage(parameters);
+        return true;
+
+      case _OrthancPluginService_GetDicomForInstance:
+        GetDicomForInstance(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiGet:
+        RestApiGet(parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiGetAfterPlugins:
+        RestApiGet(parameters, true);
+        return true;
+
+      case _OrthancPluginService_RestApiGet2:
+        RestApiGet2(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiPost:
+        RestApiPostPut(true, parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiPostAfterPlugins:
+        RestApiPostPut(true, parameters, true);
+        return true;
+
+      case _OrthancPluginService_RestApiDelete:
+        RestApiDelete(parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiDeleteAfterPlugins:
+        RestApiDelete(parameters, true);
+        return true;
+
+      case _OrthancPluginService_RestApiPut:
+        RestApiPostPut(false, parameters, false);
+        return true;
+
+      case _OrthancPluginService_RestApiPutAfterPlugins:
+        RestApiPostPut(false, parameters, true);
+        return true;
+
+      case _OrthancPluginService_Redirect:
+        Redirect(parameters);
+        return true;
+
+      case _OrthancPluginService_SendUnauthorized:
+        SendUnauthorized(parameters);
+        return true;
+
+      case _OrthancPluginService_SendMethodNotAllowed:
+        SendMethodNotAllowed(parameters);
+        return true;
+
+      case _OrthancPluginService_SendHttpStatus:
+        SendHttpStatus(parameters);
+        return true;
+
+      case _OrthancPluginService_SendHttpStatusCode:
+        SendHttpStatusCode(parameters);
+        return true;
+
+      case _OrthancPluginService_SetCookie:
+        SetCookie(parameters);
+        return true;
+
+      case _OrthancPluginService_SetHttpHeader:
+        SetHttpHeader(parameters);
+        return true;
+
+      case _OrthancPluginService_SetHttpErrorDetails:
+        SetHttpErrorDetails(parameters);
+        return true;
+
+      case _OrthancPluginService_LookupPatient:
+      case _OrthancPluginService_LookupStudy:
+      case _OrthancPluginService_LookupStudyWithAccessionNumber:
+      case _OrthancPluginService_LookupSeries:
+      case _OrthancPluginService_LookupInstance:
+        LookupResource(service, parameters);
+        return true;
+
+      case _OrthancPluginService_GetInstanceRemoteAet:
+      case _OrthancPluginService_GetInstanceSize:
+      case _OrthancPluginService_GetInstanceData:
+      case _OrthancPluginService_GetInstanceJson:
+      case _OrthancPluginService_GetInstanceSimplifiedJson:
+      case _OrthancPluginService_HasInstanceMetadata:
+      case _OrthancPluginService_GetInstanceMetadata:
+      case _OrthancPluginService_GetInstanceOrigin:
+      case _OrthancPluginService_GetInstanceTransferSyntaxUid:
+      case _OrthancPluginService_HasInstancePixelData:
+        AccessDicomInstance(service, parameters);
+        return true;
+
+      case _OrthancPluginService_GetInstanceFramesCount:
+      case _OrthancPluginService_GetInstanceRawFrame:
+      case _OrthancPluginService_GetInstanceDecodedFrame:
+      case _OrthancPluginService_SerializeDicomInstance:
+      case _OrthancPluginService_GetInstanceAdvancedJson:
+      case _OrthancPluginService_GetInstanceDicomWebJson:
+      case _OrthancPluginService_GetInstanceDicomWebXml:
+        AccessDicomInstance2(service, parameters);
+        return true;
+
+      case _OrthancPluginService_SetGlobalProperty:
+      {
+        const _OrthancPluginGlobalProperty& p = 
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+        if (p.property < 1024)
+        {
+          return false;
+        }
+        else
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          lock.GetContext().GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetGlobalProperty:
+      {
+        const _OrthancPluginGlobalProperty& p = 
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+
+        std::string result;
+
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          result = lock.GetContext().GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+        }
+
+        *(p.result) = CopyString(result);
+        return true;
+      }
+
+      case _OrthancPluginService_GetExpectedDatabaseVersion:
+      {
+        const _OrthancPluginReturnSingleValue& p =
+          *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
+        *(p.resultUint32) = ORTHANC_DATABASE_VERSION;
+        return true;
+      }
+
+      case _OrthancPluginService_StartMultipartAnswer:
+      {
+        const _OrthancPluginStartMultipartAnswer& p =
+          *reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters);
+        reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->StartMultipart(p.subType, p.contentType);
+        return true;
+      }
+
+      case _OrthancPluginService_SendMultipartItem:
+        ApplySendMultipartItem(parameters);
+        return true;
+
+      case _OrthancPluginService_SendMultipartItem2:
+        ApplySendMultipartItem2(parameters);
+        return true;
+
+      case _OrthancPluginService_ReadFile:
+      {
+        const _OrthancPluginReadFile& p =
+          *reinterpret_cast<const _OrthancPluginReadFile*>(parameters);
+
+        std::string content;
+        SystemToolbox::ReadFile(content, p.path);
+        CopyToMemoryBuffer(*p.target, content.size() > 0 ? content.c_str() : NULL, content.size());
+
+        return true;
+      }
+
+      case _OrthancPluginService_WriteFile:
+      {
+        const _OrthancPluginWriteFile& p =
+          *reinterpret_cast<const _OrthancPluginWriteFile*>(parameters);
+        SystemToolbox::WriteFile(p.data, p.size, p.path);
+        return true;
+      }
+
+      case _OrthancPluginService_GetErrorDescription:
+      {
+        const _OrthancPluginGetErrorDescription& p =
+          *reinterpret_cast<const _OrthancPluginGetErrorDescription*>(parameters);
+        *(p.target) = EnumerationToString(static_cast<ErrorCode>(p.error));
+        return true;
+      }
+
+      case _OrthancPluginService_GetImagePixelFormat:
+      {
+        const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
+        *(p.resultPixelFormat) = Plugins::Convert(reinterpret_cast<const ImageAccessor*>(p.image)->GetFormat());
+        return true;
+      }
+
+      case _OrthancPluginService_GetImageWidth:
+      {
+        const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
+        *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetWidth();
+        return true;
+      }
+
+      case _OrthancPluginService_GetImageHeight:
+      {
+        const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
+        *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetHeight();
+        return true;
+      }
+
+      case _OrthancPluginService_GetImagePitch:
+      {
+        const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
+        *(p.resultUint32) = reinterpret_cast<const ImageAccessor*>(p.image)->GetPitch();
+        return true;
+      }
+
+      case _OrthancPluginService_GetImageBuffer:
+      {
+        const _OrthancPluginGetImageInfo& p = *reinterpret_cast<const _OrthancPluginGetImageInfo*>(parameters);
+        *(p.resultBuffer) = reinterpret_cast<const ImageAccessor*>(p.image)->GetBuffer();
+        return true;
+      }
+
+      case _OrthancPluginService_FreeImage:
+      {
+        const _OrthancPluginFreeImage& p = *reinterpret_cast<const _OrthancPluginFreeImage*>(parameters);
+
+        if (p.image != NULL)
+        {
+          delete reinterpret_cast<ImageAccessor*>(p.image);
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_UncompressImage:
+        UncompressImage(parameters);
+        return true;
+
+      case _OrthancPluginService_CompressImage:
+        CompressImage(parameters);
+        return true;
+
+      case _OrthancPluginService_CallHttpClient:
+        CallHttpClient(parameters);
+        return true;
+
+      case _OrthancPluginService_CallHttpClient2:
+        CallHttpClient2(parameters);
+        return true;
+
+      case _OrthancPluginService_ChunkedHttpClient:
+        ChunkedHttpClient(parameters);
+        return true;
+
+      case _OrthancPluginService_ConvertPixelFormat:
+        ConvertPixelFormat(parameters);
+        return true;
+
+      case _OrthancPluginService_GetFontsCount:
+      {
+        const _OrthancPluginReturnSingleValue& p =
+          *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
+
+        {
+          OrthancConfiguration::ReaderLock lock;
+          *(p.resultUint32) = lock.GetConfiguration().GetFontRegistry().GetSize();
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_GetFontInfo:
+        GetFontInfo(parameters);
+        return true;
+
+      case _OrthancPluginService_DrawText:
+        DrawText(parameters);
+        return true;
+
+      case _OrthancPluginService_StorageAreaCreate:
+      {
+        const _OrthancPluginStorageAreaCreate& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaCreate*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        storage.Create(p.uuid, p.content, static_cast<size_t>(p.size), Plugins::Convert(p.type));
+        return true;
+      }
+
+      case _OrthancPluginService_StorageAreaRead:
+      {
+        const _OrthancPluginStorageAreaRead& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaRead*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        std::string content;
+        storage.Read(content, p.uuid, Plugins::Convert(p.type));
+        CopyToMemoryBuffer(*p.target, content);
+        return true;
+      }
+
+      case _OrthancPluginService_StorageAreaRemove:
+      {
+        const _OrthancPluginStorageAreaRemove& p =
+          *reinterpret_cast<const _OrthancPluginStorageAreaRemove*>(parameters);
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        storage.Remove(p.uuid, Plugins::Convert(p.type));
+        return true;
+      }
+
+      case _OrthancPluginService_DicomBufferToJson:
+      case _OrthancPluginService_DicomInstanceToJson:
+        ApplyDicomToJson(service, parameters);
+        return true;
+
+      case _OrthancPluginService_CreateDicom:
+        ApplyCreateDicom(service, parameters);
+        return true;
+
+      case _OrthancPluginService_WorklistAddAnswer:
+      {
+        const _OrthancPluginWorklistAnswersOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
+        reinterpret_cast<const WorklistHandler*>(p.query)->AddAnswer(p.answers, p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistMarkIncomplete:
+      {
+        const _OrthancPluginWorklistAnswersOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistAnswersOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistIsMatch:
+      {
+        const _OrthancPluginWorklistQueryOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
+        *p.isMatch = reinterpret_cast<const WorklistHandler*>(p.query)->IsMatch(p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_WorklistGetDicomQuery:
+      {
+        const _OrthancPluginWorklistQueryOperation& p =
+          *reinterpret_cast<const _OrthancPluginWorklistQueryOperation*>(parameters);
+        reinterpret_cast<const WorklistHandler*>(p.query)->GetDicomQuery(*p.target);
+        return true;
+      }
+
+      case _OrthancPluginService_FindAddAnswer:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->Add(p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_FindMarkIncomplete:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
+        return true;
+      }
+
+      case _OrthancPluginService_GetFindQuerySize:
+      case _OrthancPluginService_GetFindQueryTag:
+      case _OrthancPluginService_GetFindQueryTagName:
+      case _OrthancPluginService_GetFindQueryValue:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<const FindHandler*>(p.query)->Invoke(service, p);
+        return true;
+      }
+
+      case _OrthancPluginService_CreateImage:
+      case _OrthancPluginService_CreateImageAccessor:
+      case _OrthancPluginService_DecodeDicomImage:
+        ApplyCreateImage(service, parameters);
+        return true;
+
+      case _OrthancPluginService_ComputeMd5:
+      case _OrthancPluginService_ComputeSha1:
+        ComputeHash(service, parameters);
+        return true;
+
+      case _OrthancPluginService_LookupDictionary:
+        ApplyLookupDictionary(parameters);
+        return true;
+
+      case _OrthancPluginService_GenerateUuid:
+      {
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = 
+          CopyString(Toolbox::GenerateUuid());
+        return true;
+      }
+
+      case _OrthancPluginService_CreateFindMatcher:
+      {
+        const _OrthancPluginCreateFindMatcher& p =
+          *reinterpret_cast<const _OrthancPluginCreateFindMatcher*>(parameters);
+        ParsedDicomFile query(p.query, p.size);
+        *(p.target) = reinterpret_cast<OrthancPluginFindMatcher*>(new HierarchicalMatcher(query));
+        return true;
+      }
+
+      case _OrthancPluginService_FreeFindMatcher:
+      {
+        const _OrthancPluginFreeFindMatcher& p =
+          *reinterpret_cast<const _OrthancPluginFreeFindMatcher*>(parameters);
+
+        if (p.matcher != NULL)
+        {
+          delete reinterpret_cast<HierarchicalMatcher*>(p.matcher);
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_FindMatcherIsMatch:
+      {
+        const _OrthancPluginFindMatcherIsMatch& p =
+          *reinterpret_cast<const _OrthancPluginFindMatcherIsMatch*>(parameters);
+
+        if (p.matcher == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else
+        {
+          ParsedDicomFile query(p.dicom, p.size);
+          *p.isMatch = reinterpret_cast<const HierarchicalMatcher*>(p.matcher)->Match(query) ? 1 : 0;
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetPeers:
+      {
+        const _OrthancPluginGetPeers& p =
+          *reinterpret_cast<const _OrthancPluginGetPeers*>(parameters);
+        *(p.peers) = reinterpret_cast<OrthancPluginPeers*>(new OrthancPeers);
+        return true;
+      }
+
+      case _OrthancPluginService_FreePeers:
+      {
+        const _OrthancPluginFreePeers& p =
+          *reinterpret_cast<const _OrthancPluginFreePeers*>(parameters);
+
+        if (p.peers != NULL)
+        {
+          delete reinterpret_cast<OrthancPeers*>(p.peers);
+        }
+        
+        return true;
+      }
+
+      case _OrthancPluginService_GetPeersCount:
+      {
+        const _OrthancPluginGetPeersCount& p =
+          *reinterpret_cast<const _OrthancPluginGetPeersCount*>(parameters);
+
+        if (p.peers == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else
+        {
+          *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeersCount();
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetPeerName:
+      {
+        const _OrthancPluginGetPeerProperty& p =
+          *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
+
+        if (p.peers == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else
+        {
+          *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerName(p.peerIndex).c_str();
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetPeerUrl:
+      {
+        const _OrthancPluginGetPeerProperty& p =
+          *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
+
+        if (p.peers == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else
+        {
+          *(p.target) = reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUrl().c_str();
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_GetPeerUserProperty:
+      {
+        const _OrthancPluginGetPeerProperty& p =
+          *reinterpret_cast<const _OrthancPluginGetPeerProperty*>(parameters);
+
+        if (p.peers == NULL ||
+            p.userProperty == NULL)
+        {
+          throw OrthancException(ErrorCode_NullPointer);
+        }
+        else
+        {
+          const WebServiceParameters::Dictionary& properties = 
+            reinterpret_cast<const OrthancPeers*>(p.peers)->GetPeerParameters(p.peerIndex).GetUserProperties();
+
+          WebServiceParameters::Dictionary::const_iterator found =
+            properties.find(p.userProperty);
+
+          if (found == properties.end())
+          {
+            *(p.target) = NULL;
+          }
+          else
+          {
+            *(p.target) = found->second.c_str();
+          }
+
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_CallPeerApi:
+        CallPeerApi(parameters);
+        return true;
+
+      case _OrthancPluginService_CreateJob:
+      {
+        const _OrthancPluginCreateJob& p =
+          *reinterpret_cast<const _OrthancPluginCreateJob*>(parameters);
+        *(p.target) = reinterpret_cast<OrthancPluginJob*>(new PluginsJob(p));
+        return true;
+      }
+
+      case _OrthancPluginService_FreeJob:
+      {
+        const _OrthancPluginFreeJob& p =
+          *reinterpret_cast<const _OrthancPluginFreeJob*>(parameters);
+
+        if (p.job != NULL)
+        {
+          delete reinterpret_cast<PluginsJob*>(p.job);
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_SubmitJob:
+      {
+        const _OrthancPluginSubmitJob& p =
+          *reinterpret_cast<const _OrthancPluginSubmitJob*>(parameters);
+
+        std::string uuid;
+
+        PImpl::ServerContextLock lock(*pimpl_);
+        lock.GetContext().GetJobsEngine().GetRegistry().Submit
+          (uuid, reinterpret_cast<PluginsJob*>(p.job), p.priority);
+        
+        *p.resultId = CopyString(uuid);
+
+        return true;
+      }
+
+      case _OrthancPluginService_AutodetectMimeType:
+      {
+        const _OrthancPluginRetrieveStaticString& p =
+          *reinterpret_cast<const _OrthancPluginRetrieveStaticString*>(parameters);
+        *p.result = EnumerationToString(SystemToolbox::AutodetectMimeType(p.argument));
+        return true;
+      }
+
+      case _OrthancPluginService_SetMetricsValue:
+      {
+        const _OrthancPluginSetMetricsValue& p =
+          *reinterpret_cast<const _OrthancPluginSetMetricsValue*>(parameters);
+
+        MetricsType type;
+        switch (p.type)
+        {
+          case OrthancPluginMetricsType_Default:
+            type = MetricsType_Default;
+            break;
+
+          case OrthancPluginMetricsType_Timer:
+            type = MetricsType_MaxOver10Seconds;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+        
+        {
+          PImpl::ServerContextLock lock(*pimpl_);
+          lock.GetContext().GetMetricsRegistry().SetValue(p.name, p.value, type);
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_EncodeDicomWebJson:
+      case _OrthancPluginService_EncodeDicomWebXml:
+      {
+        const _OrthancPluginEncodeDicomWeb& p =
+          *reinterpret_cast<const _OrthancPluginEncodeDicomWeb*>(parameters);
+
+        DicomWebBinaryFormatter formatter(p.callback);
+        formatter.Apply(p.target,
+                        (service == _OrthancPluginService_EncodeDicomWebJson),
+                        p.dicom, p.dicomSize);
+        return true;
+      }
+
+      case _OrthancPluginService_EncodeDicomWebJson2:
+      case _OrthancPluginService_EncodeDicomWebXml2:
+      {
+        const _OrthancPluginEncodeDicomWeb2& p =
+          *reinterpret_cast<const _OrthancPluginEncodeDicomWeb2*>(parameters);
+
+        DicomWebBinaryFormatter formatter(p.callback, p.payload);
+        formatter.Apply(p.target,
+                        (service == _OrthancPluginService_EncodeDicomWebJson2),
+                        p.dicom, p.dicomSize);
+        return true;
+      }
+
+      case _OrthancPluginService_GetTagName:
+        GetTagName(parameters);
+        return true;
+
+      case _OrthancPluginService_CreateDicomInstance:
+      {
+        const _OrthancPluginCreateDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
+        *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
+          new DicomInstanceFromBuffer(p.buffer, p.size));
+        return true;
+      }
+        
+      case _OrthancPluginService_FreeDicomInstance:
+      {
+        const _OrthancPluginFreeDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginFreeDicomInstance*>(parameters);
+
+        if (p.dicom != NULL)
+        {
+          IDicomInstance* obj = reinterpret_cast<IDicomInstance*>(p.dicom);
+          
+          if (obj->CanBeFreed())
+          {
+            delete obj;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_Plugin, "Cannot free a DICOM instance provided to a callback");
+          }
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_TranscodeDicomInstance:
+      {
+        const _OrthancPluginCreateDicomInstance& p =
+          *reinterpret_cast<const _OrthancPluginCreateDicomInstance*>(parameters);
+
+        DicomTransferSyntax transferSyntax;
+        if (p.transferSyntax == NULL ||
+            !LookupTransferSyntax(transferSyntax, p.transferSyntax))
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange, "Unsupported transfer syntax: " +
+                                 std::string(p.transferSyntax == NULL ? "(null)" : p.transferSyntax));
+        }
+        else
+        {
+          std::set<DicomTransferSyntax> syntaxes;
+          syntaxes.insert(transferSyntax);
+
+          IDicomTranscoder::DicomImage source;
+          source.SetExternalBuffer(p.buffer, p.size);
+
+          IDicomTranscoder::DicomImage transcoded;
+          bool success;
+          
+          {
+            PImpl::ServerContextLock lock(*pimpl_);
+            success = lock.GetContext().Transcode(
+              transcoded, source, syntaxes, true /* allow new sop */);
+          }
+
+          if (success)
+          {
+            *(p.target) = reinterpret_cast<OrthancPluginDicomInstance*>(
+              new DicomInstanceFromTranscoded(transcoded));
+            return true;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode image");
+          }
+        }
+      }
+
+      case _OrthancPluginService_CreateMemoryBuffer:
+      {
+        const _OrthancPluginCreateMemoryBuffer& p =
+          *reinterpret_cast<const _OrthancPluginCreateMemoryBuffer*>(parameters);
+
+        p.target->size = p.size;
+        
+        if (p.size == 0)
+        {
+          p.target->data = NULL;
+        }
+        else
+        {
+          p.target->data = malloc(p.size);
+        }          
+        
+        return true;
+      }
+        
+      default:
+        return false;
+    }
+  }
+
+
+
+  bool OrthancPlugins::InvokeProtectedService(SharedLibrary& plugin,
+                                              _OrthancPluginService service,
+                                              const void* parameters)
+  {
+    // Services that must be run in mutual exclusion. Guideline:
+    // Whenever "pimpl_" is directly accessed by the service, it
+    // should be listed here.
+    
+    switch (service)
+    {
+      case _OrthancPluginService_RegisterRestCallback:
+        RegisterRestCallback(parameters, true);
+        return true;
+
+      case _OrthancPluginService_RegisterRestCallbackNoLock:
+        RegisterRestCallback(parameters, false);
+        return true;
+
+      case _OrthancPluginService_RegisterChunkedRestCallback:
+        RegisterChunkedRestCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterOnStoredInstanceCallback:
+        RegisterOnStoredInstanceCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterOnChangeCallback:
+        RegisterOnChangeCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterWorklistCallback:
+        RegisterWorklistCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterFindCallback:
+        RegisterFindCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterMoveCallback:
+        RegisterMoveCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterDecodeImageCallback:
+        RegisterDecodeImageCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterTranscoderCallback:
+        RegisterTranscoderCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterJobsUnserializer:
+        RegisterJobsUnserializer(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterIncomingHttpRequestFilter:
+        RegisterIncomingHttpRequestFilter(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterIncomingHttpRequestFilter2:
+        RegisterIncomingHttpRequestFilter2(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterIncomingDicomInstanceFilter:
+        RegisterIncomingDicomInstanceFilter(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterRefreshMetricsCallback:
+        RegisterRefreshMetricsCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterStorageCommitmentScpCallback:
+        RegisterStorageCommitmentScpCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_RegisterStorageArea:
+      {
+        LOG(INFO) << "Plugin has registered a custom storage area";
+        const _OrthancPluginRegisterStorageArea& p = 
+          *reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters);
+        
+        if (pimpl_->storageArea_.get() == NULL)
+        {
+          pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_StorageAreaAlreadyRegistered);
+        }
+
+        return true;
+      }
+
+      case _OrthancPluginService_SetPluginProperty:
+      {
+        const _OrthancPluginSetPluginProperty& p = 
+          *reinterpret_cast<const _OrthancPluginSetPluginProperty*>(parameters);
+        pimpl_->properties_[std::make_pair(p.plugin, p.property)] = p.value;
+        return true;
+      }
+
+      case _OrthancPluginService_GetCommandLineArgumentsCount:
+      {
+        const _OrthancPluginReturnSingleValue& p =
+          *reinterpret_cast<const _OrthancPluginReturnSingleValue*>(parameters);
+        *(p.resultUint32) = pimpl_->argc_ - 1;
+        return true;
+      }
+
+      case _OrthancPluginService_GetCommandLineArgument:
+      {
+        const _OrthancPluginGlobalProperty& p =
+          *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
+        
+        if (p.property + 1 > pimpl_->argc_)
+        {
+          return false;
+        }
+        else
+        {
+          std::string arg = std::string(pimpl_->argv_[p.property + 1]);
+          *(p.result) = CopyString(arg);
+          return true;
+        }
+      }
+
+      case _OrthancPluginService_RegisterDatabaseBackend:
+      {
+        LOG(INFO) << "Plugin has registered a custom database back-end";
+
+        const _OrthancPluginRegisterDatabaseBackend& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters);
+
+        if (pimpl_->database_.get() == NULL)
+        {
+          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), 
+                                                            *p.backend, NULL, 0, p.payload));
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
+        }
+
+        *(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
+
+        return true;
+      }
+
+      case _OrthancPluginService_RegisterDatabaseBackendV2:
+      {
+        LOG(INFO) << "Plugin has registered a custom database back-end";
+
+        const _OrthancPluginRegisterDatabaseBackendV2& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV2*>(parameters);
+
+        if (pimpl_->database_.get() == NULL)
+        {
+          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
+                                                            *p.backend, p.extensions,
+                                                            p.extensionsSize, p.payload));
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
+        }
+
+        *(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
+
+        return true;
+      }
+
+      case _OrthancPluginService_DatabaseAnswer:
+        throw OrthancException(ErrorCode_InternalError);   // Implemented before locking (*)
+
+      case _OrthancPluginService_RegisterErrorCode:
+      {
+        const _OrthancPluginRegisterErrorCode& p =
+          *reinterpret_cast<const _OrthancPluginRegisterErrorCode*>(parameters);
+        *(p.target) = pimpl_->dictionary_.Register(plugin, p.code, p.httpStatus, p.message);
+        return true;
+      }
+
+      case _OrthancPluginService_RegisterDictionaryTag:
+      {
+        const _OrthancPluginRegisterDictionaryTag& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters);
+        FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
+                                               Plugins::Convert(p.vr), p.name,
+                                               p.minMultiplicity, p.maxMultiplicity, "");
+        return true;
+      }
+
+      case _OrthancPluginService_RegisterPrivateDictionaryTag:
+      {
+        const _OrthancPluginRegisterPrivateDictionaryTag& p =
+          *reinterpret_cast<const _OrthancPluginRegisterPrivateDictionaryTag*>(parameters);
+        FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
+                                               Plugins::Convert(p.vr), p.name,
+                                               p.minMultiplicity, p.maxMultiplicity, p.privateCreator);
+        return true;
+      }
+
+      case _OrthancPluginService_ReconstructMainDicomTags:
+      {
+        const _OrthancPluginReconstructMainDicomTags& p =
+          *reinterpret_cast<const _OrthancPluginReconstructMainDicomTags*>(parameters);
+
+        if (pimpl_->database_.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin,
+                                 "The service ReconstructMainDicomTags can only be invoked by custom database plugins");
+        }
+
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        ServerToolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level));
+
+        return true;
+      }
+
+      default:
+      {
+        // This service is unknown to the Orthanc plugin engine
+        return false;
+      }
+    }
+  }
+
+
+
+  bool OrthancPlugins::InvokeService(SharedLibrary& plugin,
+                                     _OrthancPluginService service,
+                                     const void* parameters)
+  {
+    VLOG(1) << "Calling service " << service << " from plugin " << plugin.GetPath();
+
+    if (service == _OrthancPluginService_DatabaseAnswer)
+    {
+      // This case solves a deadlock at (*) reported by James Webster
+      // on 2015-10-27 that was present in versions of Orthanc <=
+      // 0.9.4 and related to database plugins implementing a custom
+      // index. The problem was that locking the database is already
+      // ensured by the "ServerIndex" class if the invoked service is
+      // "DatabaseAnswer".
+      DatabaseAnswer(parameters);
+      return true;
+    }
+
+    if (InvokeSafeService(plugin, service, parameters))
+    {
+      // The invoked service does not require locking
+      return true;
+    }
+    else
+    {
+      // The invoked service requires locking
+      boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);   // (*)
+      return InvokeProtectedService(plugin, service, parameters);
+    }
+  }
+
+
+  bool OrthancPlugins::HasStorageArea() const
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    return pimpl_->storageArea_.get() != NULL;
+  }
+  
+  bool OrthancPlugins::HasDatabaseBackend() const
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    return pimpl_->database_.get() != NULL;
+  }
+
+
+  IStorageArea* OrthancPlugins::CreateStorageArea()
+  {
+    if (!HasStorageArea())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return pimpl_->storageArea_->Create();
+    }
+  }
+
+
+  const SharedLibrary& OrthancPlugins::GetStorageAreaLibrary() const
+  {
+    if (!HasStorageArea())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return pimpl_->storageArea_->GetSharedLibrary();
+    }
+  }
+
+
+  IDatabaseWrapper& OrthancPlugins::GetDatabaseBackend()
+  {
+    if (!HasDatabaseBackend())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *pimpl_->database_;
+    }
+  }
+
+
+  const SharedLibrary& OrthancPlugins::GetDatabaseBackendLibrary() const
+  {
+    if (!HasDatabaseBackend())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return pimpl_->database_->GetSharedLibrary();
+    }
+  }
+
+
+  const char* OrthancPlugins::GetProperty(const char* plugin,
+                                          _OrthancPluginProperty property) const
+  {
+    PImpl::Property p = std::make_pair(plugin, property);
+    PImpl::Properties::const_iterator it = pimpl_->properties_.find(p);
+
+    if (it == pimpl_->properties_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      return it->second.c_str();
+    }
+  }
+
+
+  void OrthancPlugins::SetCommandLineArguments(int argc, char* argv[])
+  {
+    if (argc < 1 || argv == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    pimpl_->argc_ = argc;
+    pimpl_->argv_ = argv;
+  }
+
+
+  PluginsManager& OrthancPlugins::GetManager()
+  {
+    return pimpl_->manager_;
+  }
+
+
+  const PluginsManager& OrthancPlugins::GetManager() const
+  {
+    return pimpl_->manager_;
+  }
+
+
+  PluginsErrorDictionary&  OrthancPlugins::GetErrorDictionary()
+  {
+    return pimpl_->dictionary_;
+  }
+
+
+  IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler()
+  {
+    if (HasWorklistHandler())
+    {
+      return new WorklistHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasWorklistHandler()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+    return pimpl_->worklistCallback_ != NULL;
+  }
+
+
+  IFindRequestHandler* OrthancPlugins::ConstructFindRequestHandler()
+  {
+    if (HasFindHandler())
+    {
+      return new FindHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasFindHandler()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
+    return pimpl_->findCallback_ != NULL;
+  }
+
+
+  IMoveRequestHandler* OrthancPlugins::ConstructMoveRequestHandler()
+  {
+    if (HasMoveHandler())
+    {
+      return new MoveHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasMoveHandler()
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    return pimpl_->moveCallbacks_.callback != NULL;
+  }
+
+
+  bool OrthancPlugins::HasCustomImageDecoder()
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+    return !pimpl_->decodeImageCallbacks_.empty();
+  }
+
+
+  bool OrthancPlugins::HasCustomTranscoder()
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+    return !pimpl_->transcoderCallbacks_.empty();
+  }
+
+
+  ImageAccessor* OrthancPlugins::Decode(const void* dicom,
+                                        size_t size,
+                                        unsigned int frame)
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    for (PImpl::DecodeImageCallbacks::const_iterator
+           decoder = pimpl_->decodeImageCallbacks_.begin();
+         decoder != pimpl_->decodeImageCallbacks_.end(); ++decoder)
+    {
+      OrthancPluginImage* pluginImage = NULL;
+      if ((*decoder) (&pluginImage, dicom, size, frame) == OrthancPluginErrorCode_Success &&
+          pluginImage != NULL)
+      {
+        return reinterpret_cast<ImageAccessor*>(pluginImage);
+      }
+    }
+
+    return NULL;
+  }
+
+  
+  bool OrthancPlugins::IsAllowed(HttpMethod method,
+                                 const char* uri,
+                                 const char* ip,
+                                 const char* username,
+                                 const IHttpHandler::Arguments& httpHeaders,
+                                 const IHttpHandler::GetArguments& getArguments)
+  {
+    OrthancPluginHttpMethod cMethod = Plugins::Convert(method);
+
+    std::vector<const char*> httpKeys(httpHeaders.size());
+    std::vector<const char*> httpValues(httpHeaders.size());
+
+    size_t pos = 0;
+    for (IHttpHandler::Arguments::const_iterator
+           it = httpHeaders.begin(); it != httpHeaders.end(); ++it, pos++)
+    {
+      httpKeys[pos] = it->first.c_str();
+      httpValues[pos] = it->second.c_str();
+    }
+
+    std::vector<const char*> getKeys(getArguments.size());
+    std::vector<const char*> getValues(getArguments.size());
+
+    for (size_t i = 0; i < getArguments.size(); i++)
+    {
+      getKeys[i] = getArguments[i].first.c_str();
+      getValues[i] = getArguments[i].second.c_str();
+    }
+
+    {
+      boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    
+      // Improved callback with support for GET arguments, since Orthanc 1.3.0
+      for (PImpl::IncomingHttpRequestFilters2::const_iterator
+             filter = pimpl_->incomingHttpRequestFilters2_.begin();
+           filter != pimpl_->incomingHttpRequestFilters2_.end(); ++filter)
+      {
+        int32_t allowed = (*filter) (cMethod, uri, ip,
+                                     httpKeys.size(),
+                                     httpKeys.empty() ? NULL : &httpKeys[0],
+                                     httpValues.empty() ? NULL : &httpValues[0],
+                                     getKeys.size(),
+                                     getKeys.empty() ? NULL : &getKeys[0],
+                                     getValues.empty() ? NULL : &getValues[0]);
+
+        if (allowed == 0)
+        {
+          return false;
+        }
+        else if (allowed != 1)
+        {
+          // The callback is only allowed to answer 0 or 1
+          throw OrthancException(ErrorCode_Plugin);
+        }
+      }
+
+      for (PImpl::IncomingHttpRequestFilters::const_iterator
+             filter = pimpl_->incomingHttpRequestFilters_.begin();
+           filter != pimpl_->incomingHttpRequestFilters_.end(); ++filter)
+      {
+        int32_t allowed = (*filter) (cMethod, uri, ip, httpKeys.size(),
+                                     httpKeys.empty() ? NULL : &httpKeys[0],
+                                     httpValues.empty() ? NULL : &httpValues[0]);
+
+        if (allowed == 0)
+        {
+          return false;
+        }
+        else if (allowed != 1)
+        {
+          // The callback is only allowed to answer 0 or 1
+          throw OrthancException(ErrorCode_Plugin);
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  IJob* OrthancPlugins::UnserializeJob(const std::string& type,
+                                       const Json::Value& value)
+  {
+    const std::string serialized = value.toStyledString();
+
+    boost::mutex::scoped_lock lock(pimpl_->jobsUnserializersMutex_);
+
+    for (PImpl::JobsUnserializers::iterator 
+           unserializer = pimpl_->jobsUnserializers_.begin();
+         unserializer != pimpl_->jobsUnserializers_.end(); ++unserializer)
+    {
+      OrthancPluginJob* job = (*unserializer) (type.c_str(), serialized.c_str());
+      if (job != NULL)
+      {
+        return reinterpret_cast<PluginsJob*>(job);
+      }
+    }
+
+    return NULL;
+  }
+
+
+  void OrthancPlugins::RefreshMetrics()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->refreshMetricsMutex_);
+
+    for (PImpl::RefreshMetricsCallbacks::iterator 
+           it = pimpl_->refreshMetricsCallbacks_.begin();
+         it != pimpl_->refreshMetricsCallbacks_.end(); ++it)
+    {
+      if (*it != NULL)
+      {
+        (*it) ();
+      }
+    }
+  }
+
+
+  class OrthancPlugins::HttpServerChunkedReader : public IHttpHandler::IChunkedRequestReader
+  {
+  private:
+    OrthancPluginServerChunkedRequestReader*  reader_;
+    _OrthancPluginChunkedRestCallback         parameters_;
+    PluginsErrorDictionary&                   errorDictionary_;
+
+  public:
+    HttpServerChunkedReader(OrthancPluginServerChunkedRequestReader* reader,
+                            const _OrthancPluginChunkedRestCallback& parameters,
+                            PluginsErrorDictionary& errorDictionary) :
+      reader_(reader),
+      parameters_(parameters),
+      errorDictionary_(errorDictionary)
+    {
+      assert(reader_ != NULL);
+    }
+
+    virtual ~HttpServerChunkedReader()
+    {
+      assert(reader_ != NULL);
+      parameters_.finalize(reader_);
+    }
+
+    virtual void AddBodyChunk(const void* data,
+                              size_t size)
+    {
+      if (static_cast<uint32_t>(size) != size)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+      }
+
+      assert(reader_ != NULL);
+      parameters_.addChunk(reader_, data, size);
+    }    
+
+    virtual void Execute(HttpOutput& output)
+    {
+      assert(reader_ != NULL);
+
+      PImpl::PluginHttpOutput pluginOutput(output);
+
+      OrthancPluginErrorCode error = parameters_.execute(
+        reader_, reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput));
+
+      pluginOutput.Close(error, errorDictionary_);
+    }
+  };
+
+
+  bool OrthancPlugins::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
+                                                  RequestOrigin origin,
+                                                  const char* remoteIp,
+                                                  const char* username,
+                                                  HttpMethod method,
+                                                  const UriComponents& uri,
+                                                  const Arguments& headers)
+  {
+    if (method != HttpMethod_Post &&
+        method != HttpMethod_Put)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    RestCallbackMatcher matcher(uri);
+
+    PImpl::ChunkedRestCallback* callback = NULL;
+
+    // Loop over the callbacks registered by the plugins
+    for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+         it != pimpl_->chunkedRestCallbacks_.end(); ++it)
+    {
+      if (matcher.IsMatch((*it)->GetRegularExpression()))
+      {
+        callback = *it;
+        break;
+      }
+    }
+
+    if (callback == NULL)
+    {
+      // Callback not found
+      return false;
+    }
+    else
+    {
+      OrthancPluginServerChunkedRequestReaderFactory handler;
+
+      switch (method)
+      {
+        case HttpMethod_Post:
+          handler = callback->GetParameters().postHandler;
+          break;
+
+        case HttpMethod_Put:
+          handler = callback->GetParameters().putHandler;
+          break;
+
+        default:
+          handler = NULL;
+          break;
+      }
+
+      if (handler == NULL)
+      {
+        return false;
+      }
+      else
+      {
+        LOG(INFO) << "Delegating chunked HTTP request to plugin for URI: " << matcher.GetFlatUri();
+
+        HttpRequestConverter converter(matcher, method, headers);
+        converter.GetRequest().body = NULL;
+        converter.GetRequest().bodySize = 0;
+
+        OrthancPluginServerChunkedRequestReader* reader = NULL;
+    
+        OrthancPluginErrorCode errorCode = handler(
+          &reader, matcher.GetFlatUri().c_str(), &converter.GetRequest());
+    
+        if (reader == NULL)
+        {
+          // The plugin has not created a reader for chunked body
+          return false;
+        }
+        else if (errorCode != OrthancPluginErrorCode_Success)
+        {
+          throw OrthancException(static_cast<ErrorCode>(errorCode));
+        }
+        else
+        {
+          target.reset(new HttpServerChunkedReader(reader, callback->GetParameters(), GetErrorDictionary()));
+          return true;
+        }
+      }
+    }
+  }
+
+
+  IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::CreateStorageCommitment(
+    const std::string& jobId,
+    const std::string& transactionUid,
+    const std::vector<std::string>& sopClassUids,
+    const std::vector<std::string>& sopInstanceUids,
+    const std::string& remoteAet,
+    const std::string& calledAet)
+  {
+    boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
+
+    for (PImpl::StorageCommitmentScpCallbacks::iterator
+           it = pimpl_->storageCommitmentScpCallbacks_.begin(); 
+         it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
+    {
+      assert(*it != NULL);
+      IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment
+        (jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
+
+      if (handler != NULL)
+      {
+        return handler;
+      }
+    } 
+    
+    return NULL;
+  }
+
+
+  class MemoryBufferRaii : public boost::noncopyable
+  {
+  private:
+    OrthancPluginMemoryBuffer  buffer_;
+
+  public:
+    MemoryBufferRaii()
+    {
+      buffer_.size = 0;
+      buffer_.data = NULL;
+    }
+
+    ~MemoryBufferRaii()
+    {
+      if (buffer_.size != 0)
+      {
+        free(buffer_.data);
+      }
+    }
+
+    OrthancPluginMemoryBuffer* GetObject()
+    {
+      return &buffer_;
+    }
+
+    void ToString(std::string& target) const
+    {
+      target.resize(buffer_.size);
+
+      if (buffer_.size != 0)
+      {
+        memcpy(&target[0], buffer_.data, buffer_.size);
+      }
+    }
+  };
+  
+
+  bool OrthancPlugins::TranscodeBuffer(std::string& target,
+                                       const void* buffer,
+                                       size_t size,
+                                       const std::set<DicomTransferSyntax>& allowedSyntaxes,
+                                       bool allowNewSopInstanceUid)
+  {
+    boost::shared_lock<boost::shared_mutex> lock(pimpl_->decoderTranscoderMutex_);
+
+    if (pimpl_->transcoderCallbacks_.empty())
+    {
+      return false;
+    }
+
+    std::vector<const char*> uids;
+    uids.reserve(allowedSyntaxes.size());
+    for (std::set<DicomTransferSyntax>::const_iterator
+           it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it)
+    {
+      uids.push_back(GetTransferSyntaxUid(*it));
+    }
+    
+    for (PImpl::TranscoderCallbacks::const_iterator
+           transcoder = pimpl_->transcoderCallbacks_.begin();
+         transcoder != pimpl_->transcoderCallbacks_.end(); ++transcoder)
+    {
+      MemoryBufferRaii a;
+
+      if ((*transcoder) (a.GetObject(), buffer, size, uids.empty() ? NULL : &uids[0],
+                         static_cast<uint32_t>(uids.size()), allowNewSopInstanceUid) ==
+          OrthancPluginErrorCode_Success)
+      {
+        a.ToString(target);
+        return true;
+      }
+    }
+
+    return false;
+  }
+}