changeset 300:b4abaeb783b1 am-callable-and-promise

messaging refactoring almost complete: works fine in native
author am@osimis.io
date Tue, 18 Sep 2018 15:23:21 +0200
parents 3897f9f28cfa
children 547e1cf7aa7b
files Applications/Samples/SimpleViewerApplication.h Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Messages/IObservable.h Framework/Messages/IObserver.h Framework/Messages/MessageForwarder.h Framework/Messages/MessageType.h Framework/SmartLoader.cpp Framework/SmartLoader.h Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Volumes/StructureSetLoader.cpp Framework/Volumes/StructureSetLoader.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/dev.h Platforms/Generic/OracleWebService.h Platforms/Generic/WebServiceCommandBase.cpp Platforms/Generic/WebServiceCommandBase.h Platforms/Generic/WebServiceGetCommand.cpp Platforms/Generic/WebServiceGetCommand.h Platforms/Generic/WebServicePostCommand.cpp Platforms/Generic/WebServicePostCommand.h Resources/CMake/OrthancStoneConfiguration.cmake UnitTestsSources/TestMessageBroker2.cpp
diffstat 30 files changed, 715 insertions(+), 1386 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewerApplication.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Tue Sep 18 15:23:21 2018 +0200
@@ -268,7 +268,7 @@
         if (parameters.count("studyId") < 1)
         {
           LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
-          orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
+          orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
         }
         else
         {
@@ -276,31 +276,31 @@
         }
       }
 
-      void OnStudyListReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
+      void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
       {
-        const Json::Value& response = message.response_;
+        const Json::Value& response = message.Response;
 
         if (response.isArray() && response.size() > 1)
         {
           SelectStudy(response[0].asString());
         }
       }
-      void OnStudyReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
+      void OnStudyReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
       {
-        const Json::Value& response = message.response_;
+        const Json::Value& response = message.Response;
 
         if (response.isObject() && response["Series"].isArray())
         {
           for (size_t i=0; i < response["Series"].size(); i++)
           {
-            orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
+            orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
           }
         }
       }
 
-      void OnSeriesReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
+      void OnSeriesReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
       {
-        const Json::Value& response = message.response_;
+        const Json::Value& response = message.Response;
 
         if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
         {
@@ -338,7 +338,7 @@
 
       void SelectStudy(const std::string& studyId)
       {
-        orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
+        orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
       }
 
       void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
--- a/Framework/Layers/ILayerSource.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Tue Sep 18 15:23:21 2018 +0200
@@ -32,66 +32,38 @@
   {
   public:
 
-    typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage;
-    typedef NoPayloadMessage<MessageType_LayerSource_GeometryError> GeometryErrorMessage;
-    typedef NoPayloadMessage<MessageType_LayerSource_ContentChanged> ContentChangedMessage;
+    typedef OriginMessage<MessageType_LayerSource_GeometryReady, ILayerSource> GeometryReadyMessage;
+    typedef OriginMessage<MessageType_LayerSource_GeometryError, ILayerSource> GeometryErrorMessage;
+    typedef OriginMessage<MessageType_LayerSource_ContentChanged, ILayerSource> ContentChangedMessage;
 
-    struct SliceChangedMessage : public IMessage
+    struct SliceChangedMessage : public OriginMessage<MessageType_LayerSource_SliceChanged, ILayerSource>
     {
       const Slice& slice_;
-      SliceChangedMessage(const Slice& slice)
-        : IMessage(MessageType_LayerSource_SliceChanged),
+      SliceChangedMessage(ILayerSource& origin, const Slice& slice)
+        : OriginMessage(origin),
           slice_(slice)
       {
       }
     };
 
-    struct LayerReadyMessage : public IMessage
+    struct LayerReadyMessage : public OriginMessage<MessageType_LayerSource_LayerReady,ILayerSource>
     {
-      std::auto_ptr<ILayerRenderer>& layer_;
+      std::auto_ptr<ILayerRenderer>& renderer_;
       const CoordinateSystem3D& slice_;
       bool isError_;
 
-      LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
+      LayerReadyMessage(ILayerSource& origin,
+                        std::auto_ptr<ILayerRenderer>& layer,
                         const CoordinateSystem3D& slice,
-                        bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
-        : IMessage(MessageType_LayerSource_LayerReady),
-          layer_(layer),
+                        bool isError  // TODO Shouldn't this be separate as NotifyLayerError?
+                        )
+        : OriginMessage(origin),
+          renderer_(layer),
           slice_(slice),
           isError_(isError)
       {
       }
     };
-
-    //    class IObserver : public boost::noncopyable
-    //    {
-    //    public:
-    //      virtual ~IObserver()
-    //      {
-    //      }
-
-    //      // Triggered as soon as the source has enough information to
-    //      // answer to "GetExtent()"
-    //      virtual void NotifyGeometryReady(const ILayerSource& source) = 0;
-
-    //      virtual void NotifyGeometryError(const ILayerSource& source) = 0;
-
-    //      // Triggered if the content of several slices in the source
-    //      // volume has changed
-    //      virtual void NotifyContentChange(const ILayerSource& source) = 0;
-
-    //      // Triggered if the content of some individual slice in the
-    //      // source volume has changed
-    //      virtual void NotifySliceChange(const ILayerSource& source,
-    //                                     const Slice& slice) = 0;
-
-    //      // The layer must be deleted by the observer that releases the
-    //      // std::auto_ptr
-    //      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
-    //                                    const ILayerSource& source,
-    //                                    const CoordinateSystem3D& slice,
-    //                                    bool isError) = 0;  // TODO Shouldn't this be separate as NotifyLayerError?
-    //    };
     
     ILayerSource(MessageBroker& broker)
       : IObservable(broker)
@@ -101,8 +73,6 @@
     {
     }
 
-    //    virtual void Register(IObserver& observer) = 0;
-
     virtual bool GetExtent(std::vector<Vector>& points,
                            const CoordinateSystem3D& viewportSlice) = 0;
 
--- a/Framework/Layers/LayerSourceBase.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -27,22 +27,22 @@
 {
   void LayerSourceBase::NotifyGeometryReady()
   {
-    EmitMessage(ILayerSource::GeometryReadyMessage());
+    EmitMessage(ILayerSource::GeometryReadyMessage(*this));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    EmitMessage(ILayerSource::GeometryErrorMessage());
+    EmitMessage(ILayerSource::GeometryErrorMessage(*this));
   }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    EmitMessage(ILayerSource::ContentChangedMessage());
+    EmitMessage(ILayerSource::ContentChangedMessage(*this));
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
   {
-    EmitMessage(ILayerSource::SliceChangedMessage(slice));
+    EmitMessage(ILayerSource::SliceChangedMessage(*this, slice));
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
@@ -50,7 +50,7 @@
                                          bool isError)
   {
     std::auto_ptr<ILayerRenderer> renderer(layer);
-    EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError));
+    EmitMessage(ILayerSource::LayerReadyMessage(*this, renderer, slice, isError));
   }
 
 }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -61,17 +61,12 @@
     LayerSourceBase::NotifyLayerReady(NULL, message.slice_.GetGeometry(), true);
   }
 
-  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
+  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, OrthancApiClient& orthanc) :
     LayerSourceBase(broker),
     IObserver(broker),
-    //OrthancSlicesLoader::ISliceLoaderObserver(broker),
     loader_(broker, orthanc),
     quality_(SliceImageQuality_FullPng)
   {
-//    DeclareHandledMessage(MessageType_SliceLoader_GeometryReady);
-//    DeclareHandledMessage(MessageType_SliceLoader_GeometryError);
-//    DeclareHandledMessage(MessageType_SliceLoader_ImageReady);
-//    DeclareHandledMessage(MessageType_SliceLoader_ImageError);
     loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceGeometryReadyMessage>(*this, &OrthancFrameLayerSource::OnSliceGeometryReady));
     loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceGeometryErrorMessage>(*this, &OrthancFrameLayerSource::OnSliceGeometryError));
     loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceImageReadyMessage>(*this, &OrthancFrameLayerSource::OnSliceImageReady));
--- a/Framework/Layers/OrthancFrameLayerSource.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Tue Sep 18 15:23:21 2018 +0200
@@ -24,6 +24,7 @@
 #include "LayerSourceBase.h"
 #include "../Toolbox/IWebService.h"
 #include "../Toolbox/OrthancSlicesLoader.h"
+#include "../Toolbox/OrthancApiClient.h"
 
 namespace OrthancStone
 {  
@@ -40,7 +41,7 @@
     SliceImageQuality    quality_;
 
   public:
-    OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc);
+    OrthancFrameLayerSource(MessageBroker& broker, OrthancApiClient& orthanc);
 
     void LoadSeries(const std::string& seriesId);
 
--- a/Framework/Messages/IObservable.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Messages/IObservable.h	Tue Sep 18 15:23:21 2018 +0200
@@ -34,17 +34,6 @@
 
 namespace OrthancStone {
 
-  class MessageNotDeclaredException : public std::logic_error
-  {
-    MessageType messageType_;
-  public:
-    MessageNotDeclaredException(MessageType messageType)
-      : std::logic_error("Message not declared by observer."),
-        messageType_(messageType)
-    {
-    }
-  };
-
   class IObservable : public boost::noncopyable
   {
   protected:
@@ -52,7 +41,6 @@
 
     typedef std::map<int, std::set<ICallable*> >   Callables;
     Callables                         callables_;
-    std::set<MessageType>             emittableMessages_;
 
   public:
 
@@ -82,11 +70,6 @@
 
     void EmitMessage(const IMessage& message)
     {
-      if (emittableMessages_.find(message.GetType()) == emittableMessages_.end())
-      {
-        throw MessageNotDeclaredException(message.GetType());
-      }
-
       Callables::const_iterator found = callables_.find(message.GetType());
 
       if (found != callables_.end())
@@ -101,16 +84,6 @@
         }
       }
     }
-    const std::set<MessageType>& GetEmittableMessages() const
-    {
-      return emittableMessages_;
-    }
-  protected:
-
-    void DeclareEmittableMessage(MessageType messageType)
-    {
-      emittableMessages_.insert(messageType);
-    }
 
   };
 
