changeset 251:192e6e349e69 am-2

first usage of new message system (in SDL only)
author am@osimis.io
date Mon, 02 Jul 2018 18:13:46 +0200
parents 5e642859267e
children 40b21c1f8b8d
files Applications/Samples/SampleMainSdl.cpp Applications/Samples/SimpleViewerApplication.h Applications/Sdl/BasicSdlApplication.cpp Applications/Sdl/BasicSdlApplication.h Framework/Layers/DicomStructureSetRendererFactory.h Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Messages/IMessage.h Framework/Messages/IObserver.h Framework/Messages/MessageBroker.h Framework/Messages/MessageType.h Framework/Toolbox/IWebService.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/TestMessageBroker.cpp
diffstat 31 files changed, 565 insertions(+), 271 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SampleMainSdl.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Applications/Samples/SampleMainSdl.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -21,10 +21,12 @@
 
 #include "SampleList.h"
 #include "../Sdl/BasicSdlApplication.h"
+#include "../../Framework/Messages/MessageBroker.h"
 
 int main(int argc, char* argv[]) 
 {
-  Application application;
+  OrthancStone::MessageBroker broker;
+  Application application(broker);
 
-  return OrthancStone::BasicSdlApplication::ExecuteWithSdl(application, argc, argv);
+  return OrthancStone::BasicSdlApplication::ExecuteWithSdl(broker, application, argc, argv);
 }
--- a/Applications/Samples/SimpleViewerApplication.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Mon Jul 02 18:13:46 2018 +0200
@@ -26,6 +26,7 @@
 #include "../../Framework/Layers/OrthancFrameLayerSource.h"
 #include "../../Framework/Widgets/LayerWidget.h"
 #include "../../Framework/Widgets/LayoutWidget.h"
+#include "../../Framework/Messages/IObserver.h"
 
 #include <Core/Logging.h>
 