--- a/Framework/Messages/IObserver.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Messages/IObserver.h	Tue Sep 18 15:23:21 2018 +0200
@@ -34,8 +34,6 @@
   {
   protected:
     MessageBroker&                    broker_;
-    std::set<MessageType>             handledMessages_;
-    std::set<MessageType>             ignoredMessages_;
 
   public:
     IObserver(MessageBroker& broker)
@@ -48,41 +46,6 @@
     {
       broker_.Unregister(*this);
     }
-
-//    void HandleMessage_(IObservable &from, const IMessage &message)
-//    {
-//      assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling
-
-//      HandleMessage(from, message);
-//    }
-
-//    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
-
-
-//    const std::set<MessageType>& GetHandledMessages() const
-//    {
-//      return handledMessages_;
-//    }
-
-//    const std::set<MessageType>& GetIgnoredMessages() const
-//    {
-//      return ignoredMessages_;
-//    }
-
-//  protected:
-
-//    // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration)
-//    // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer
-//    void DeclareHandledMessage(MessageType messageType)
-//    {
-//      handledMessages_.insert(messageType);
-//    }
-
-//    void DeclareIgnoredMessage(MessageType messageType)
-//    {
-//      ignoredMessages_.insert(messageType);
-//    }
-
   };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageForwarder.h	Tue Sep 18 15:23:21 2018 +0200
@@ -0,0 +1,53 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ICallable.h"
+#include "IObservable.h"
+#include "IObserver.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone {
+
+
+  template<typename TMessage>
+  class MessageForwarder : public IObserver, public Callable<MessageForwarder<TMessage>, TMessage>
+  {
+    IObservable& observable_;
+  public:
+    MessageForwarder(MessageBroker& broker,
+                     IObservable& observable // the object that will emit the forwarded message
+                     )
+      : IObserver(broker),
+        Callable<MessageForwarder<TMessage>, TMessage>(*this, &MessageForwarder::ForwardMessage),
+        observable_(observable)
+    {
+    }
+
+  protected:
+    void ForwardMessage(const TMessage& message)
+    {
+      observable_.EmitMessage(message);
+    }
+  };
+}
--- a/Framework/Messages/MessageType.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Messages/MessageType.h	Tue Sep 18 15:23:21 2018 +0200
@@ -45,6 +45,7 @@
     MessageType_OrthancApi_InternalGetJsonResponseError,
 
     MessageType_OrthancApi_GenericGetJson_Ready,
+    MessageType_OrthancApi_GenericGetBinary_Ready,
     MessageType_OrthancApi_GenericHttpError_Ready,
     MessageType_OrthancApi_GetStudyIds_Ready,
     MessageType_OrthancApi_GetStudy_Ready,
--- a/Framework/SmartLoader.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/SmartLoader.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -21,6 +21,7 @@
 
 #include "SmartLoader.h"
 #include "Layers/OrthancFrameLayerSource.h"
+#include "Messages/MessageForwarder.h"
 
 namespace OrthancStone
 {
@@ -31,41 +32,6 @@
     webService_(webService),
     orthancApiClient_(broker, webService)
   {
-//    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
-//    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
-//    DeclareIgnoredMessage(MessageType_LayerSource_GeometryError);
-//    DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged);
-//    DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged);
-
-//    DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-//    DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
-  }
-
-  void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    switch (message.GetType()) {
-    case MessageType_LayerSource_GeometryReady:
-    {
-      //const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
-      // TODO keep track of objects that have been loaded already
-    }; break;
-    case MessageType_LayerSource_LayerReady:
-    {
-      //const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
-      // TODO keep track of objects that have been loaded already
-    }; break;
-//    case MessageType_OrthancApi_GetStudyIds_Ready:
-//    {
-
-//      const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast<OrthancApiClient::GetJsonResponseReadyMessage&>(message);
-
-//    }; break;
-    default:
-      VLOG("unhandled message type" << message.GetType());
-    }
-
-    // forward messages to its own observers
-    // TODO TODO TODO  IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
   }
 
   ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame)
@@ -76,9 +42,10 @@
     // - if currently loading, we need to return an object that will observe the existing LayerSource and forward
     //   the messages to its observables
     // in both cases, we must be carefull about objects lifecycle !!!
-    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_));
+    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, orthancApiClient_));
     layerSource->SetImageQuality(imageQuality_);
-    //layerSource->RegisterObserverCallback(new Callable<SmartLoader, ILayerSource::GeometryReadyMessage>(*this, &SmartLoader::....));
+    layerSource->RegisterObserverCallback(new MessageForwarder<ILayerSource::GeometryReadyMessage>(IObserver::broker_, *this));
+    layerSource->RegisterObserverCallback(new MessageForwarder<ILayerSource::LayerReadyMessage>(IObserver::broker_, *this));
     layerSource->LoadFrame(instanceId, frame);
 
     return layerSource.release();
--- a/Framework/SmartLoader.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/SmartLoader.h	Tue Sep 18 15:23:21 2018 +0200
@@ -40,8 +40,6 @@
   public:
     SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
-
     void PreloadStudy(const std::string studyId);
     void PreloadSeries(const std::string seriesId);
     void LoadStudyList();
--- a/Framework/Toolbox/IWebService.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Sep 18 15:23:21 2018 +0200
@@ -49,9 +49,9 @@
       size_t AnswerSize;
       Orthanc::IDynamicObject* Payload;
       NewHttpRequestSuccessMessage(const std::string& uri,
-                                const void* answer,
-                                size_t answerSize,
-                                Orthanc::IDynamicObject* payload)
+                                   const void* answer,
+                                   size_t answerSize,
+                                   Orthanc::IDynamicObject* payload)
         : BaseMessage(),
           Uri(uri),
           Answer(answer),
@@ -65,7 +65,7 @@
       const std::string& Uri;
       Orthanc::IDynamicObject* Payload;
       NewHttpRequestErrorMessage(const std::string& uri,
-                              Orthanc::IDynamicObject* payload)
+                                 Orthanc::IDynamicObject* payload)
         : BaseMessage(),
           Uri(uri),
           Payload(payload)
@@ -109,8 +109,6 @@
       ICallback(MessageBroker& broker)
         : IObserver(broker)
       {
-//        DeclareHandledMessage(MessageType_HttpRequestError);
-//        DeclareHandledMessage(MessageType_HttpRequestSuccess);
       }
       virtual ~ICallback()
       {
@@ -157,22 +155,18 @@
     {
     }
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    const Headers& headers,
-                                    Orthanc::IDynamicObject* payload) = 0;
-
     virtual void GetAsync(const std::string& uri,
                           const Headers& headers,
                           Orthanc::IDynamicObject* payload,
                           MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
                           MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback) = 0;
 
+    virtual void PostAsync(const std::string& uri,
+                           const Headers& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload,
+                           MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
+                           MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback) = 0;
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const Headers& headers,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload) = 0;
   };
 }
--- a/Framework/Toolbox/OrthancApiClient.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -25,193 +25,22 @@
 
 namespace OrthancStone {
 
-  struct OrthancApiClient::InternalGetJsonResponseReadyMessage :
-      public IMessage
-  {
-    OrthancApiClient::BaseRequest*  request_;
-    Json::Value   response_;
-
-    InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest*  request,
-                                        const Json::Value& response)
-      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady),
-        request_(request),
-        response_(response)
-    {
-    }
-
-  };
-
-  struct OrthancApiClient::InternalGetJsonResponseErrorMessage :
-      public IMessage
-  {
-    OrthancApiClient::BaseRequest*  request_;
-
-    InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request)
-      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError),
-        request_(request)
-    {
-    }
-  };
-
-
-  // this class handles a single request to the OrthancApiClient.
-  // Once the response is ready, it will emit a message to the responseObserver
-  // the responseObserver must handle only that message (and not all messages from the OrthancApiClient)
-  class OrthancApiClient::BaseRequest:
-      //      public IObserver,
-      public IObservable,
-      public Orthanc::IDynamicObject
-  {
-  public:
-    std::string               uri_;
-    OrthancApiClient&         orthanc_;
-    MessageType               messageToEmitWhenResponseReady_;
-    OrthancApiClient::Mode    mode_;
-
-  public:
-    BaseRequest(
-        OrthancApiClient& orthanc,
-        IObserver& responseObserver,
-        const std::string& uri,
-        MessageType messageToEmitWhenResponseReady,
-        OrthancApiClient::Mode mode)
-      :
-        //IObserver(orthanc.broker_),
-        IObservable(orthanc.broker_),
-        uri_(uri),
-        orthanc_(orthanc),
-        messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady),
-        mode_(mode)
-    {
-      // this object will emit only a single message, the one the final responseObserver is expecting
-      DeclareEmittableMessage(messageToEmitWhenResponseReady);
-
-      //      // this object is observing the OrthancApi so it must handle all messages
-      //      DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-      //      DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
-
-      //orthanc_.RegisterObserver(*this);
-      //this->RegisterObserver(responseObserver);
-    }
-    virtual ~BaseRequest() {}
-
-    //    // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver
-    //    virtual void HandleMessage(IObservable& from, const IMessage& message)
-    //    {
-    //      switch (message.GetType())
-    //      {
-    //        case MessageType_OrthancApi_InternalGetJsonResponseReady:
-    //      {
-    //        const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message);
-    //        EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_));
-    //        orthanc_.ReleaseRequest(messageReceived.request_);
-    //      }; break;
-    //      default:
-    //        throw MessageNotDeclaredException(message.GetType());
-    //      }
-    //    }
-
-  };
-
-
-  class OrthancApiClient::WebCallback : public IWebService::ICallback
-  {
-  private:
-    OrthancApiClient&  that_;
-
-  public:
-    WebCallback(MessageBroker& broker, OrthancApiClient&  that) :
-      IWebService::ICallback(broker),
-      that_(that)
-    {
-    }
-
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                                      const void* answer,
-                                      size_t answerSize,
-                                      Orthanc::IDynamicObject* payload)
-    {
-      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
-
-      switch (request->mode_)
-      {
-      case OrthancApiClient::Mode_GetJson:
-      {
-        Json::Value response;
-        if (MessagingToolbox::ParseJson(response, answer, answerSize))
-        {
-          request->EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(request->messageToEmitWhenResponseReady_, request->uri_, response));
-        }
-        else
-        {
-          //          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-          //          that_.EmitMessage(msg);
-        }
-      };  break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      that_.ReleaseRequest(request);
-    }
-
-    virtual void OnHttpRequestError(const std::string& uri,
-                                    Orthanc::IDynamicObject* payload)
-    {
-      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
-
-      switch (request->mode_)
-      {
-      case OrthancApiClient::Mode_GetJson:
-      {
-        //        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-        //        that_.EmitMessage(msg);
-        // TODO: the request shall send an error message
-      };  break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      that_.ReleaseRequest(request);
-    }
-  };
-
   OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc)
     : IObservable(broker),
-      orthanc_(orthanc),
-      webCallback_(new OrthancApiClient::WebCallback(broker, *this))
-  {
-    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
-  }
-
-  void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady)
+      orthanc_(orthanc)
   {
-    OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this,
-                                                                               responseObserver,
-                                                                               uri,
-                                                                               messageToEmitWhenResponseReady,
-                                                                               OrthancApiClient::Mode_GetJson);
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request);
-    requestsInProgress_.insert(request);
-  }
-
-  void OrthancApiClient::ReleaseRequest(BaseRequest* request)
-  {
-    requestsInProgress_.erase(request);
-    delete request;
   }
 
   // performs the translation between IWebService messages and OrthancApiClient messages
   // TODO: handle destruction of this object (with shared_ptr ?::delete_later ???)
   class HttpResponseToJsonConverter : public IObserver, IObservable
   {
-    std::auto_ptr<MessageHandler<OrthancApiClient::NewGetJsonResponseReadyMessage>> orthancApiSuccessCallback_;
-    std::auto_ptr<MessageHandler<OrthancApiClient::NewHttpErrorMessage>> orthancApiFailureCallback_;
+    std::auto_ptr<MessageHandler<OrthancApiClient::JsonResponseReadyMessage>> orthancApiSuccessCallback_;
+    std::auto_ptr<MessageHandler<OrthancApiClient::HttpErrorMessage>> orthancApiFailureCallback_;
   public:
     HttpResponseToJsonConverter(MessageBroker& broker,
-                                MessageHandler<OrthancApiClient::NewGetJsonResponseReadyMessage>* orthancApiSuccessCallback,
-                                MessageHandler<OrthancApiClient::NewHttpErrorMessage>* orthancApiFailureCallback)
+                                MessageHandler<OrthancApiClient::JsonResponseReadyMessage>* orthancApiSuccessCallback,
+                                MessageHandler<OrthancApiClient::HttpErrorMessage>* orthancApiFailureCallback)
       : IObserver(broker),
         IObservable(broker),
         orthancApiSuccessCallback_(orthancApiSuccessCallback),
@@ -226,12 +55,12 @@
       {
         if (orthancApiSuccessCallback_.get() != NULL)
         {
-          orthancApiSuccessCallback_->Apply(OrthancApiClient::NewGetJsonResponseReadyMessage(message.Uri, response));
+          orthancApiSuccessCallback_->Apply(OrthancApiClient::JsonResponseReadyMessage(message.Uri, response, message.Payload));
         }
       }
       else if (orthancApiFailureCallback_.get() != NULL)
       {
-        orthancApiFailureCallback_->Apply(OrthancApiClient::NewHttpErrorMessage(message.Uri));
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.Uri, message.Payload));
       }
 
       delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
@@ -241,7 +70,49 @@
     {
       if (orthancApiFailureCallback_.get() != NULL)
       {
-        orthancApiFailureCallback_->Apply(OrthancApiClient::NewHttpErrorMessage(message.Uri));
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.Uri));
+      }
+
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
+    }
+  };
+
+  // performs the translation between IWebService messages and OrthancApiClient messages
+  // TODO: handle destruction of this object (with shared_ptr ?::delete_later ???)
+  class HttpResponseToBinaryConverter : public IObserver, IObservable
+  {
+    std::auto_ptr<MessageHandler<OrthancApiClient::BinaryResponseReadyMessage>> orthancApiSuccessCallback_;
+    std::auto_ptr<MessageHandler<OrthancApiClient::HttpErrorMessage>> orthancApiFailureCallback_;
+  public:
+    HttpResponseToBinaryConverter(MessageBroker& broker,
+                                  MessageHandler<OrthancApiClient::BinaryResponseReadyMessage>* orthancApiSuccessCallback,
+                                  MessageHandler<OrthancApiClient::HttpErrorMessage>* orthancApiFailureCallback)
+      : IObserver(broker),
+        IObservable(broker),
+        orthancApiSuccessCallback_(orthancApiSuccessCallback),
+        orthancApiFailureCallback_(orthancApiFailureCallback)
+    {
+    }
+
+    void ConvertResponseToBinary(const IWebService::NewHttpRequestSuccessMessage& message)
+    {
+      if (orthancApiSuccessCallback_.get() != NULL)
+      {
+        orthancApiSuccessCallback_->Apply(OrthancApiClient::BinaryResponseReadyMessage(message.Uri, message.Answer, message.AnswerSize, message.Payload));
+      }
+      else if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.Uri, message.Payload));
+      }
+
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
+    }
+
+    void ConvertError(const IWebService::NewHttpRequestErrorMessage& message)
+    {
+      if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.Uri));
       }
 
       delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
@@ -249,14 +120,41 @@
   };
 
   void OrthancApiClient::GetJsonAsync(const std::string& uri,
-                                      MessageHandler<NewGetJsonResponseReadyMessage>* successCallback,
-                                      MessageHandler<NewHttpErrorMessage>* failureCallback)
+                                      MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                      MessageHandler<HttpErrorMessage>* failureCallback,
+                                      Orthanc::IDynamicObject* payload)
   {
-    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);
-    orthanc_.GetAsync(uri, IWebService::Headers(), NULL,
+    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.GetAsync(uri, IWebService::Headers(), payload,
                       new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson),
                       new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError));
 
   }
 
+  void OrthancApiClient::GetBinaryAsync(const std::string& uri,
+                                        const IWebService::Headers& headers,
+                                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                                        MessageHandler<HttpErrorMessage>* failureCallback,
+                                        Orthanc::IDynamicObject* payload)
+  {
+    HttpResponseToBinaryConverter* converter = new HttpResponseToBinaryConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.GetAsync(uri, headers, payload,
+                      new Callable<HttpResponseToBinaryConverter, IWebService::NewHttpRequestSuccessMessage>(*converter, &HttpResponseToBinaryConverter::ConvertResponseToBinary),
+                      new Callable<HttpResponseToBinaryConverter, IWebService::NewHttpRequestErrorMessage>(*converter, &HttpResponseToBinaryConverter::ConvertError));
+  }
+
+  void OrthancApiClient::PostBinaryAsyncExpectJson(const std::string& uri,
+                                                   const std::string& body,
+                                                   MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                                   MessageHandler<HttpErrorMessage>* failureCallback,
+                                                   Orthanc::IDynamicObject* payload)
+  {
+    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.PostAsync(uri, IWebService::Headers(), body, payload,
+                       new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson),
+                       new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError));
+
+  }
+
+
 }
--- a/Framework/Toolbox/OrthancApiClient.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.h	Tue Sep 18 15:23:21 2018 +0200
@@ -33,55 +33,61 @@
   class OrthancApiClient:
       public IObservable
   {
-  protected:
-    class BaseRequest;
-    class GetJsonRequest;
+  public:
 
-    struct InternalGetJsonResponseReadyMessage;
-    struct InternalGetJsonResponseErrorMessage;
-
-  public:
-    struct GetJsonResponseReadyMessage : public IMessage
+    struct JsonResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready>
     {
-      Json::Value   response_;
-      std::string   uri_;
+      Json::Value   Response;
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
 
-      GetJsonResponseReadyMessage(MessageType messageType,
-                                  const std::string& uri,
-                                  const Json::Value& response)
-        : IMessage(messageType),
-          response_(response),
-          uri_(uri)
+      JsonResponseReadyMessage(const std::string& uri,
+                               const Json::Value& response,
+                               Orthanc::IDynamicObject*  payload = NULL)
+        : BaseMessage(),
+          Response(response),
+          Uri(uri),
+          Payload(payload)
       {
       }
     };
 
-    struct NewGetJsonResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready>
+    struct HttpErrorMessage : public BaseMessage<MessageType_OrthancApi_GenericHttpError_Ready>
     {
-      Json::Value   response_;
-      std::string   uri_;
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
 
-      NewGetJsonResponseReadyMessage(const std::string& uri,
-                                     const Json::Value& response)
+      HttpErrorMessage(const std::string& uri,
+                       Orthanc::IDynamicObject*  payload = NULL)
         : BaseMessage(),
-          response_(response),
-          uri_(uri)
+          Uri(uri),
+          Payload(payload)
       {
       }
     };
 
-    struct NewHttpErrorMessage : public BaseMessage<MessageType_OrthancApi_GenericHttpError_Ready>
+    struct BinaryResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetBinary_Ready>
     {
-      std::string   uri_;
+      const void* Answer;
+      size_t AnswerSize;
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
 
-      NewHttpErrorMessage(const std::string& uri)
+      BinaryResponseReadyMessage(const std::string& uri,
+                                 const void* answer,
+                                 size_t answerSize,
+                                 Orthanc::IDynamicObject*  payload = NULL)
         : BaseMessage(),
-          uri_(uri)
+          Answer(answer),
+          AnswerSize(answerSize),
+          Uri(uri),
+          Payload(payload)
       {
       }
     };
 
 
+
   public:
 
     enum Mode
@@ -91,31 +97,43 @@
 
   protected:
     IWebService&                      orthanc_;
-    class WebCallback;
-    boost::shared_ptr<WebCallback>    webCallback_;  // This is a PImpl pattern
-    std::set<BaseRequest*>            requestsInProgress_;
-
-    //    int ScheduleGetJsonRequest(const std::string& uri);
-
-    void ReleaseRequest(BaseRequest* request);
 
   public:
     OrthancApiClient(MessageBroker& broker,
                      IWebService& orthanc);
     virtual ~OrthancApiClient() {}
 
-    // schedule a GET request expecting a JSON request.
-    // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call
-    void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady);
+    // schedule a GET request expecting a JSON response.
+    void GetJsonAsync(const std::string& uri,
+                      MessageHandler<JsonResponseReadyMessage>* successCallback,
+                      MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                      Orthanc::IDynamicObject* payload = NULL);
 
-    void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);}
-    void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);}
-    void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);}
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const std::string& contentType,
+                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL)
+    {
+      IWebService::Headers headers;
+      headers["Accept"] = contentType;
+      GetBinaryAsync(uri, headers, successCallback, failureCallback, payload);
+    }
 