@@ -35,7 +36,7 @@
   {
     class SimpleViewerApplication :
         public SampleApplicationBase,
-        private ILayerSource::IObserver
+        public IObserver
     {
     private:
       class Interactor : public IWorldSceneInteractor
@@ -172,38 +173,13 @@
       //        }
       //      }
 
-      
-      virtual void NotifyGeometryReady(const ILayerSource& source)
-      {
-        // Once the geometry of the series is downloaded from Orthanc,
-        // display its first slice, and adapt the viewport to fit this
-        // slice
-        if (source_ == &source)
-        {
-          //SetSlice(source_->GetSliceCount() / 2);
+
+      virtual void HandleMessage(IObservable& from, const IMessage& message) {
+        switch (message.GetType()) {
+        case MessageType_GeometryReady:
+          mainLayout_->SetDefaultView();
+          break;
         }
-
-        mainLayout_->SetDefaultView();
-      }
-      
-      virtual void NotifyGeometryError(const ILayerSource& source)
-      {
-      }
-      
-      virtual void NotifyContentChange(const ILayerSource& source)
-      {
-      }
-
-      virtual void NotifySliceChange(const ILayerSource& source,
-                                     const Slice& slice)
-      {
-      }
-
-      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
-                                    const ILayerSource& source,
-                                    const CoordinateSystem3D& slice,
-                                    bool isError)
-      {
       }
 
       std::unique_ptr<Interactor>     interactor_;
@@ -218,18 +194,19 @@
 
       OrthancFrameLayerSource*        source_;
       unsigned int                    slice_;
-      
+
     public:
-      SimpleViewerApplication() :
+      SimpleViewerApplication(MessageBroker& broker) :
         mainLayout_(NULL),
         currentInstanceIndex_(0),
         source_(NULL),
         slice_(0),
         wasmViewport1_(NULL),
-        wasmViewport2_(NULL)
+        wasmViewport2_(NULL),
+        IObserver(broker)
       {
       }
-      
+
       virtual void Finalize() {}
       virtual IWidget* GetCentralWidget() {return mainLayout_;}
 
@@ -282,9 +259,9 @@
         thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
         thumbnailsLayout_->SetVertical();
 
-        mainViewport_ = new LayerWidget();
-        thumbnails_.push_back(new LayerWidget());
-        thumbnails_.push_back(new LayerWidget());
+        mainViewport_ = new LayerWidget(broker_);
+        thumbnails_.push_back(new LayerWidget(broker_));
+        thumbnails_.push_back(new LayerWidget(broker_));
 
         // hierarchy
         mainLayout_->AddWidget(thumbnailsLayout_);
@@ -293,15 +270,15 @@
         thumbnailsLayout_->AddWidget(thumbnails_[1]);
 
         // sources
-        source_ = new OrthancFrameLayerSource(context_->GetWebService());
+        source_ = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         source_->LoadFrame(instances_[currentInstanceIndex_], 0);
         source_->Register(*this);
 
         mainViewport_->AddLayer(source_);
 
-        OrthancFrameLayerSource* thumb0 = new OrthancFrameLayerSource(context_->GetWebService());
+        OrthancFrameLayerSource* thumb0 = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         thumb0->LoadFrame(instances_[0], 0);
-        OrthancFrameLayerSource* thumb1 = new OrthancFrameLayerSource(context_->GetWebService());
+        OrthancFrameLayerSource* thumb1 = new OrthancFrameLayerSource(broker_, context_->GetWebService());
         thumb1->LoadFrame(instances_[1], 0);
 
         thumbnails_[0]->AddLayer(thumb0);
@@ -326,7 +303,7 @@
         currentInstanceIndex_ = (currentInstanceIndex_ + 1) % instances_.size();
 
         std::auto_ptr<OrthancFrameLayerSource> layer
-            (new OrthancFrameLayerSource(context_->GetWebService()));
+            (new OrthancFrameLayerSource(broker_, context_->GetWebService()));
         layer->LoadFrame(instances_[currentInstanceIndex_], 0);
 
         mainViewport_->ReplaceLayer(0, layer.release());
--- a/Applications/Sdl/BasicSdlApplication.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Applications/Sdl/BasicSdlApplication.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -80,7 +80,8 @@
   }
 
 
-  int BasicSdlApplication::ExecuteWithSdl(IBasicApplication& application,
+  int BasicSdlApplication::ExecuteWithSdl(MessageBroker& broker,
+                                          IBasicApplication& application,
                                         int argc,
                                         char* argv[])
   {
@@ -224,7 +225,7 @@
 
       boost::mutex stoneGlobalMutex;
       Oracle oracle(stoneGlobalMutex, 4); // use 4 threads to download content
-      OracleWebService webService(oracle, webServiceParameters);
+      OracleWebService webService(broker, oracle, webServiceParameters);
       BasicSdlApplicationContext context(webService);
 
       application.Initialize(&context, statusBar, parameters);
--- a/Applications/Sdl/BasicSdlApplication.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Applications/Sdl/BasicSdlApplication.h	Mon Jul 02 18:13:46 2018 +0200
@@ -35,7 +35,8 @@
   {
   public:
 
-    static int ExecuteWithSdl(IBasicApplication& application,
+    static int ExecuteWithSdl(MessageBroker& broker,
+                              IBasicApplication& application,
                               int argc,
                               char* argv[]);
   };
--- a/Framework/Layers/DicomStructureSetRendererFactory.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/DicomStructureSetRendererFactory.h	Mon Jul 02 18:13:46 2018 +0200
@@ -37,21 +37,22 @@
     {
       LayerSourceBase::NotifyGeometryReady();
     }
-      
+
     virtual void NotifyGeometryError(const IVolumeLoader& loader)
     {
       LayerSourceBase::NotifyGeometryError();
     }
-      
+
     virtual void NotifyContentChange(const IVolumeLoader& loader)
     {
       LayerSourceBase::NotifyContentChange();
     }
-    
+
     StructureSetLoader& loader_;
 
   public:
-    DicomStructureSetRendererFactory(StructureSetLoader& loader) :
+    DicomStructureSetRendererFactory(MessageBroker& broker, StructureSetLoader& loader) :
+      LayerSourceBase(broker),
       loader_(loader)
     {
       loader_.Register(*this);
--- a/Framework/Layers/ILayerSource.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Mon Jul 02 18:13:46 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/>.
  **/
@@ -23,47 +23,80 @@
 
 #include "ILayerRenderer.h"
 #include "../Toolbox/Slice.h"
+#include "../../Framework/Messages/IObservable.h"
+#include "../../Framework/Messages/IMessage.h"
 
 namespace OrthancStone
 {
-  class ILayerSource : public boost::noncopyable
+  class ILayerSource : public boost::noncopyable, public IObservable
   {
   public:
-    class IObserver : public boost::noncopyable
+    struct SliceChangedMessage : public IMessage
+    {
+      const Slice& slice;
+      SliceChangedMessage(const Slice& slice)
+        : IMessage(MessageType_SliceChanged),
+          slice(slice)
+      {
+      }
+    };
+
+    struct LayerReadyMessage : public IMessage
     {
-    public:
-      virtual ~IObserver()
+      std::auto_ptr<ILayerRenderer>& layer;
+      const CoordinateSystem3D& slice;
+      bool isError;
+
+      LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
+                        const CoordinateSystem3D& slice,
+                        bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
+        : IMessage(MessageType_LayerReady),
+          layer(layer),
+          slice(slice),
+          isError(isError)
       {
       }
+    };
 
-      // 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;
+    //    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 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?
-    };
+    //      // 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)
+    {}
+
     virtual ~ILayerSource()
     {
     }
 
-    virtual void Register(IObserver& observer) = 0;
+    //    virtual void Register(IObserver& observer) = 0;
 
     virtual bool GetExtent(std::vector<Vector>& points,
                            const CoordinateSystem3D& viewportSlice) = 0;
--- a/Framework/Layers/LayerSourceBase.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -25,63 +25,65 @@
 
 namespace OrthancStone
 {
-  namespace
-  {
-    class LayerReadyFunctor : public boost::noncopyable
-    {
-    private:
-      std::auto_ptr<ILayerRenderer>  layer_;
-      const CoordinateSystem3D&      slice_;
-      bool                           isError_;
+//  namespace
+//  {
+//    class LayerReadyFunctor : public boost::noncopyable
+//    {
+//    private:
+//      std::auto_ptr<ILayerRenderer>  layer_;
+//      const CoordinateSystem3D&      slice_;
+//      bool                           isError_;
       
-    public:
-      LayerReadyFunctor(ILayerRenderer* layer,
-                        const CoordinateSystem3D& slice,
-                        bool isError) :
-        layer_(layer),
-        slice_(slice),
-        isError_(isError)
-      {
-      }
+//    public:
+//      LayerReadyFunctor(ILayerRenderer* layer,
+//                        const CoordinateSystem3D& slice,
+//                        bool isError) :
+//        layer_(layer),
+//        slice_(slice),
+//        isError_(isError)
+//      {
+//      }
 
-      void operator() (ILayerSource::IObserver& observer,
-                       const ILayerSource& source)
-      {
-        observer.NotifyLayerReady(layer_, source, slice_, isError_);
-      }
-    };
-  }
+//      void operator() (ILayerSource::IObserver& observer,
+//                       const ILayerSource& source)
+//      {
+//        observer.NotifyLayerReady(layer_, source, slice_, isError_);
+//      }
+//    };
+//  }
 
   void LayerSourceBase::NotifyGeometryReady()
   {
-    observers_.Apply(*this, &IObserver::NotifyGeometryReady);
+    Emit(IMessage(MessageType_GeometryReady));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    observers_.Apply(*this, &IObserver::NotifyGeometryError);
-  }  
+    Emit(IMessage(MessageType_GeometryError));
+  }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    observers_.Apply(*this, &IObserver::NotifyContentChange);
+    Emit(IMessage(MessageType_ContentChanged));
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
   {
-    observers_.Apply(*this, &IObserver::NotifySliceChange, slice);
+    Emit(ILayerSource::SliceChangedMessage(slice));
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
                                          const CoordinateSystem3D& slice,
                                          bool isError)
   {
-    LayerReadyFunctor functor(layer, slice, isError);
-    observers_.Notify(*this, functor);
+    std::auto_ptr<ILayerRenderer> renderer(layer);
+    Emit(ILayerSource::LayerReadyMessage(renderer, slice, isError));
+//    LayerReadyFunctor functor(layer, slice, isError);
+//    observers_.Notify(*this, functor);
   }
 
   void LayerSourceBase::Register(IObserver& observer)
   {
-    observers_.Register(observer);
+    RegisterObserver(observer);
   }
 }
--- a/Framework/Layers/LayerSourceBase.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Mon Jul 02 18:13:46 2018 +0200
@@ -28,11 +28,6 @@
 {
   class LayerSourceBase : public ILayerSource
   {
-  private:
-    typedef ObserversRegistry<ILayerSource, IObserver>  Observers;
-
-    Observers  observers_;
-
   protected:
     void NotifyGeometryReady();
     
@@ -46,6 +41,10 @@
                           const CoordinateSystem3D& slice,
                           bool isError);
 
+    LayerSourceBase(MessageBroker& broker)
+      : ILayerSource(broker)
+    {}
+
   public:
     virtual void Register(IObserver& observer);
   };
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -68,8 +68,9 @@
   }
 
 
-  OrthancFrameLayerSource::OrthancFrameLayerSource(IWebService& orthanc) :
-    loader_(*this, orthanc),
+  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
+    LayerSourceBase(broker),
+    loader_(broker, *this, orthanc),
     quality_(SliceImageQuality_Full)
   {
   }
--- a/Framework/Layers/OrthancFrameLayerSource.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Mon Jul 02 18:13:46 2018 +0200
@@ -51,7 +51,7 @@
                                        SliceImageQuality quality);
 
   public:
-    OrthancFrameLayerSource(IWebService& orthanc);
+    OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc);
 
     void LoadSeries(const std::string& seriesId);
 
--- a/Framework/Messages/IMessage.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Messages/IMessage.h	Mon Jul 02 18:13:46 2018 +0200
@@ -23,15 +23,18 @@
 
 #include "MessageType.h"
 
+#include <boost/noncopyable.hpp>
+
 namespace OrthancStone {
 
-  class IMessage  : public boost::noncopyable
+  struct IMessage  : public boost::noncopyable
   {
     MessageType messageType_;
   public:
     IMessage(const MessageType& messageType)
       : messageType_(messageType)
     {}
+    virtual ~IMessage() {}
 
     MessageType GetType() const {return messageType_;}
   };
--- a/Framework/Messages/IObserver.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Messages/IObserver.h	Mon Jul 02 18:13:46 2018 +0200
@@ -22,14 +22,16 @@
 #pragma once
 
 #include "MessageBroker.h"
+#include "IMessage.h"
+#include "IObservable.h"
 
 namespace OrthancStone {
 
   class IObservable;
-  class IMessage;
 
   class IObserver : public boost::noncopyable
   {
+  protected:
     MessageBroker&                    broker_;
 
   public:
--- a/Framework/Messages/MessageBroker.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Messages/MessageBroker.h	Mon Jul 02 18:13:46 2018 +0200
@@ -27,6 +27,7 @@
 #include <map>
 #include <list>
 #include <set>
+
 namespace OrthancStone
 {
   class IObserver;
--- a/Framework/Messages/MessageType.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Messages/MessageType.h	Mon Jul 02 18:13:46 2018 +0200
@@ -18,6 +18,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#pragma once
 
 namespace OrthancStone {
 
@@ -25,6 +26,14 @@
   {
     MessageType_Generic,
 
-    MessageType_GeometryReady
+    MessageType_GeometryReady,
+    MessageType_GeometryError,
+    MessageType_ContentChanged,
+    MessageType_SliceChanged,
+    MessageType_LayerReady,
+
+    MessageType_HttpRequestSuccess,
+    MessageType_HttpRequestError
+
   };
 }
--- a/Framework/Toolbox/IWebService.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Mon Jul 02 18:13:46 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/>.
  **/
@@ -22,41 +22,102 @@
 #pragma once
 
 #include <Core/IDynamicObject.h>
-
+#include "../../Framework/Messages/IObserver.h"
 #include <string>
 
 namespace OrthancStone
 {
-  class IWebService : public boost::noncopyable
-  {
-  public:
-    class ICallback : public boost::noncopyable
+    class IWebService
     {
+    protected:
+        MessageBroker& broker_;
     public:
-      virtual ~ICallback()
-      {
-      }
+        class ICallback : public IObserver
+        {
+        public:
+            struct HttpRequestSuccessMessage: public IMessage
+            {
+                const std::string& Uri;
+                const void* Answer;
+                size_t AnswerSize;
+                Orthanc::IDynamicObject* Payload;
+                HttpRequestSuccessMessage(const std::string& uri,
+                                          const void* answer,
+                                          size_t answerSize,
+                                          Orthanc::IDynamicObject* payload)
+                    : IMessage(MessageType_HttpRequestSuccess),
+                      Uri(uri),
+                      Answer(answer),
+                      AnswerSize(answerSize),
+                      Payload(payload)
+                {}
+            };
 
-      virtual void NotifyError(const std::string& uri,
-                               Orthanc::IDynamicObject* payload) = 0;
+            struct HttpRequestErrorMessage: public IMessage
+            {
+                const std::string& Uri;
+                Orthanc::IDynamicObject* Payload;
+                HttpRequestErrorMessage(const std::string& uri,
+                                        Orthanc::IDynamicObject* payload)
+                    : IMessage(MessageType_HttpRequestError),
+                      Uri(uri),
+                      Payload(payload)
+                {}
+            };
+
+            ICallback(MessageBroker& broker)
+                : IObserver(broker)
+            {}
+            virtual ~ICallback()
+            {
+            }
 
-      virtual void NotifySuccess(const std::string& uri,
-                                 const void* answer,
-                                 size_t answerSize,
-                                 Orthanc::IDynamicObject* payload) = 0;
-    };
-    
-    virtual ~IWebService()
-    {
-    }
+            virtual void HandleMessage(IObservable& from, const IMessage& message)
+            {
+                switch(message.GetType())
+                {
+                case MessageType_HttpRequestError:
+                {    const HttpRequestErrorMessage& msg = dynamic_cast<const HttpRequestErrorMessage&>(message);
+                    OnHttpRequestError(msg.Uri,
+                                       msg.Payload);
+                }; break;
+
+                case MessageType_HttpRequestSuccess:
+                {
+                    const HttpRequestSuccessMessage& msg = dynamic_cast<const HttpRequestSuccessMessage&>(message);
+                    OnHttpRequestSuccess(msg.Uri,
+                                         msg.Answer,
+                                         msg.AnswerSize,
+                                         msg.Payload);
+                }; break;
+
+                }
+            }
+
+            virtual void OnHttpRequestError(const std::string& uri,
+                                            Orthanc::IDynamicObject* payload) = 0;
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    Orthanc::IDynamicObject* payload) = 0;
+            virtual void OnHttpRequestSuccess(const std::string& uri,
+                                              const void* answer,
+                                              size_t answerSize,
+                                              Orthanc::IDynamicObject* payload) = 0;
+        };
+
+        IWebService(MessageBroker& broker)
+            : broker_(broker)
+        {}
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload) = 0;
-  };
+        virtual ~IWebService()
+        {
+        }
+
+        virtual void ScheduleGetRequest(ICallback& callback,
+                                        const std::string& uri,
+                                        Orthanc::IDynamicObject* payload) = 0;
+
+        virtual void SchedulePostRequest(ICallback& callback,
+                                         const std::string& uri,
+                                         const std::string& body,
+                                         Orthanc::IDynamicObject* payload) = 0;
+    };
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -175,12 +175,13 @@
     OrthancSlicesLoader&  that_;
 
   public:
-    WebCallback(OrthancSlicesLoader&  that) :
+    WebCallback(MessageBroker& broker, OrthancSlicesLoader&  that) :
+      IWebService::ICallback(broker),
       that_(that)
     {
     }
 
-    virtual void NotifySuccess(const std::string& uri,
+    virtual void OnHttpRequestSuccess(const std::string& uri,
                                const void* answer,
                                size_t answerSize,
                                Orthanc::IDynamicObject* payload)
@@ -230,7 +231,7 @@
       }
     }
 
-    virtual void NotifyError(const std::string& uri,
+    virtual void OnHttpRequestError(const std::string& uri,
                              Orthanc::IDynamicObject* payload)
     {
       std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
@@ -715,9 +716,10 @@
   }
 
 
-  OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback,
+  OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
+                                           ICallback& callback,
                                            IWebService& orthanc) :
-    webCallback_(new WebCallback(*this)),
+    webCallback_(new WebCallback(broker, *this)),
     userCallback_(callback),
     orthanc_(orthanc),
     state_(State_Initialization)
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Mon Jul 02 18:13:46 2018 +0200
@@ -122,7 +122,8 @@
     void SortAndFinalizeSlices();
     
   public:
-    OrthancSlicesLoader(ICallback& callback,
+    OrthancSlicesLoader(MessageBroker& broker,
+                        ICallback& callback,
                         IWebService& orthanc);
 
     void ScheduleLoadSeries(const std::string& seriesId);
--- a/Framework/Volumes/StructureSetLoader.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -61,14 +61,14 @@
   };
 
 
-  void StructureSetLoader::NotifyError(const std::string& uri,
+  void StructureSetLoader::OnHttpRequestError(const std::string& uri,
                                        Orthanc::IDynamicObject* payload)
   {
     // TODO
   }
 
   
-  void StructureSetLoader::NotifySuccess(const std::string& uri,
+  void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri,
                                          const void* answer,
                                          size_t answerSize,
                                          Orthanc::IDynamicObject* payload)
@@ -145,7 +145,8 @@
   } 
 
   
-  StructureSetLoader::StructureSetLoader(IWebService& orthanc) :
+  StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) :
+    IWebService::ICallback(broker),
     orthanc_(orthanc)
   {
   }
--- a/Framework/Volumes/StructureSetLoader.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.h	Mon Jul 02 18:13:46 2018 +0200
@@ -34,10 +34,10 @@
   private:
     class Operation;
     
-    virtual void NotifyError(const std::string& uri,
+    virtual void OnHttpRequestError(const std::string& uri,
                              Orthanc::IDynamicObject* payload);
 
-    virtual void NotifySuccess(const std::string& uri,
+    virtual void OnHttpRequestSuccess(const std::string& uri,
                                const void* answer,
                                size_t answerSize,
                                Orthanc::IDynamicObject* payload);
@@ -46,7 +46,7 @@
     std::auto_ptr<DicomStructureSet>  structureSet_;
 
   public:
-    StructureSetLoader(IWebService& orthanc);
+    StructureSetLoader(MessageBroker& broker, IWebService& orthanc);
 
     void ScheduleLoadInstance(const std::string& instance);
 
--- a/Framework/Widgets/LayerWidget.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Mon Jul 02 18:13:46 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/>.
  **/
@@ -55,7 +55,7 @@
         countMissing_++;
       }
     }
-      
+
   public:
     Scene(const CoordinateSystem3D& slice,
           double thickness,
@@ -184,7 +184,7 @@
 #endif
         
         cairo_set_line_width(cr, 2.0 / view.GetZoom());
-        cairo_set_source_rgb(cr, 1, 1, 1); 
+        cairo_set_source_rgb(cr, 1, 1, 1);
         cairo_stroke_preserve(cr);
         cairo_set_source_rgb(cr, 1, 0, 0);
         cairo_fill(cr);
@@ -215,7 +215,7 @@
       {
         double z = (slice_.ProjectAlongNormal(slice.GetOrigin()) -
                     slice_.ProjectAlongNormal(slice_.GetOrigin()));
-      
+
         if (z < 0)
         {
           z = -z;
@@ -249,7 +249,7 @@
       return true;
     }
   }
-    
+
 
   void LayerWidget::GetLayerExtent(Extent2D& extent,
                                    ILayerSource& source) const
@@ -268,7 +268,7 @@
     }
   }
 
-        
+
   Extent2D LayerWidget::GetSceneExtent()
   {
     Extent2D sceneExtent;
@@ -359,7 +359,8 @@
   }
 
   
-  LayerWidget::LayerWidget() :
+  LayerWidget::LayerWidget(MessageBroker& broker) :
+    IObserver(broker),
     started_(false)
   {
     SetBackgroundCleared(true);
@@ -388,7 +389,7 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->Register(*this);
+    layer->RegisterObserver(*this);
 
     ResetChangedLayers();
 
@@ -412,7 +413,7 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->Register(*this);
+    layer->RegisterObserver(*this);
 
     InvalidateLayer(index);
   }
@@ -479,8 +480,35 @@
     }
   }
 