-    void GetJsonAsync(const std::string& uri,
-                      MessageHandler<NewGetJsonResponseReadyMessage>* successCallback,
-                      MessageHandler<NewHttpErrorMessage>* failureCallback = NULL);
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const IWebService::Headers& headers,
+                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL);
 
+    // schedule a POST request expecting a JSON response.
+    void PostBinaryAsyncExpectJson(const std::string& uri,
+                                   const std::string& body,
+                                   MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                   MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                                   Orthanc::IDynamicObject* payload = NULL);
 
 
   };
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -79,32 +79,32 @@
     const Slice*       slice_;
     std::string        instanceId_;
     SliceImageQuality  quality_;
-    
+
     Operation(Mode mode) :
       mode_(mode)
     {
     }
-    
+
   public:
     Mode GetMode() const
     {
       return mode_;
     }
-    
+
     SliceImageQuality GetQuality() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return quality_;
     }
-    
+
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return sliceIndex_;
     }
-    
+
     const Slice& GetSlice() const
     {
       assert(mode_ == Mode_LoadImage ||
@@ -112,7 +112,7 @@
       assert(slice_ != NULL);
       return *slice_;
     }
-    
+
     unsigned int GetFrame() const
     {
       assert(mode_ == Mode_FrameGeometry);
@@ -126,18 +126,13 @@
       return instanceId_;
     }
 
-    static Operation* DownloadSeriesGeometry()
-    {
-      return new Operation(Mode_SeriesGeometry);
-    }
-    
     static Operation* DownloadInstanceGeometry(const std::string& instanceId)
     {
       std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
       operation->instanceId_ = instanceId;
       return operation.release();
     }
-    
+
     static Operation* DownloadFrameGeometry(const std::string& instanceId,
                                             unsigned int frame)
     {
@@ -146,7 +141,7 @@
       operation->frame_ = frame;
       return operation.release();
     }
-    
+
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
                                          const Slice&  slice,
                                          SliceImageQuality quality)
@@ -157,7 +152,7 @@
       tmp->quality_ = quality;
       return tmp.release();
     }
-    
+
     static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
                                             const Slice&  slice)
     {
@@ -177,107 +172,6 @@
 
   };
 
-  
-  class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
-  {
-  private:
-    OrthancSlicesLoader&  that_;
-    
-  public:
-    WebCallback(MessageBroker& broker, OrthancSlicesLoader&  that) :
-      IWebService::ICallback(broker),
-      that_(that)
-    {
-    }
-    
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                                      const void* answer,
-                                      size_t answerSize,
-                                      Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      
-      switch (operation->GetMode())
-      {
-      case Mode_SeriesGeometry:
-        that_.ParseSeriesGeometry(answer, answerSize);
-        break;
-        
-      case Mode_InstanceGeometry:
-        that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
-        break;
-        
-      case Mode_FrameGeometry:
-        that_.ParseFrameGeometry(operation->GetInstanceId(),
-                                 operation->GetFrame(), answer, answerSize);
-        break;
-        
-      case Mode_LoadImage:
-        switch (operation->GetQuality())
-        {
-        case SliceImageQuality_FullPng:
-          that_.ParseSliceImagePng(*operation, answer, answerSize);
-          break;
-        case SliceImageQuality_FullPam:
-          that_.ParseSliceImagePam(*operation, answer, answerSize);
-          break;
-
-        case SliceImageQuality_Jpeg50:
-        case SliceImageQuality_Jpeg90:
-        case SliceImageQuality_Jpeg95:
-          that_.ParseSliceImageJpeg(*operation, answer, answerSize);
-          break;
-          
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        break;
-        
-      case Mode_LoadRawImage:
-        that_.ParseSliceRawImage(*operation, answer, answerSize);
-        break;
-        
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    
-    virtual void OnHttpRequestError(const std::string& uri,
-                                    Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      LOG(ERROR) << "Cannot download " << uri;
-      
-      switch (operation->GetMode())
-      {
-      case Mode_FrameGeometry:
-      case Mode_SeriesGeometry:
-        that_.EmitMessage(SliceGeometryErrorMessage(that_));
-        that_.state_ = State_Error;
-        break;
-        
-      case Mode_LoadImage:
-      {
-        OrthancSlicesLoader::SliceImageErrorMessage msg(operation->GetSliceIndex(),
-                                   operation->GetSlice(),
-                                   operation->GetQuality());
-        that_.EmitMessage(msg);
-      }; break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-  };
-  
-//  void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message)
-//  {
-//    // forward messages to its own observers
-//    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
-//  }
-
-  
   void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
                                                     std::auto_ptr<Orthanc::ImageAccessor>& image)
   {
@@ -330,18 +224,21 @@
     }
   }
   
-  
-  void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
-                                                size_t size)
+  void OrthancSlicesLoader::OnGeometryError(const OrthancApiClient::HttpErrorMessage& message)
+  {
+    EmitMessage(SliceGeometryErrorMessage(*this));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::OnSliceImageError(const OrthancApiClient::HttpErrorMessage& message)
   {
-    Json::Value series;
-    if (!MessagingToolbox::ParseJson(series, answer, size) ||
-        series.type() != Json::objectValue)
-    {
-      EmitMessage(SliceGeometryErrorMessage(*this));
-      return;
-    }
-    
+    NotifySliceImageError(dynamic_cast<const Operation&>(*(message.Payload)));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    Json::Value series = message.Response;
     Json::Value::Members instances = series.getMemberNames();
     
     slices_.Reserve(instances.size());
@@ -376,19 +273,11 @@
     SortAndFinalizeSlices();
   }
   
-  
-  void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
-                                                  const void* answer,
-                                                  size_t size)
+  void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value tags;
-    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
-        tags.type() != Json::objectValue)
-    {
-      EmitMessage(SliceGeometryErrorMessage(*this));
-      return;
-    }
-    
+    Json::Value tags = message.Response;
+    const std::string& instanceId = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetInstanceId();
+
     OrthancPlugins::FullOrthancDataset dataset(tags);
     
     Orthanc::DicomMap dicom;
@@ -421,19 +310,12 @@
   }
   
   
-  void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
-                                               unsigned int frame,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value tags;
-    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
-        tags.type() != Json::objectValue)
-    {
-      EmitMessage(SliceGeometryErrorMessage(*this));
-      return;
-    }
-    
+    Json::Value tags = message.Response;
+    const std::string& instanceId = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetInstanceId();
+    unsigned int frame = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetFrame();
+
     OrthancPlugins::FullOrthancDataset dataset(tags);
     
     state_ = State_GeometryReady;
@@ -446,7 +328,7 @@
     {
       LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      EmitMessage(SliceGeometryErrorMessage(*this));
+      EmitMessage(SliceGeometryReadyMessage(*this));
     }
     else
     {
@@ -456,16 +338,15 @@
   }
   
   
-  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     std::auto_ptr<Orthanc::ImageAccessor>  image;
     
     try
     {
       image.reset(new Orthanc::PngReader);
-      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size);
+      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.Answer, message.AnswerSize);
     }
     catch (Orthanc::OrthancException&)
     {
@@ -497,16 +378,15 @@
     NotifySliceImageSuccess(operation, image);
   }
   
-  void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     std::auto_ptr<Orthanc::ImageAccessor>  image;
 
     try
     {
       image.reset(new Orthanc::PamReader);
-      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(answer), size));
+      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(message.Answer), message.AnswerSize));
     }
     catch (Orthanc::OrthancException&)
     {
@@ -539,13 +419,12 @@
   }
 
 
-  void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
-                                                const void* answer,
-                                                size_t size)
+  void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value encoded;
-    if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
-        encoded.type() != Json::objectValue ||
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
+
+    Json::Value encoded = message.Response;
+    if (encoded.type() != Json::objectValue ||
         !encoded.isMember("Orthanc") ||
         encoded["Orthanc"].type() != Json::objectValue)
     {
@@ -714,14 +593,13 @@
     }
   };
   
-  void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     Orthanc::GzipCompressor compressor;
     
     std::string raw;
-    compressor.Uncompress(raw, answer, size);
+    compressor.Uncompress(raw, message.Answer, message.AnswerSize);
     
     const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation();
     
@@ -776,18 +654,12 @@
   
   
   OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
-                                           //ISliceLoaderObserver& callback,
-                                           IWebService& orthanc) :
+                                           OrthancApiClient& orthanc) :
     IObservable(broker),
-    webCallback_(new WebCallback(broker, *this)),
-    //userCallback_(callback),
+    IObserver(broker),
     orthanc_(orthanc),
     state_(State_Initialization)
   {
-    DeclareEmittableMessage(MessageType_SliceLoader_GeometryReady);
-    DeclareEmittableMessage(MessageType_SliceLoader_GeometryError);
-    DeclareEmittableMessage(MessageType_SliceLoader_ImageError);
-    DeclareEmittableMessage(MessageType_SliceLoader_ImageReady);
   }
   
   
@@ -800,12 +672,13 @@
     else
     {
       state_ = State_LoadingGeometry;
-      std::string uri = "/series/" + seriesId + "/instances-tags";
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry());
+      orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            NULL);
     }
   }
   
-  
   void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
   {
     if (state_ != State_Initialization)
@@ -818,9 +691,10 @@
       
       // Tag "3004-000c" is "Grid Frame Offset Vector", which is
       // mandatory to read RT DOSE, but is too long to be returned by default
-      std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
-      orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId));
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
@@ -835,9 +709,11 @@
     else
     {
       state_ = State_LoadingGeometry;
-      std::string uri = "/instances/" + instanceId + "/tags";
-      orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame));
+
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
@@ -906,10 +782,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
-    IWebService::Headers headers;
-    headers["Accept"] = "image/png";
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
+    orthanc_.GetBinaryAsync(uri, "image/png",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImagePng),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                            Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
   }
   
   void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
@@ -936,10 +812,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    IWebService::Headers headers;
-    headers["Accept"] = "image/x-portable-arbitrarymap";
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, headers,
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
+    orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImagePam),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                            Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
   }
 
 
@@ -974,8 +850,10 @@
                        "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
 
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                Operation::DownloadSliceImage(index, slice, quality));
+    orthanc_.GetJsonAsync(uri,
+                          new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
+                          new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                          Operation::DownloadSliceImage(index, slice, quality));
   }
   
   
@@ -1008,8 +886,10 @@
     {
       std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                  Operation::DownloadSliceRawImage(index, slice));
+      orthanc_.GetBinaryAsync(uri, IWebService::Headers(),
+                              new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceRawImage),
+                              new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                              Operation::DownloadSliceRawImage(index, slice));
     }
   }
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Sep 18 15:23:21 2018 +0200
@@ -26,10 +26,11 @@
 #include "../StoneEnumerations.h"
 #include "../Messages/IObservable.h"
 #include <boost/shared_ptr.hpp>
+#include "OrthancApiClient.h"
 
 namespace OrthancStone
 {
-  class OrthancSlicesLoader : public IObservable
+  class OrthancSlicesLoader : public IObservable, public IObserver
   {
   public:
 
@@ -93,11 +94,8 @@
     };
 
     class Operation;
-    class WebCallback;
 
-    boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
-
-    IWebService&  orthanc_;
+    OrthancApiClient&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
 
@@ -105,34 +103,23 @@
                                  std::auto_ptr<Orthanc::ImageAccessor>& image);
   
     void NotifySliceImageError(const Operation& operation);
-    
-    void ParseSeriesGeometry(const void* answer,
-                             size_t size);
+
+    void OnGeometryError(const OrthancApiClient::HttpErrorMessage& message);
+    void OnSliceImageError(const OrthancApiClient::HttpErrorMessage& message);
 
-    void ParseInstanceGeometry(const std::string& instanceId,
-                               const void* answer,
-                               size_t size);
+    void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseFrameGeometry(const std::string& instanceId,
-                            unsigned int frame,
-                            const void* answer,
-                            size_t size);
+    void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseSliceImagePng(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseSliceImagePam(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
-    void ParseSliceImageJpeg(const Operation& operation,
-                             const void* answer,
-                             size_t size);
+    void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
-    void ParseSliceRawImage(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
     void ScheduleSliceImagePng(const Slice& slice,
                                size_t index);
@@ -149,7 +136,7 @@
   public:
     OrthancSlicesLoader(MessageBroker& broker,
                         //ISliceLoaderObserver& callback,
-                        IWebService& orthanc);
+                        OrthancApiClient& orthancApi);
 
     void ScheduleLoadSeries(const std::string& seriesId);
 
@@ -170,6 +157,6 @@
     void ScheduleLoadSliceImage(size_t index,
                                 SliceImageQuality requestedQuality);
 
-//    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
   };
 }
--- a/Framework/Volumes/StructureSetLoader.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -27,131 +27,63 @@
 
 namespace OrthancStone
 {
-  class StructureSetLoader::Operation : public Orthanc::IDynamicObject
-  {
-  public:
-    enum Type
-    {
-      Type_LoadStructureSet,
-      Type_LookupSopInstanceUid,
-      Type_LoadReferencedSlice
-    };
-    
-  private:
-    Type         type_;
-    std::string  value_;
-
-  public:
-    Operation(Type type,
-              const std::string& value) :
-      type_(type),
-      value_(value)
-    {
-    }
-
-    Type GetType() const
-    {
-      return type_;
-    }
-
-    const std::string& GetIdentifier() const
-    {
-      return value_;
-    }
-  };
-
-
-  void StructureSetLoader::OnHttpRequestError(const std::string& uri,
-                                       Orthanc::IDynamicObject* payload)
-  {
-    // TODO
-  }
-
   
-  void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri,
-                                         const void* answer,
-                                         size_t answerSize,
-                                         Orthanc::IDynamicObject* payload)
-  {
-    std::auto_ptr<Operation> op(dynamic_cast<Operation*>(payload));
-
-    switch (op->GetType())
-    {
-      case Operation::Type_LoadStructureSet:
-      {
-        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
-        structureSet_.reset(new DicomStructureSet(dataset));
-
-        std::set<std::string> instances;
-        structureSet_->GetReferencedInstances(instances);
-
-        for (std::set<std::string>::const_iterator it = instances.begin();
-             it != instances.end(); ++it)
-        {
-          orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it,
-                                       new Operation(Operation::Type_LookupSopInstanceUid, *it));
-        }
-        
-        VolumeLoaderBase::NotifyGeometryReady();
-
-        break;
-      }
-        
-      case Operation::Type_LookupSopInstanceUid:
-      {
-        Json::Value lookup;
-        
-        if (MessagingToolbox::ParseJson(lookup, answer, answerSize))
-        {
-          if (lookup.type() != Json::arrayValue ||
-              lookup.size() != 1 ||
-              !lookup[0].isMember("Type") ||
-              !lookup[0].isMember("Path") ||
-              lookup[0]["Type"].type() != Json::stringValue ||
-              lookup[0]["ID"].type() != Json::stringValue ||
-              lookup[0]["Type"].asString() != "Instance")
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
-          }
-
-          const std::string& instance = lookup[0]["ID"].asString();
-          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(),
-                                      new Operation(Operation::Type_LoadReferencedSlice, instance));
-        }
-        else
-        {
-          // TODO
-        }
-        
-        break;
-      }
-
-      case Operation::Type_LoadReferencedSlice:
-      {
-        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
-
-        Orthanc::DicomMap slice;
-        MessagingToolbox::ConvertDataset(slice, dataset);
-        structureSet_->AddReferencedSlice(slice);
-
-        VolumeLoaderBase::NotifyContentChange();
-
-        break;
-      }
-      
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  } 
-
-  
-  StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) :
-    IWebService::ICallback(broker),
+  StructureSetLoader::StructureSetLoader(MessageBroker& broker, OrthancApiClient& orthanc) :
+    OrthancStone::IObserver(broker),
     orthanc_(orthanc)
   {
   }
   
 
+  void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.Response);
+
+    Orthanc::DicomMap slice;
+    MessagingToolbox::ConvertDataset(slice, dataset);
+    structureSet_->AddReferencedSlice(slice);
+
+    VolumeLoaderBase::NotifyContentChange();
+  }
+
+  void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.Response);
+    structureSet_.reset(new DicomStructureSet(dataset));
+
+    std::set<std::string> instances;
+    structureSet_->GetReferencedInstances(instances);
+
+    for (std::set<std::string>::const_iterator it = instances.begin();
+         it != instances.end(); ++it)
+    {
+      orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it,
+                            new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted));
+    }
+
+    VolumeLoaderBase::NotifyGeometryReady();
+  }
+
+  void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    Json::Value lookup = message.Response;
+
+    if (lookup.type() != Json::arrayValue ||
+        lookup.size() != 1 ||
+        !lookup[0].isMember("Type") ||
+        !lookup[0].isMember("Path") ||
+        lookup[0]["Type"].type() != Json::stringValue ||
+        lookup[0]["ID"].type() != Json::stringValue ||
+        lookup[0]["Type"].asString() != "Instance")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    const std::string& instance = lookup[0]["ID"].asString();
+    orthanc_.GetJsonAsync("/instances/" + instance + "/tags",
+                          new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded));
+  }
+
   void StructureSetLoader::ScheduleLoadInstance(const std::string& instance)
   {
     if (structureSet_.get() != NULL)
@@ -160,8 +92,8 @@
     }
     else
     {
-      const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050";
-      orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance));
+      orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050",
+                            new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded));
     }
   }
 
--- a/Framework/Volumes/StructureSetLoader.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.h	Tue Sep 18 15:23:21 2018 +0200
@@ -22,31 +22,22 @@
 #pragma once
 
 #include "../Toolbox/DicomStructureSet.h"
-#include "../Toolbox/IWebService.h"
+#include "../Toolbox/OrthancApiClient.h"
 #include "VolumeLoaderBase.h"
 
 namespace OrthancStone
 {
   class StructureSetLoader :
     public VolumeLoaderBase,
-    private IWebService::ICallback
+    public OrthancStone::IObserver
   {
   private:
-    class Operation;
-    
-    virtual void OnHttpRequestError(const std::string& uri,
-                             Orthanc::IDynamicObject* payload);
 
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                               const void* answer,
-                               size_t answerSize,
-                               Orthanc::IDynamicObject* payload);
-
-    IWebService&                      orthanc_;
+    OrthancApiClient&                      orthanc_;
     std::auto_ptr<DicomStructureSet>  structureSet_;
 
   public:
-    StructureSetLoader(MessageBroker& broker, IWebService& orthanc);
+    StructureSetLoader(MessageBroker& broker, OrthancApiClient& orthanc);
 
     void ScheduleLoadInstance(const std::string& instance);
 
@@ -56,5 +47,12 @@
     }
 
     DicomStructureSet& GetStructureSet();
+
+  protected:
+    void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message);
   };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -386,6 +386,15 @@
     }
   }
   
+  void LayerWidget::ObserveLayer(ILayerSource& layer)
+  {
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryReadyMessage>(*this, &LayerWidget::OnGeometryReady));
+    // currently ignore errors layer->RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryErrorMessage>(*this, &LayerWidget::...));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::SliceChangedMessage>(*this, &LayerWidget::OnSliceChanged));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::ContentChangedMessage>(*this, &LayerWidget::OnContentChanged));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::LayerReadyMessage>(*this, &LayerWidget::OnLayerReady));
+  }
+
 
   size_t LayerWidget::AddLayer(ILayerSource* layer)  // Takes ownership
   {
@@ -400,7 +409,8 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-//    layer->RegisterObserver(*this);
+
+    ObserveLayer(*layer);
 
     ResetChangedLayers();
 
@@ -424,7 +434,8 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-//    layer->RegisterObserver(*this);
+
+    ObserveLayer(*layer);
 
     InvalidateLayer(index);
   }
@@ -491,38 +502,10 @@
     }
   }
 