+  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
+  {
+    switch (message.GetType()) {
+    case MessageType_GeometryReady:
+      OnGeometryReady(dynamic_cast<ILayerSource&>(from));
+      break;
+    case MessageType_GeometryError:
+      LOG(ERROR) << "Cannot get geometry";
+      break;
+    case MessageType_ContentChanged:
+      OnContentChanged(dynamic_cast<ILayerSource&>(from));
+      break;
+    case MessageType_SliceChanged:
+      OnSliceChanged(dynamic_cast<ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice);
+      break;
+    case MessageType_LayerReady:
+    {
+      const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
+      OnLayerReady(layerReadyMessage.layer,
+                   dynamic_cast<ILayerSource&>(from),
+                   layerReadyMessage.slice,
+                   layerReadyMessage.isError);
+    }; break;
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
 
-  void LayerWidget::NotifyGeometryReady(const ILayerSource& source)
+  void LayerWidget::OnGeometryReady(const ILayerSource& source)
   {
     size_t i;
     if (LookupLayer(i, source))
@@ -492,13 +520,6 @@
     }
   }
   
-
-  void LayerWidget::NotifyGeometryError(const ILayerSource& source)
-  {
-    LOG(ERROR) << "Cannot get geometry";
-  }
-  
-
   void LayerWidget::InvalidateAllLayers()
   {
     for (size_t i = 0; i < layers_.size(); i++)
@@ -525,7 +546,7 @@
   }
 
 
-  void LayerWidget::NotifyContentChange(const ILayerSource& source)
+  void LayerWidget::OnContentChanged(const ILayerSource& source)
   {
     size_t index;
     if (LookupLayer(index, source))
@@ -535,8 +556,8 @@
   }
   
 
-  void LayerWidget::NotifySliceChange(const ILayerSource& source,
-                                      const Slice& slice)
+  void LayerWidget::OnSliceChanged(const ILayerSource& source,
+                                   const Slice& slice)
   {
     if (slice.ContainsPlane(slice_))
     {
@@ -549,10 +570,10 @@
   }
   
   
-  void LayerWidget::NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                     const ILayerSource& source,
-                                     const CoordinateSystem3D& slice,
-                                     bool isError)
+  void LayerWidget::OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+                                 const ILayerSource& source,
+                                 const CoordinateSystem3D& slice,
+                                 bool isError)
   {
     size_t index;
     if (LookupLayer(index, source))
--- a/Framework/Widgets/LayerWidget.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Mon Jul 02 18:13:46 2018 +0200
@@ -24,6 +24,7 @@
 #include "WorldSceneWidget.h"
 #include "../Layers/ILayerSource.h"
 #include "../Toolbox/Extent2D.h"
+#include "../../Framework/Messages/IObserver.h"
 
 #include <map>
 
@@ -31,7 +32,7 @@
 {
   class LayerWidget :
     public WorldSceneWidget,
-    private ILayerSource::IObserver
+    public IObserver
   {
   private:
     class Scene;
@@ -53,23 +54,26 @@
     void GetLayerExtent(Extent2D& extent,
                         ILayerSource& source) const;
 
-    virtual void NotifyGeometryReady(const ILayerSource& source);
-
-    virtual void NotifyGeometryError(const ILayerSource& source);
+    void OnGeometryReady(const ILayerSource& source);
 
-    virtual void NotifyContentChange(const ILayerSource& source);
+    virtual void OnContentChanged(const ILayerSource& source);
 
-    virtual void NotifySliceChange(const ILayerSource& source,
+    virtual void OnSliceChanged(const ILayerSource& source,
                                    const Slice& slice);
 
-    virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
+    virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
                                   const ILayerSource& source,
                                   const CoordinateSystem3D& slice,
                                   bool isError);
 
+
     void ResetChangedLayers();
 
   public:
+    LayerWidget(MessageBroker& broker);
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
     virtual Extent2D GetSceneExtent();
  
   protected:
--- a/Framework/dev.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Framework/dev.h	Mon Jul 02 18:13:46 2018 +0200
@@ -215,9 +215,10 @@
     }
 
   public:
-    OrthancVolumeImage(IWebService& orthanc,
+    OrthancVolumeImage(MessageBroker& broker,
+                       IWebService& orthanc,
                        bool computeRange) : 
-      loader_(*this, orthanc),
+      loader_(broker, *this, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)
     {
@@ -576,7 +577,8 @@
 
 
   public:
-    VolumeImageSource(OrthancVolumeImage&  volume) :
+    VolumeImageSource(MessageBroker& broker, OrthancVolumeImage&  volume) :
+      LayerSourceBase(broker),
       volume_(volume)
     {
       volume_.Register(*this);
@@ -814,7 +816,8 @@
     LayerWidget&  otherPlane_;
 
   public:
-    SliceLocationSource(LayerWidget&  otherPlane) :
+    SliceLocationSource(MessageBroker& broker, LayerWidget&  otherPlane) :
+      LayerSourceBase(broker),
       otherPlane_(otherPlane)
     {
       NotifyGeometryReady();
--- a/Platforms/Generic/OracleWebService.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Mon Jul 02 18:13:46 2018 +0200
@@ -35,8 +35,10 @@
     Orthanc::WebServiceParameters  parameters_;
 
   public:
-    OracleWebService(Oracle& oracle,
+    OracleWebService(MessageBroker& broker,
+                     Oracle& oracle,
                      const Orthanc::WebServiceParameters& parameters) : 
+      IWebService(broker),
       oracle_(oracle),
       parameters_(parameters)
     {
@@ -46,7 +48,7 @@
                                     const std::string& uri,
                                     Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServiceGetCommand(callback, parameters_, uri, payload));
+      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, payload));
     }
 
     virtual void SchedulePostRequest(ICallback& callback,
@@ -54,7 +56,7 @@
                                      const std::string& body,
                                      Orthanc::IDynamicObject* payload)
     {
-      oracle_.Submit(new WebServicePostCommand(callback, parameters_, uri, body, payload));
+      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, body, payload));
     }
 
     void Start()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "WebServiceCommandBase.h"
+
+#include <Core/HttpClient.h>
+
+namespace OrthancStone
+{
+  WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker,
+                                             IWebService::ICallback& callback,
+                                             const Orthanc::WebServiceParameters& parameters,
+                                             const std::string& uri,
+                                             Orthanc::IDynamicObject* payload /* takes ownership */) :
+    IObservable(broker),
+    callback_(callback),
+    parameters_(parameters),
+    uri_(uri),
+    payload_(payload)
+  {
+    RegisterObserver(callback);
+  }
+
+
+  void WebServiceCommandBase::Commit()
+  {
+    if (success_)
+    {
+      IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release());
+      Emit(message);
+    }
+    else
+    {
+      IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release());
+      Emit(message);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/WebServiceCommandBase.h	Mon Jul 02 18:13:46 2018 +0200
@@ -0,0 +1,56 @@
+/**
+ * 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 "IOracleCommand.h"
+
+#include "../../Framework/Toolbox/IWebService.h"
+#include "../../Framework/Messages/IObservable.h"
+
+#include <Core/WebServiceParameters.h>
+
+#include <memory>
+
+namespace OrthancStone
+{
+  class WebServiceCommandBase : public IOracleCommand, IObservable
+  {
+  protected:
+    IWebService::ICallback&                 callback_;
+    Orthanc::WebServiceParameters           parameters_;
+    std::string                             uri_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    bool                                    success_;
+    std::string                             answer_;
+
+  public:
+    WebServiceCommandBase(MessageBroker& broker,
+                         IWebService::ICallback& callback,
+                         const Orthanc::WebServiceParameters& parameters,
+                         const std::string& uri,
+                         Orthanc::IDynamicObject* payload /* takes ownership */);
+
+    virtual void Execute() = 0;
+
+    virtual void Commit();
+  };
+}
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -25,14 +25,12 @@
 
 namespace OrthancStone
 {
-  WebServiceGetCommand::WebServiceGetCommand(IWebService::ICallback& callback,
+  WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker,
+                                             IWebService::ICallback& callback,
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
                                              Orthanc::IDynamicObject* payload /* takes ownership */) :
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    payload_(payload)
+    WebServiceCommandBase(broker, callback, parameters, uri, payload)
   {
   }
 
@@ -45,16 +43,4 @@
     success_ = client.Apply(answer_);
   }
 