-  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    switch (message.GetType()) {
-    case MessageType_LayerSource_GeometryReady:
-      OnGeometryReady(dynamic_cast<const ILayerSource&>(from));
-      break;
-    case MessageType_LayerSource_GeometryError:
-      LOG(ERROR) << "Cannot get geometry";
-      break;
-    case MessageType_LayerSource_ContentChanged:
-      OnContentChanged(dynamic_cast<const ILayerSource&>(from));
-      break;
-    case MessageType_LayerSource_SliceChanged:
-      OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
-      break;
-    case MessageType_LayerSource_LayerReady:
-    {
-      const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
-      OnLayerReady(layerReadyMessage.layer_,
-                   dynamic_cast<const ILayerSource&>(from),
-                   layerReadyMessage.slice_,
-                   layerReadyMessage.isError_);
-    }; break;
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-
-  void LayerWidget::OnGeometryReady(const ILayerSource& source)
+  void LayerWidget::OnGeometryReady(const ILayerSource::GeometryReadyMessage& message)
   {
     size_t i;
-    if (LookupLayer(i, source))
+    if (LookupLayer(i, message.origin_))
     {
       LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
 
@@ -558,23 +541,22 @@
   }
 
 
-  void LayerWidget::OnContentChanged(const ILayerSource& source)
+  void LayerWidget::OnContentChanged(const ILayerSource::ContentChangedMessage& message)
   {
     size_t index;
-    if (LookupLayer(index, source))
+    if (LookupLayer(index, message.origin_))
     {
       InvalidateLayer(index);
     }
   }
   
 
-  void LayerWidget::OnSliceChanged(const ILayerSource& source,
-                                   const Slice& slice)
+  void LayerWidget::OnSliceChanged(const ILayerSource::SliceChangedMessage& message)
   {
-    if (slice.ContainsPlane(slice_))
+    if (message.slice_.ContainsPlane(slice_))
     {
       size_t index;
-      if (LookupLayer(index, source))
+      if (LookupLayer(index, message.origin_))
       {
         InvalidateLayer(index);
       }
@@ -582,15 +564,12 @@
   }
   
   
-  void LayerWidget::OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                 const ILayerSource& source,
-                                 const CoordinateSystem3D& slice,
-                                 bool isError)
+  void LayerWidget::OnLayerReady(const ILayerSource::LayerReadyMessage& message)
   {
     size_t index;
-    if (LookupLayer(index, source))
+    if (LookupLayer(index, message.origin_))
     {
-      if (isError)
+      if (message.isError_)
       {
         LOG(ERROR) << "Using error renderer on layer " << index;
       }
@@ -599,11 +578,11 @@
         LOG(INFO) << "Renderer ready for layer " << index;
       }
       
-      if (renderer.get() != NULL)
+      if (message.renderer_.get() != NULL)
       {
-        UpdateLayer(index, renderer.release(), slice);
+        UpdateLayer(index, message.renderer_.release(), message.slice_);
       }
-      else if (isError)
+      else if (message.isError_)
       {
         // TODO
         //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
--- a/Framework/Widgets/LayerWidget.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Tue Sep 18 15:23:21 2018 +0200
@@ -59,26 +59,21 @@
     void GetLayerExtent(Extent2D& extent,
                         ILayerSource& source) const;
 
-    void OnGeometryReady(const ILayerSource& source);
+    void OnGeometryReady(const ILayerSource::GeometryReadyMessage& message);
 
-    virtual void OnContentChanged(const ILayerSource& source);
+    virtual void OnContentChanged(const ILayerSource::ContentChangedMessage& message);
 
-    virtual void OnSliceChanged(const ILayerSource& source,
-                                const Slice& slice);
+    virtual void OnSliceChanged(const ILayerSource::SliceChangedMessage& message);
 
-    virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                              const ILayerSource& source,
-                              const CoordinateSystem3D& slice,
-                              bool isError);
+    virtual void OnLayerReady(const ILayerSource::LayerReadyMessage& message);
 
+    void ObserveLayer(ILayerSource& source);
 
     void ResetChangedLayers();
 
   public:
     LayerWidget(MessageBroker& broker, const std::string& name);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
-
     virtual Extent2D GetSceneExtent();
 
   protected:
--- a/Framework/dev.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Framework/dev.h	Tue Sep 18 15:23:21 2018 +0200
@@ -232,7 +232,7 @@
 
   public:
     OrthancVolumeImage(MessageBroker& broker,
-                       IWebService& orthanc,
+                       OrthancApiClient& orthanc,
                        bool computeRange) : 
       OrthancStone::IObserver(broker),
       loader_(broker, orthanc),
--- a/Platforms/Generic/OracleWebService.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Tue Sep 18 15:23:21 2018 +0200
@@ -51,30 +51,23 @@
     {
     }
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    const Headers& headers,
-                                    Orthanc::IDynamicObject* payload)
-    {
-      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload, context_));
-    }
-
     virtual void GetAsync(const std::string& uri,
                           const Headers& headers,
-                          Orthanc::IDynamicObject* payload,
+                          Orthanc::IDynamicObject* payload, // takes ownership
                           MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,   // takes ownership
                           MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback = NULL)// takes ownership
     {
-      oracle_.Submit(new NewWebServiceGetCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, payload, context_));
+      oracle_.Submit(new WebServiceGetCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, payload, context_));
     }
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const Headers& headers,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload)
+    virtual void PostAsync(const std::string& uri,
+                           const Headers& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload, // takes ownership
+                           MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback, // takes ownership
+                           MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback) // takes ownership
     {
-      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload, context_));
+      oracle_.Submit(new WebServicePostCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, body, payload, context_));
     }
 
     void Start()
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -26,45 +26,8 @@
 namespace OrthancStone
 {
   WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker,
-                                               IWebService::ICallback& callback,
-                                               const Orthanc::WebServiceParameters& parameters,
-                                               const std::string& uri,
-                                               const IWebService::Headers& headers,
-                                               Orthanc::IDynamicObject* payload /* takes ownership */,
-                                               NativeStoneApplicationContext& context) :
-    IObservable(broker),
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    headers_(headers),
-    payload_(payload),
-    context_(context)
-  {
-    DeclareEmittableMessage(MessageType_HttpRequestError);
-    DeclareEmittableMessage(MessageType_HttpRequestSuccess);
-    // TODO ? RegisterObserver(callback);
-  }
-
-
-  void WebServiceCommandBase::Commit()
-  {
-    NativeStoneApplicationContext::GlobalMutexLocker lock(context_);  // we want to make sure that, i.e, the UpdateThread is not triggered while we are updating the "model" with the result of a WebServiceCommand
-
-    if (success_)
-    {
-      IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release());
-      EmitMessage(message);
-    }
-    else
-    {
-      IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release());
-      EmitMessage(message);
-    }
-  }
-
-  NewWebServiceCommandBase::NewWebServiceCommandBase(MessageBroker& broker,
-                                                     MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
-                                                     MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,
+                                               MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
+                                               MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
                                                const IWebService::Headers& headers,
@@ -82,7 +45,7 @@
   }
 
 
-  void NewWebServiceCommandBase::Commit()
+  void WebServiceCommandBase::Commit()
   {
     NativeStoneApplicationContext::GlobalMutexLocker lock(context_);  // we want to make sure that, i.e, the UpdateThread is not triggered while we are updating the "model" with the result of a WebServiceCommand
 
--- a/Platforms/Generic/WebServiceCommandBase.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.h	Tue Sep 18 15:23:21 2018 +0200
@@ -37,32 +37,6 @@
   class WebServiceCommandBase : public IOracleCommand, IObservable
   {
   protected:
-    IWebService::ICallback&                 callback_;
-    Orthanc::WebServiceParameters           parameters_;
-    std::string                             uri_;
-    std::map<std::string, std::string>      headers_;
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-    bool                                    success_;
-    std::string                             answer_;
-    NativeStoneApplicationContext&          context_;
-
-  public:
-    WebServiceCommandBase(MessageBroker& broker,
-                          IWebService::ICallback& callback,
-                          const Orthanc::WebServiceParameters& parameters,
-                          const std::string& uri,
-                          const std::map<std::string, std::string>& headers,
-                          Orthanc::IDynamicObject* payload /* takes ownership */,
-                          NativeStoneApplicationContext& context);
-
-    virtual void Execute() = 0;
-
-    virtual void Commit();
-  };
-
-  class NewWebServiceCommandBase : public IOracleCommand, IObservable
-  {
-  protected:
     std::auto_ptr<MessageHandler<IWebService::NewHttpRequestSuccessMessage>>                              successCallback_;
     std::auto_ptr<MessageHandler<IWebService::NewHttpRequestErrorMessage>>                                failureCallback_;
     Orthanc::WebServiceParameters           parameters_;
@@ -74,7 +48,7 @@
     NativeStoneApplicationContext&          context_;
 
   public:
-    NewWebServiceCommandBase(MessageBroker& broker,
+    WebServiceCommandBase(MessageBroker& broker,
                           MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
                           MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -13,7 +13,7 @@
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Affero General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
@@ -25,14 +25,16 @@
 
 namespace OrthancStone
 {
+
   WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker,
-                                             IWebService::ICallback& callback,
+                                             MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
+                                             MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
                                              const IWebService::Headers& headers,
                                              Orthanc::IDynamicObject* payload /* takes ownership */,
                                              NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context)
+    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, payload, context)
   {
   }
 
@@ -51,31 +53,4 @@
     success_ = client.Apply(answer_);
   }
 
-  NewWebServiceGetCommand::NewWebServiceGetCommand(MessageBroker& broker,
-                                                   MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
-                                                   MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
-                                             const Orthanc::WebServiceParameters& parameters,
-                                             const std::string& uri,
-                                             const IWebService::Headers& headers,
-                                             Orthanc::IDynamicObject* payload /* takes ownership */,
-                                             NativeStoneApplicationContext& context) :
-    NewWebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, payload, context)
-  {
-  }
-
-
-  void NewWebServiceGetCommand::Execute()
-  {
-    Orthanc::HttpClient client(parameters_, uri_);
-    client.SetTimeout(60);
-    client.SetMethod(Orthanc::HttpMethod_Get);
-
-    for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
-    {
-      client.AddHeader(it->first, it->second);
-    }
-
-    success_ = client.Apply(answer_);
-  }
-
 }
--- a/Platforms/Generic/WebServiceGetCommand.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Tue Sep 18 15:23:21 2018 +0200
@@ -29,7 +29,8 @@
   {
   public:
     WebServiceGetCommand(MessageBroker& broker,
-                         IWebService::ICallback& callback,
+                         MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
+                         MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
                          const IWebService::Headers& headers,
@@ -39,19 +40,4 @@
     virtual void Execute();
   };
 
-  class NewWebServiceGetCommand : public NewWebServiceCommandBase
-  {
-  public:
-    NewWebServiceGetCommand(MessageBroker& broker,
-                            MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
-                            MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
-                            const Orthanc::WebServiceParameters& parameters,
-                            const std::string& uri,
-                            const IWebService::Headers& headers,
-                            Orthanc::IDynamicObject* payload /* takes ownership */,
-                            NativeStoneApplicationContext& context);
-
-    virtual void Execute();
-  };
-
 }
--- a/Platforms/Generic/WebServicePostCommand.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -26,14 +26,15 @@
 namespace OrthancStone
 {
   WebServicePostCommand::WebServicePostCommand(MessageBroker& broker,
-                                               IWebService::ICallback& callback,
+                                               MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
+                                               MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
                                                const IWebService::Headers& headers,
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                                NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context),
+    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, payload, context),
     body_(body)
   {
   }
--- a/Platforms/Generic/WebServicePostCommand.h	Fri Sep 14 16:44:01 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Tue Sep 18 15:23:21 2018 +0200
@@ -32,7 +32,8 @@
 
   public:
     WebServicePostCommand(MessageBroker& broker,
-                          IWebService::ICallback& callback,
+                          MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
+                          MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
                           const IWebService::Headers& headers,
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Sep 14 16:44:01 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Sep 18 15:23:21 2018 +0200
@@ -285,6 +285,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageForwarder.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h
 
--- a/UnitTestsSources/TestMessageBroker2.cpp	Fri Sep 14 16:44:01 2018 +0200
+++ b/UnitTestsSources/TestMessageBroker2.cpp	Tue Sep 18 15:23:21 2018 +0200
@@ -22,296 +22,291 @@
 #include "gtest/gtest.h"
 
 #include "Framework/Messages/MessageBroker.h"
+#include "Framework/Messages/Promise.h"
+#include "Framework/Messages/IObservable.h"
+#include "Framework/Messages/IObserver.h"
+#include "Framework/Messages/MessageForwarder.h"
 
-#include <boost/noncopyable.hpp>
-#include <boost/function.hpp>
-#include <boost/bind.hpp>
-
-#include <string>
-#include <map>
-#include <set>
 
 int testCounter = 0;
 namespace {
 
-  class IObserver;
-  class IObservable;
-  class Promise;
+//  class IObserver;
+//  class IObservable;
+//  class Promise;
 
-  enum MessageType
-  {
-    MessageType_Test1,
-    MessageType_Test2,
+//  enum MessageType
+//  {
+//    MessageType_Test1,
+//    MessageType_Test2,
 
-    MessageType_CustomMessage,
-    MessageType_LastGenericStoneMessage
-  };
+//    MessageType_CustomMessage,
+//    MessageType_LastGenericStoneMessage
+//  };
 
-  struct IMessage  : public boost::noncopyable
-  {
-    MessageType messageType_;
-  public:
-    IMessage(const MessageType& messageType)
-      : messageType_(messageType)
-    {}
-    virtual ~IMessage() {}
+//  struct IMessage  : public boost::noncopyable
+//  {
+//    MessageType messageType_;
+//  public:
+//    IMessage(const MessageType& messageType)
+//      : messageType_(messageType)
+//    {}
+//    virtual ~IMessage() {}
 
-    virtual int GetType() const {return messageType_;}
-  };
+//    virtual int GetType() const {return messageType_;}
+//  };
 
 
-  struct ICustomMessage  : public IMessage
-  {
-    int customMessageType_;
-  public:
-    ICustomMessage(int customMessageType)
-      : IMessage(MessageType_CustomMessage),
-        customMessageType_(customMessageType)
-    {}
-    virtual ~ICustomMessage() {}
+//  struct ICustomMessage  : public IMessage
+//  {
+//    int customMessageType_;
+//  public:
+//    ICustomMessage(int customMessageType)
+//      : IMessage(MessageType_CustomMessage),
+//        customMessageType_(customMessageType)
+//    {}
+//    virtual ~ICustomMessage() {}
 
-    virtual int GetType() const {return customMessageType_;}
-  };
+//    virtual int GetType() const {return customMessageType_;}
+//  };
 
 
-  // This is referencing an object and member function that can be notified
-  // by an IObservable.  The object must derive from IO
-  // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage
-  class ICallable : public boost::noncopyable
-  {
-  public:
-    virtual ~ICallable()
-    {
-    }
+//  // This is referencing an object and member function that can be notified
+//  // by an IObservable.  The object must derive from IO
+//  // The member functions must be of type "void Function(const IMessage& message)" or reference a derived class of IMessage
+//  class ICallable : public boost::noncopyable
+//  {
+//  public:
+//    virtual ~ICallable()
+//    {
+//    }
 
-    virtual void Apply(const IMessage& message) = 0;
+//    virtual void Apply(const IMessage& message) = 0;
 
-    virtual MessageType GetMessageType() const = 0;
-    virtual IObserver* GetObserver() const = 0;
-  };
+//    virtual MessageType GetMessageType() const = 0;
+//    virtual IObserver* GetObserver() const = 0;
+//  };
 
-  template <typename TObserver,
-            typename TMessage>
-  class Callable : public ICallable
-  {
-  private:
-    typedef void (TObserver::* MemberFunction) (const TMessage&);
+//  template <typename TObserver,
+//            typename TMessage>
+//  class Callable : public ICallable
+//  {
+//  private:
+//    typedef void (TObserver::* MemberFunction) (const TMessage&);
 
-    TObserver&      observer_;
-    MemberFunction  function_;
+//    TObserver&      observer_;
+//    MemberFunction  function_;
 
-  public:
-    Callable(TObserver& observer,
-             MemberFunction function) :
-      observer_(observer),
-      function_(function)
-    {
-    }
+//  public:
+//    Callable(TObserver& observer,
+//             MemberFunction function) :
+//      observer_(observer),
+//      function_(function)
+//    {
+//    }
 
-    void ApplyInternal(const TMessage& message)
-    {
-      (observer_.*function_) (message);
-    }
+//    void ApplyInternal(const TMessage& message)
+//    {
+//      (observer_.*function_) (message);
+//    }
 
-    virtual void Apply(const IMessage& message)
-    {
-      ApplyInternal(dynamic_cast<const TMessage&>(message));
-    }
+//    virtual void Apply(const IMessage& message)
+//    {
+//      ApplyInternal(dynamic_cast<const TMessage&>(message));
+//    }
 
-    virtual MessageType GetMessageType() const
-    {
-      return static_cast<MessageType>(TMessage::Type);
-    }
+//    virtual MessageType GetMessageType() const
+//    {
+//      return static_cast<MessageType>(TMessage::Type);
+//    }
 
-    virtual IObserver* GetObserver() const
-    {
-      return &observer_;
-    }
-  };
+//    virtual IObserver* GetObserver() const
+//    {
+//      return &observer_;
+//    }
+//  };
 
 
 
 
-  /*
-   * This is a central message broker.  It keeps track of all observers and knows
-   * when an observer is deleted.
-   * This way, it can prevent an observable to send a message to a delete observer.
-   */
-  class MessageBroker : public boost::noncopyable
-  {
+//  /*
+//   * This is a central message broker.  It keeps track of all observers and knows
+//   * when an observer is deleted.
+//   * This way, it can prevent an observable to send a message to a delete observer.
+//   */
+//  class MessageBroker : public boost::noncopyable
+//  {
 
-    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
+//    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
 
-  public:
+//  public:
 
-    void Register(IObserver& observer)
-    {
-      activeObservers_.insert(&observer);
-    }
+//    void Register(IObserver& observer)
+//    {
+//      activeObservers_.insert(&observer);
+//    }
 
-    void Unregister(IObserver& observer)
-    {
-      activeObservers_.erase(&observer);
-    }
+//    void Unregister(IObserver& observer)
+//    {
+//      activeObservers_.erase(&observer);
+//    }
 
-    bool IsActive(IObserver* observer)
-    {
-      return activeObservers_.find(observer) != activeObservers_.end();
-    }
-  };
+//    bool IsActive(IObserver* observer)
+//    {
+//      return activeObservers_.find(observer) != activeObservers_.end();
+//    }
+//  };
 
 
-  class Promise : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
+//  class Promise : public boost::noncopyable
+//  {
+//  protected:
+//    MessageBroker&                    broker_;
 
-    ICallable* successCallable_;
-    ICallable* failureCallable_;
+//    ICallable* successCallable_;
+//    ICallable* failureCallable_;
 
-  public:
-    Promise(MessageBroker& broker)
-      : broker_(broker),
-        successCallable_(NULL),
-        failureCallable_(NULL)
-    {
-    }
+//  public:
+//    Promise(MessageBroker& broker)
+//      : broker_(broker),
+//        successCallable_(NULL),
+//        failureCallable_(NULL)
+//    {
+//    }
 
-    void Success(const IMessage& message)
-    {
-      // check the target is still alive in the broker
-      if (broker_.IsActive(successCallable_->GetObserver()))
-      {
-        successCallable_->Apply(message);
-      }
-    }
+//    void Success(const IMessage& message)
+//    {
+//      // check the target is still alive in the broker
+//      if (broker_.IsActive(successCallable_->GetObserver()))
+//      {
+//        successCallable_->Apply(message);
+//      }
+//    }
 
-    void Failure(const IMessage& message)
-    {
-      // check the target is still alive in the broker
-      if (broker_.IsActive(failureCallable_->GetObserver()))
-      {
-        failureCallable_->Apply(message);
-      }
-    }
+//    void Failure(const IMessage& message)
+//    {
+//      // check the target is still alive in the broker
+//      if (broker_.IsActive(failureCallable_->GetObserver()))
+//      {
+//        failureCallable_->Apply(message);
+//      }
+//    }
 
-    Promise& Then(ICallable* successCallable)
-    {
-      if (successCallable_ != NULL)
-      {
-        // TODO: throw throw new "Promise may only have a single success target"
-      }
-      successCallable_ = successCallable;
-      return *this;
-    }
+//    Promise& Then(ICallable* successCallable)
+//    {
+//      if (successCallable_ != NULL)
+//      {
+//        // TODO: throw throw new "Promise may only have a single success target"
+//      }
+//      successCallable_ = successCallable;
+//      return *this;
+//    }
 
-    Promise& Else(ICallable* failureCallable)
-    {
-      if (failureCallable_ != NULL)
-      {
-        // TODO: throw throw new "Promise may only have a single failure target"
-      }
-      failureCallable_ = failureCallable;
-      return *this;
-    }
+//    Promise& Else(ICallable* failureCallable)
+//    {
+//      if (failureCallable_ != NULL)
+//      {
+//        // TODO: throw throw new "Promise may only have a single failure target"
+//      }
+//      failureCallable_ = failureCallable;
+//      return *this;
+//    }
 
-  };
+//  };
 
-  class IObserver : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                    broker_;
+//  class IObserver : public boost::noncopyable
+//  {
+//  protected:
+//    MessageBroker&                    broker_;
 
-  public:
-    IObserver(MessageBroker& broker)
-      : broker_(broker)
-    {
-      broker_.Register(*this);
-    }
+//  public:
+//    IObserver(MessageBroker& broker)
+//      : broker_(broker)
+//    {
+//      broker_.Register(*this);
+//    }
 
-    virtual ~IObserver()
-    {
-      broker_.Unregister(*this);
-    }
+//    virtual ~IObserver()
+//    {
+//      broker_.Unregister(*this);
+//    }
 
-  };
+//  };
 
 
-  class IObservable : public boost::noncopyable
-  {
-  protected:
-    MessageBroker&                     broker_;
+//  class IObservable : public boost::noncopyable
+//  {
+//  protected:
+//    MessageBroker&                     broker_;
 
-    typedef std::map<int, std::set<ICallable*> >   Callables;
-    Callables  callables_;
-  public:
+//    typedef std::map<int, std::set<ICallable*> >   Callables;
+//    Callables  callables_;
+//  public:
 
-    IObservable(MessageBroker& broker)
-      : broker_(broker)
-    {
-    }
+//    IObservable(MessageBroker& broker)
+//      : broker_(broker)
+//    {
+//    }
 
-    virtual ~IObservable()
-    {
-      for (Callables::const_iterator it = callables_.begin();
-           it != callables_.end(); ++it)
-      {
-        for (std::set<ICallable*>::const_iterator
-               it2 = it->second.begin(); it2 != it->second.end(); ++it2)
-        {
-          delete *it2;
-        }
-      }
-    }
+//    virtual ~IObservable()
+//    {
+//      for (Callables::const_iterator it = callables_.begin();
+//           it != callables_.end(); ++it)
+//      {
+//        for (std::set<ICallable*>::const_iterator
+//               it2 = it->second.begin(); it2 != it->second.end(); ++it2)
+//        {
+//          delete *it2;
+//        }
+//      }
+//    }
 
-    void Register(ICallable* callable)
-    {
-      MessageType messageType = callable->GetMessageType();
+//    void Register(ICallable* callable)
+//    {
+//      MessageType messageType = callable->GetMessageType();
 
-      callables_[messageType].insert(callable);
-    }
+//      callables_[messageType].insert(callable);
+//    }
 
-    void EmitMessage(const IMessage& message)
-    {
-      Callables::const_iterator found = callables_.find(message.GetType());
+//    void EmitMessage(const IMessage& message)
+//    {
+//      Callables::const_iterator found = callables_.find(message.GetType());
 
-      if (found != callables_.end())
-      {
-        for (std::set<ICallable*>::const_iterator
-               it = found->second.begin(); it != found->second.end(); ++it)
-        {
-          if (broker_.IsActive((*it)->GetObserver()))
-          {
-            (*it)->Apply(message);
-          }
-        }
-      }
-    }
+//      if (found != callables_.end())
+//      {
+//        for (std::set<ICallable*>::const_iterator
+//               it = found->second.begin(); it != found->second.end(); ++it)
+//        {
+//          if (broker_.IsActive((*it)->GetObserver()))
+//          {
+//            (*it)->Apply(message);
+//          }
+//        }
+//      }
+//    }
 
-  };
+//  };
 
 
-  enum CustomMessageType
-  {
-    CustomMessageType_First = MessageType_LastGenericStoneMessage + 1,
+//  enum CustomMessageType
+//  {
+//    CustomMessageType_First = MessageType_LastGenericStoneMessage + 1,
 
-    CustomMessageType_Completed,
-    CustomMessageType_Increment
-  };
+//    CustomMessageType_Completed,
+//    CustomMessageType_Increment
+//  };
+
+  using namespace OrthancStone;
 
   class MyObservable : public IObservable
   {
   public:
-    struct MyCustomMessage: public ICustomMessage
+    struct MyCustomMessage: public BaseMessage<MessageType_Test1>
     {
       int payload_;
-      enum
-      {
-        Type = CustomMessageType_Completed
-      };
 
       MyCustomMessage(int payload)
-        : ICustomMessage(Type),
+        : BaseMessage(),
           payload_(payload)
       {}
     };
@@ -337,20 +332,30 @@
   };
 
 