-
-  void WebServiceGetCommand::Commit()
-  {
-    if (success_)
-    {
-      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
-    }
-    else
-    {
-      callback_.NotifyError(uri_, payload_.release());
-    }
-  }
 }
--- a/Platforms/Generic/WebServiceGetCommand.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Mon Jul 02 18:13:46 2018 +0200
@@ -21,34 +21,19 @@
 
 #pragma once
 
-#include "IOracleCommand.h"
-
-#include "../../Framework/Toolbox/IWebService.h"
-
-#include <Core/WebServiceParameters.h>
-
-#include <memory>
+#include "WebServiceCommandBase.h"
 
 namespace OrthancStone
 {
-  class WebServiceGetCommand : public IOracleCommand
+  class WebServiceGetCommand : public WebServiceCommandBase
   {
-  private:
-    IWebService::ICallback&                 callback_;
-    Orthanc::WebServiceParameters           parameters_;
-    std::string                             uri_;
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-    bool                                    success_;
-    std::string                             answer_;
-
   public:
-    WebServiceGetCommand(IWebService::ICallback& callback,
+    WebServiceGetCommand(MessageBroker& broker,
+                         IWebService::ICallback& callback,
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
                          Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- a/Platforms/Generic/WebServicePostCommand.cpp	Mon Jul 02 16:36:17 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -25,16 +25,14 @@
 
 namespace OrthancStone
 {
-  WebServicePostCommand::WebServicePostCommand(IWebService::ICallback& callback,
+  WebServicePostCommand::WebServicePostCommand(MessageBroker& broker,
+                                               IWebService::ICallback& callback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */) :
-    callback_(callback),
-    parameters_(parameters),
-    uri_(uri),
-    body_(body),
-    payload_(payload)
+    WebServiceCommandBase(broker, callback, parameters, uri, payload),
+    body_(body)
   {
   }
 
@@ -47,15 +45,4 @@
     success_ = client.Apply(answer_);
   }
 
-  void WebServicePostCommand::Commit()
-  {
-    if (success_)
-    {
-      callback_.NotifySuccess(uri_, answer_.c_str(), answer_.size(), payload_.release());
-    }
-    else
-    {
-      callback_.NotifyError(uri_, payload_.release());
-    }
-  }
 }
--- a/Platforms/Generic/WebServicePostCommand.h	Mon Jul 02 16:36:17 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Mon Jul 02 18:13:46 2018 +0200
@@ -21,36 +21,23 @@
 
 #pragma once
 
-#include "IOracleCommand.h"
-
-#include "../../Framework/Toolbox/IWebService.h"
-
-#include <Core/WebServiceParameters.h>
-
-#include <memory>
+#include "WebServiceCommandBase.h"
 
 namespace OrthancStone
 {
-  class WebServicePostCommand : public IOracleCommand
+  class WebServicePostCommand : public WebServiceCommandBase
   {
-  private:
-    IWebService::ICallback&                 callback_;
-    Orthanc::WebServiceParameters           parameters_;
-    std::string                             uri_;
+  protected:
     std::string                             body_;
-    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
-    bool                                    success_;
-    std::string                             answer_;
 
   public:
-    WebServicePostCommand(IWebService::ICallback& callback,
+    WebServicePostCommand(MessageBroker& broker,
+                          IWebService::ICallback& callback,
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
                           const std::string& body,
                           Orthanc::IDynamicObject* payload /* takes ownership */);
 
     virtual void Execute();
-
-    virtual void Commit();
   };
 }
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Jul 02 16:36:17 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Jul 02 18:13:46 2018 +0200
@@ -142,6 +142,7 @@
 
 if (NOT ORTHANC_SANDBOXED)
   set(PLATFORM_SOURCES
+    ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceCommandBase.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceGetCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServicePostCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker.cpp	Mon Jul 02 18:13:46 2018 +0200
@@ -0,0 +1,109 @@
+/**
+ * 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/>.
+ **/
+
+
+#include "gtest/gtest.h"
+
+#include "../Framework/Messages/MessageBroker.h"
+#include "../Framework/Messages/IMessage.h"
+#include "../Framework/Messages/IObservable.h"
+#include "../Framework/Messages/IObserver.h"
+#include "../Framework/StoneEnumerations.h"
+
+
+static int globalCounter = 0;
+class MyObserver : public OrthancStone::IObserver
+{
+
+public:
+  MyObserver(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObserver(broker)
+  {}
+
+
+  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+    if (message.GetType() == OrthancStone::MessageType_Generic) {
+      globalCounter++;
+    }
+
+  }
+
+};
+
+
+TEST(MessageBroker, NormalUsage)
+{
+  OrthancStone::MessageBroker broker;
+  OrthancStone::IObservable observable(broker);
+
+  globalCounter = 0;
+
+  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+
+  // no observers have been registered -> nothing shall happen
+  observable.Emit(genericMessage);
+
+  ASSERT_EQ(0, globalCounter);
+
+  // register an observer, check it is called
+  MyObserver observer(broker);
+  observable.RegisterObserver(observer);
+
+  observable.Emit(genericMessage);
+
+  ASSERT_EQ(1, globalCounter);
+
+  // check the observer is not called when another message is issued
+  OrthancStone::IMessage wrongMessage(OrthancStone::MessageType_GeometryReady);
+  // no observers have been registered
+  observable.Emit(wrongMessage);
+
+  ASSERT_EQ(1, globalCounter);
+
+  // unregister the observer, make sure nothing happens afterwards
+  observable.UnregisterObserver(observer);
+  observable.Emit(genericMessage);
+  ASSERT_EQ(1, globalCounter);
+}
+
+TEST(MessageBroker, DeleteObserverWhileRegistered)
+{
+  OrthancStone::MessageBroker broker;
+  OrthancStone::IObservable observable(broker);
+
+  globalCounter = 0;
+
+  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+
+  {
+    // register an observer, check it is called
+    MyObserver observer(broker);
+    observable.RegisterObserver(observer);
+
+    observable.Emit(genericMessage);
+
+    ASSERT_EQ(1, globalCounter);
+  }
+
+  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
+  observable.Emit(genericMessage);
+
+  ASSERT_EQ(1, globalCounter);
+}