+  class MyIntermediate : public IObserver, public IObservable
+  {
+    IObservable& observedObject_;
+  public:
+    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
+      : IObserver(broker),
+        IObservable(broker),
+        observedObject_(observedObject)
+    {
+      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
+    }
+  };
+
+
   class MyPromiseSource : public IObservable
   {
     Promise* currentPromise_;
   public:
-    struct MyPromiseMessage: public ICustomMessage
+    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
     {
       int increment;
-      enum
-      {
-        Type = CustomMessageType_Increment
-      };
 
       MyPromiseMessage(int increment)
-        : ICustomMessage(Type),
+        : BaseMessage(),
           increment(increment)
       {}
     };
@@ -407,7 +412,7 @@
   MyObserver    observer(broker);
 
   // create a permanent connection between an observable and an observer
-  observable.Register(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
 
   testCounter = 0;
   observable.EmitMessage(MyObservable::MyCustomMessage(12));
@@ -426,7 +431,7 @@
   MyObserver*   observer = new MyObserver(broker);
 
   // create a permanent connection between an observable and an observer
-  observable.Register(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
+  observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage));
 
   testCounter = 0;
   observable.EmitMessage(MyObservable::MyCustomMessage(12));
@@ -441,6 +446,27 @@
   ASSERT_EQ(0, testCounter);
 }
 
+TEST(MessageBroker2, TestMessageForwarderSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+
 
 TEST(MessageBroker2, TestPromiseSuccessFailure)
 {
@@ -496,196 +522,3 @@
   ASSERT_EQ(0, testCounter);
 }
 
-
-
-//#include <stdio.h>
-//#include <boost/noncopyable.hpp>
-
-//#include <string>
-//#include <memory>
-//#include <map>
-//#include <set>
-
-//enum MessageType
-//{
-//  MessageType_SeriesDownloaded = 1
-//};
-
-
-//class IMessage : public boost::noncopyable
-//{
-//private:
-//  MessageType  type_;
-
-//public:
-//  IMessage(MessageType  type) :
-//    type_(type)
-//  {
-//  }
-
-//  virtual ~IMessage()
-//  {
-//  }
-
-//  MessageType GetMessageType() const
-//  {
-//    return type_;
-//  }
-//};
-
-
-//class IObserver : public boost::noncopyable
-//{
-//public:
-//  virtual ~IObserver()
-//  {
-//  }
-//};
-
-
-//class SeriesDownloadedMessage : public IMessage
-//{
-//private:
-//  std::string value_;
-
-//public:
-//  enum
-//  {
-//    Type = MessageType_SeriesDownloaded
-//  };
-
-//  SeriesDownloadedMessage(const std::string& value) :
-//    IMessage(static_cast<MessageType>(Type)),
-//    value_(value)
-//  {
-//  }
-
-//  const std::string& GetValue() const
-//  {
-//    return value_;
-//  }
-//};
-
-
-//class MyObserver : public IObserver
-//{
-//public:
-//  void OnSeriesDownloaded(const SeriesDownloadedMessage& message)
-//  {
-//    printf("received: [%s]\n", message.GetValue().c_str());
-//  }
-//};
-
-
-
-//class ICallable : public boost::noncopyable  // ne peut referencer que les classes de base
-//{
-//public:
-//  virtual ~ICallable()
-//  {
-//  }
-
-//  virtual void Apply(const IMessage& message) = 0;
-
-//  virtual MessageType GetMessageType() const = 0;
-//};
-
-
-
-//template <typename Observer,
-//          typename Message>
-//class Callable : public ICallable
-//{
-//private:
-//  typedef void (Observer::* MemberFunction) (const Message&);
-
-//  Observer&       observer_;
-//  MemberFunction  function_;
-
-//public:
-//  Callable(Observer& observer,
-//           MemberFunction function) :
-//    observer_(observer),
-//    function_(function)
-//  {
-//  }
-
-//  void ApplyInternal(const Message& message)
-//  {
-//    (observer_.*function_) (message);
-//  }
-
-//  virtual void Apply(const IMessage& message)
-//  {
-//    ApplyInternal(dynamic_cast<const Message&>(message));
-//  }
-
-//  virtual MessageType GetMessageType() const
-//  {
-//    return static_cast<MessageType>(Message::Type);
-//  }
-//};
-
-
-
-//class IObservable : public boost::noncopyable
-//{
-//private:
-//  typedef std::map<MessageType, std::set<ICallable*> >   Callables;
-
-//  Callables  callables_;
-
-//public:
-//  virtual ~IObservable()
-//  {
-//    for (Callables::const_iterator it = callables_.begin();
-//         it != callables_.end(); ++it)
-//    {
-//      for (std::set<ICallable*>::const_iterator
-//             it2 = it->second.begin(); it2 != it->second.end(); ++it2)
-//      {
-//        delete *it2;
-//      }
-//    }
-//  }
-
-//  void Register(ICallable* callable)
-//  {
-//    MessageType type = callable->GetMessageType();
-
-//    callables_[type].insert(callable);
-//  }
-
-//  void Emit(const IMessage& message) const
-//  {
-//    Callables::const_iterator found = callables_.find(message.GetMessageType());
-
-//    if (found != callables_.end())
-//    {
-//      for (std::set<ICallable*>::const_iterator
-//             it = found->second.begin(); it != found->second.end(); ++it)
-//      {
-//        (*it)->Apply(message);
-//      }
-//    }
-//  }
-//};
-
-
-
-
-//int main()
-//{
-//  MyObserver observer;
-
-//  SeriesDownloadedMessage message("coucou");
-
-//  IObservable observable;
-//  observable.Register(new Callable<MyObserver, SeriesDownloadedMessage>(observer, &MyObserver::OnSeriesDownloaded));
-//  observable.Register(new Callable<MyObserver, SeriesDownloadedMessage>(observer, &MyObserver::OnSeriesDownloaded));
-
-//  SeriesDownloadedMessage message2("hello");
-//  observable.Emit(message2);
-
-//  printf("%d\n", SeriesDownloadedMessage::Type);
-//}