changeset 299:3897f9f28cfa am-callable-and-promise

backup work in progress: updated messaging framework with ICallable
author am@osimis.io
date Fri, 14 Sep 2018 16:44:01 +0200
parents f58bfb7bbcc9
children b4abaeb783b1
files Applications/Samples/CMakeLists.txt Applications/Samples/SimpleViewerApplication.h Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Layers/OrthancFrameLayerSource.h Framework/Messages/ICallable.h Framework/Messages/IMessage.h Framework/Messages/IObservable.h Framework/Messages/IObserver.h Framework/Messages/MessageBroker.cpp Framework/Messages/MessageBroker.h Framework/Messages/MessageType.h Framework/Messages/Promise.h Framework/SmartLoader.cpp Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.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/Wasm/WasmWebService.cpp Platforms/Wasm/WasmWebService.h Platforms/Wasm/WasmWebService.js Platforms/Wasm/wasm-application-runner.ts Resources/CMake/OrthancStoneConfiguration.cmake UnitTestsSources/TestMessageBroker.cpp UnitTestsSources/TestMessageBroker2.cpp UnitTestsSources/TestMessageBroker2_connect_ok.cpp UnitTestsSources/TestMessageBroker2_promise_and_connect_ok.cpp
diffstat 38 files changed, 2348 insertions(+), 394 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/CMakeLists.txt	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/CMakeLists.txt	Fri Sep 14 16:44:01 2018 +0200
@@ -140,6 +140,7 @@
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestCommands.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestExceptions.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker.cpp
+    ${ORTHANC_STONE_ROOT}/UnitTestsSources/TestMessageBroker2.cpp
     ${ORTHANC_STONE_ROOT}/UnitTestsSources/UnitTestsMain.cpp
     )
 
--- a/Applications/Samples/SimpleViewerApplication.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Fri Sep 14 16:44:01 2018 +0200
@@ -204,12 +204,7 @@
         wasmViewport1_(NULL),
         wasmViewport2_(NULL)
       {
-        DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
-        DeclareHandledMessage(MessageType_Widget_GeometryChanged);
-
-        DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready);
-        DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready);
-        DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready);
+//        DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
       }
 
       virtual void Finalize() {}
@@ -249,7 +244,7 @@
           thumbnailsLayout_->SetVertical();
 
           mainWidget_ = new LayerWidget(broker_, "main-viewport");
-          mainWidget_->RegisterObserver(*this);
+          //mainWidget_->RegisterObserver(*this);
 
           // hierarchy
           mainLayout_->AddWidget(thumbnailsLayout_);
@@ -273,7 +268,7 @@
         if (parameters.count("studyId") < 1)
         {
           LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
-          orthancApiClient_->ScheduleGetStudyIds(*this);
+          orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
         }
         else
         {
@@ -281,26 +276,32 @@
         }
       }
 
-      void OnStudyListReceived(const Json::Value& response)
+      void OnStudyListReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
       {
+        const Json::Value& response = message.response_;
+
         if (response.isArray() && response.size() > 1)
         {
           SelectStudy(response[0].asString());
         }
       }
-      void OnStudyReceived(const Json::Value& response)
+      void OnStudyReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
       {
+        const Json::Value& response = message.response_;
+
         if (response.isObject() && response["Series"].isArray())
         {
           for (size_t i=0; i < response["Series"].size(); i++)
           {
-            orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString());
+            orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
           }
         }
       }
 
-      void OnSeriesReceived(const Json::Value& response)
+      void OnSeriesReceived(const OrthancApiClient::NewGetJsonResponseReadyMessage& message)
       {
+        const Json::Value& response = message.response_;
+
         if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
         {
           // keep track of all instances IDs
@@ -330,34 +331,19 @@
         LayerWidget* thumbnailWidget = new LayerWidget(broker_, "thumbnail-series-" + seriesId);
         thumbnails_.push_back(thumbnailWidget);
         thumbnailsLayout_->AddWidget(thumbnailWidget);
-        thumbnailWidget->RegisterObserver(*this);
+        thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
         thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0));
         thumbnailWidget->SetInteractor(*thumbnailInteractor_);
       }
 
       void SelectStudy(const std::string& studyId)
       {
-        orthancApiClient_->ScheduleGetStudy(*this, studyId);
+        orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::NewGetJsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
       }
 
-      virtual void HandleMessage(IObservable& from, const IMessage& message) {
-        switch (message.GetType()) {
-        case MessageType_Widget_GeometryChanged:
-          LOG(INFO) << "Widget geometry ready: " << dynamic_cast<LayerWidget&>(from).GetName();
-          dynamic_cast<LayerWidget&>(from).SetDefaultView();
-          break;
-        case MessageType_OrthancApi_GetStudyIds_Ready:
-          OnStudyListReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
-          break;
-        case MessageType_OrthancApi_GetSeries_Ready:
-          OnSeriesReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
-          break;
-        case MessageType_OrthancApi_GetStudy_Ready:
-          OnStudyReceived(dynamic_cast<const OrthancApiClient::GetJsonResponseReadyMessage&>(message).response_);
-          break;
-        default:
-          VLOG("unhandled message type" << message.GetType());
-        }
+      void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
+      {
+        message.origin_.SetDefaultView();
       }
 
       void SelectSeriesInMainViewport(const std::string& seriesId)
--- a/Framework/Layers/ILayerSource.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Fri Sep 14 16:44:01 2018 +0200
@@ -31,6 +31,11 @@
   class ILayerSource : public IObservable
   {
   public:
+
+    typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage;
+    typedef NoPayloadMessage<MessageType_LayerSource_GeometryError> GeometryErrorMessage;
+    typedef NoPayloadMessage<MessageType_LayerSource_ContentChanged> ContentChangedMessage;
+
     struct SliceChangedMessage : public IMessage
     {
       const Slice& slice_;
--- a/Framework/Layers/LayerSourceBase.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -27,17 +27,17 @@
 {
   void LayerSourceBase::NotifyGeometryReady()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_GeometryReady));
+    EmitMessage(ILayerSource::GeometryReadyMessage());
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_GeometryError));
+    EmitMessage(ILayerSource::GeometryErrorMessage());
   }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_ContentChanged));
+    EmitMessage(ILayerSource::ContentChangedMessage());
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
--- a/Framework/Layers/LayerSourceBase.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Fri Sep 14 16:44:01 2018 +0200
@@ -44,11 +44,11 @@
     LayerSourceBase(MessageBroker& broker)
       : ILayerSource(broker)
     {
-      DeclareEmittableMessage(MessageType_LayerSource_GeometryReady);
-      DeclareEmittableMessage(MessageType_LayerSource_GeometryError);
-      DeclareEmittableMessage(MessageType_LayerSource_ContentChanged);
-      DeclareEmittableMessage(MessageType_LayerSource_SliceChanged);
-      DeclareEmittableMessage(MessageType_LayerSource_LayerReady);
+//      DeclareEmittableMessage(MessageType_LayerSource_GeometryReady);
+//      DeclareEmittableMessage(MessageType_LayerSource_GeometryError);
+//      DeclareEmittableMessage(MessageType_LayerSource_ContentChanged);
+//      DeclareEmittableMessage(MessageType_LayerSource_SliceChanged);
+//      DeclareEmittableMessage(MessageType_LayerSource_LayerReady);
     }
 
   };
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -31,46 +31,35 @@
 
 namespace OrthancStone
 {
-  void OrthancFrameLayerSource::HandleMessage(IObservable& from, const IMessage& message)
+
+  void OrthancFrameLayerSource::OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message)
   {
-    switch (message.GetType())
-    {
-    case MessageType_SliceLoader_GeometryReady:
-    {
-      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
-      if (loader.GetSliceCount() > 0)
-      {
-        LayerSourceBase::NotifyGeometryReady();
-      }
-      else
-      {
-        LayerSourceBase::NotifyGeometryError();
-      }
-
-    }; break;
-    case MessageType_SliceLoader_GeometryError:
+    if (message.origin_.GetSliceCount() > 0)
     {
-      const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
-      LayerSourceBase::NotifyGeometryError();
-    }; break;
-    case MessageType_SliceLoader_ImageReady:
+      LayerSourceBase::NotifyGeometryReady();
+    }
+    else
     {
-      const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
-      bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam);
-      LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(msg.image_.release(), msg.slice_, isFull),
-                                        msg.slice_.GetGeometry(), false);
-
-    }; break;
-    case MessageType_SliceLoader_ImageError:
-    {
-      const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
-      LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true);
-    }; break;
-    default:
-      VLOG("unhandled message type" << message.GetType());
+      LayerSourceBase::NotifyGeometryError();
     }
   }
 
+  void OrthancFrameLayerSource::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message)
+  {
+    LayerSourceBase::NotifyGeometryError();
+  }
+
+  void OrthancFrameLayerSource::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message)
+  {
+    bool isFull = (message.effectiveQuality_ == SliceImageQuality_FullPng || message.effectiveQuality_ == SliceImageQuality_FullPam);
+    LayerSourceBase::NotifyLayerReady(FrameRenderer::CreateRenderer(message.image_.release(), message.slice_, isFull),
+                                      message.slice_.GetGeometry(), false);
+  }
+
+  void OrthancFrameLayerSource::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message)
+  {
+    LayerSourceBase::NotifyLayerReady(NULL, message.slice_.GetGeometry(), true);
+  }
 
   OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
     LayerSourceBase(broker),
@@ -79,11 +68,14 @@
     loader_(broker, orthanc),
     quality_(SliceImageQuality_FullPng)
   {
-    DeclareHandledMessage(MessageType_SliceLoader_GeometryReady);
-    DeclareHandledMessage(MessageType_SliceLoader_GeometryError);
-    DeclareHandledMessage(MessageType_SliceLoader_ImageReady);
-    DeclareHandledMessage(MessageType_SliceLoader_ImageError);
-    loader_.RegisterObserver(*this);
+//    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));
+    loader_.RegisterObserverCallback(new Callable<OrthancFrameLayerSource, OrthancSlicesLoader::SliceImageErrorMessage>(*this, &OrthancFrameLayerSource::OnSliceImageError));
   }
 
   
--- a/Framework/Layers/OrthancFrameLayerSource.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.h	Fri Sep 14 16:44:01 2018 +0200
@@ -69,6 +69,11 @@
 
     virtual void ScheduleLayerCreation(const CoordinateSystem3D& viewportSlice);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
+protected:
+    void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message);
+    void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message);
+    void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message);
+    void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message);
+//    virtual void HandleMessage(IObservable& from, const IMessage& message);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/ICallable.h	Fri Sep 14 16:44:01 2018 +0200
@@ -0,0 +1,92 @@
+/**
+ * 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 "IMessage.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone {
+
+  class IObserver;
+
+  // 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 MessageType GetMessageType() const = 0;
+    virtual IObserver* GetObserver() const = 0;
+  };
+
+  template <typename TMessage>
+  class MessageHandler: public ICallable
+  {
+  };
+
+
+  template <typename TObserver,
+            typename TMessage>
+  class Callable : public MessageHandler<TMessage>
+  {
+  private:
+    typedef void (TObserver::* MemberFunction) (const TMessage&);
+
+    TObserver&      observer_;
+    MemberFunction  function_;
+
+  public:
+    Callable(TObserver& observer,
+             MemberFunction function) :
+      observer_(observer),
+      function_(function)
+    {
+    }
+
+    void ApplyInternal(const TMessage& message)
+    {
+      (observer_.*function_) (message);
+    }
+
+    virtual void Apply(const IMessage& message)
+    {
+      ApplyInternal(dynamic_cast<const TMessage&>(message));
+    }
+
+    virtual MessageType GetMessageType() const
+    {
+      return static_cast<MessageType>(TMessage::Type);
+    }
+
+    virtual IObserver* GetObserver() const
+    {
+      return &observer_;
+    }
+  };
+}
--- a/Framework/Messages/IMessage.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/IMessage.h	Fri Sep 14 16:44:01 2018 +0200
@@ -27,16 +27,61 @@
 
 namespace OrthancStone {
 
-  struct IMessage  : public boost::noncopyable
+
+  // base message that are exchanged between IObservable and IObserver
+  struct IMessage : public boost::noncopyable
   {
     MessageType messageType_;
-  public:
+  protected:
     IMessage(const MessageType& messageType)
       : messageType_(messageType)
     {}
+  public:
     virtual ~IMessage() {}
 
     MessageType GetType() const {return messageType_;}
   };
 
+
+  // base class to derive from to implement your own messages
+  // it handles the message type for you
+  template <MessageType type>
+  struct BaseMessage : public IMessage
+  {
+    enum
+    {
+      Type = type
+    };
+
+    BaseMessage()
+      : IMessage(static_cast<MessageType>(Type))
+    {}
+  };
+
+  // simple message implementation when no payload is needed
+  // sample usage:
+  // typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage;
+  template <MessageType type>
+  struct NoPayloadMessage : public BaseMessage<type>
+  {
+    NoPayloadMessage()
+      : BaseMessage<type>()
+    {}
+
+  };
+
+  // simple message implementation when no payload is needed but the origin is required
+  // sample usage:
+  // typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage;
+  template <MessageType type, typename TOrigin>
+  struct OriginMessage : public BaseMessage<type>
+  {
+    TOrigin& origin_;
+    OriginMessage(TOrigin& origin)
+      : BaseMessage<type>(),
+        origin_(origin)
+    {}
+
+  };
+
 }
--- a/Framework/Messages/IObservable.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/IObservable.h	Fri Sep 14 16:44:01 2018 +0200
@@ -25,9 +25,11 @@
 #include <assert.h>
 #include <algorithm>
 #include <iostream>
+#include <map>
 
 #include "MessageBroker.h"
 #include "MessageType.h"
+#include "ICallable.h"
 #include "IObserver.h"
 
 namespace OrthancStone {
@@ -48,7 +50,8 @@
   protected:
     MessageBroker&                     broker_;
 
-    std::set<IObserver*>              observers_;
+    typedef std::map<int, std::set<ICallable*> >   Callables;
+    Callables                         callables_;
     std::set<MessageType>             emittableMessages_;
 
   public:
@@ -59,6 +62,22 @@
     }
     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 RegisterObserverCallback(ICallable* callable)
+    {
+      MessageType messageType = callable->GetMessageType();
+
+      callables_[messageType].insert(callable);
     }
 
     void EmitMessage(const IMessage& message)
@@ -68,25 +87,24 @@
         throw MessageNotDeclaredException(message.GetType());
       }
 
-      broker_.EmitMessage(*this, observers_, message);
-    }
+      Callables::const_iterator found = callables_.find(message.GetType());
 
-    void RegisterObserver(IObserver& observer)
-    {
-      CheckObserverDeclaredAllObservableMessages(observer);
-      observers_.insert(&observer);
+      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);
+          }
+        }
+      }
     }
-
-    void UnregisterObserver(IObserver& observer)
-    {
-      observers_.erase(&observer);
-    }
-
     const std::set<MessageType>& GetEmittableMessages() const
     {
       return emittableMessages_;
     }
-
   protected:
 
     void DeclareEmittableMessage(MessageType messageType)
@@ -94,18 +112,6 @@
       emittableMessages_.insert(messageType);
     }
 
-    void CheckObserverDeclaredAllObservableMessages(IObserver& observer)
-    {
-      for (std::set<MessageType>::const_iterator it = emittableMessages_.begin(); it != emittableMessages_.end(); it++)
-      {
-        // the observer must have "declared" all observable messages
-        if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end()
-            && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end())
-        {
-          throw MessageNotDeclaredException(*it);
-        }
-      }
-    }
   };
 
 }
--- a/Framework/Messages/IObserver.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/IObserver.h	Fri Sep 14 16:44:01 2018 +0200
@@ -49,39 +49,39 @@
       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
+//    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);
-    }
+//      HandleMessage(from, message);
+//    }
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
+//    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
 
 
-    const std::set<MessageType>& GetHandledMessages() const
-    {
-      return handledMessages_;
-    }
+//    const std::set<MessageType>& GetHandledMessages() const
+//    {
+//      return handledMessages_;
+//    }
 
-    const std::set<MessageType>& GetIgnoredMessages() const
-    {
-      return ignoredMessages_;
-    }
+//    const std::set<MessageType>& GetIgnoredMessages() const
+//    {
+//      return ignoredMessages_;
+//    }
 
-  protected:
+//  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);
-    }
+//    // 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);
-    }
+//    void DeclareIgnoredMessage(MessageType messageType)
+//    {
+//      ignoredMessages_.insert(messageType);
+//    }
 
   };
 
--- a/Framework/Messages/MessageBroker.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * 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 "MessageBroker.h"
-
-#include <algorithm>
-#include <assert.h>
-#include <vector>
-
-#include "IObserver.h"
-#include "MessageType.h"
-
-namespace OrthancStone {
-
-  void MessageBroker::EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message)
-  {
-    std::vector<IObserver*> activeObservers;
-    std::set_intersection(observers.begin(),
-                          observers.end(),
-                          activeObservers_.begin(),
-                          activeObservers_.end(),
-                          std::back_inserter(activeObservers)
-                          );
-
-    for (std::vector<IObserver*>::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++)
-    {
-      if ((*observer)->GetHandledMessages().find(message.GetType()) != (*observer)->GetHandledMessages().end())
-      {
-        (*observer)->HandleMessage_(from, message);
-      }
-      else
-      {
-        assert((*observer)->GetIgnoredMessages().find(message.GetType()) != (*observer)->GetIgnoredMessages().end()); // message has not been declared by Observer (this should already have been checked during registration)
-      }
-    }
-  }
-
-}
--- a/Framework/Messages/MessageBroker.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/MessageBroker.h	Fri Sep 14 16:44:01 2018 +0200
@@ -21,23 +21,18 @@
 
 #pragma once
 
-#include "../StoneEnumerations.h"
-
 #include "boost/noncopyable.hpp"
-#include <map>
-#include <list>
 #include <set>
 
 namespace OrthancStone
 {
   class IObserver;
   class IObservable;
-  class IMessage;
 
   /*
    * 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 dead observer.
+   * This way, it can prevent an observable to send a message to a deleted observer.
    */
   class MessageBroker : public boost::noncopyable
   {
@@ -56,7 +51,10 @@
       activeObservers_.erase(&observer);
     }
 
-    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
+    bool IsActive(IObserver* observer)
+    {
+      return activeObservers_.find(observer) != activeObservers_.end();
+    }
   };
 
 }
--- a/Framework/Messages/MessageType.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/MessageType.h	Fri Sep 14 16:44:01 2018 +0200
@@ -44,6 +44,8 @@
     MessageType_OrthancApi_InternalGetJsonResponseReady,
     MessageType_OrthancApi_InternalGetJsonResponseError,
 
+    MessageType_OrthancApi_GenericGetJson_Ready,
+    MessageType_OrthancApi_GenericHttpError_Ready,
     MessageType_OrthancApi_GetStudyIds_Ready,
     MessageType_OrthancApi_GetStudy_Ready,
     MessageType_OrthancApi_GetSeries_Ready,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/Promise.h	Fri Sep 14 16:44:01 2018 +0200
@@ -0,0 +1,89 @@
+/**
+ * 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 "MessageBroker.h"
+#include "ICallable.h"
+#include "IMessage.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone {
+
+  class Promise : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+    ICallable* successCallable_;
+    ICallable* failureCallable_;
+
+  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 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& Else(ICallable* failureCallable)
+    {
+      if (failureCallable_ != NULL)
+      {
+        // TODO: throw throw new "Promise may only have a single failure target"
+      }
+      failureCallable_ = failureCallable;
+      return *this;
+    }
+
+  };
+
+
+}
--- a/Framework/SmartLoader.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/SmartLoader.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -31,11 +31,11 @@
     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_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);
@@ -65,7 +65,7 @@
     }
 
     // forward messages to its own observers
-    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
+    // TODO TODO TODO  IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
   }
 
   ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame)
@@ -78,7 +78,7 @@
     // in both cases, we must be carefull about objects lifecycle !!!
     std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_));
     layerSource->SetImageQuality(imageQuality_);
-    layerSource->RegisterObserver(*this);
+    //layerSource->RegisterObserverCallback(new Callable<SmartLoader, ILayerSource::GeometryReadyMessage>(*this, &SmartLoader::....));
     layerSource->LoadFrame(instanceId, frame);
 
     return layerSource.release();
--- a/Framework/Toolbox/IWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Fri Sep 14 16:44:01 2018 +0200
@@ -23,7 +23,9 @@
 
 #include <Core/IDynamicObject.h>
 #include "../../Framework/Messages/IObserver.h"
+#include "../../Framework/Messages/ICallable.h"
 #include <string>
+#include <map>
 #include <Core/Logging.h>
 
 namespace OrthancStone
@@ -40,6 +42,37 @@
   public:
     typedef std::map<std::string, std::string> Headers;
 
+    struct NewHttpRequestSuccessMessage: public BaseMessage<MessageType_HttpRequestSuccess>
+    {
+      const std::string& Uri;
+      const void* Answer;
+      size_t AnswerSize;
+      Orthanc::IDynamicObject* Payload;
+      NewHttpRequestSuccessMessage(const std::string& uri,
+                                const void* answer,
+                                size_t answerSize,
+                                Orthanc::IDynamicObject* payload)
+        : BaseMessage(),
+          Uri(uri),
+          Answer(answer),
+          AnswerSize(answerSize),
+          Payload(payload)
+      {}
+    };
+
+    struct NewHttpRequestErrorMessage: public BaseMessage<MessageType_HttpRequestError>
+    {
+      const std::string& Uri;
+      Orthanc::IDynamicObject* Payload;
+      NewHttpRequestErrorMessage(const std::string& uri,
+                              Orthanc::IDynamicObject* payload)
+        : BaseMessage(),
+          Uri(uri),
+          Payload(payload)
+      {}
+    };
+
+
     class ICallback : public IObserver
     {
     public:
@@ -76,8 +109,8 @@
       ICallback(MessageBroker& broker)
         : IObserver(broker)
       {
-        DeclareHandledMessage(MessageType_HttpRequestError);
-        DeclareHandledMessage(MessageType_HttpRequestSuccess);
+//        DeclareHandledMessage(MessageType_HttpRequestError);
+//        DeclareHandledMessage(MessageType_HttpRequestSuccess);
       }
       virtual ~ICallback()
       {
@@ -129,6 +162,13 @@
                                     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 SchedulePostRequest(ICallback& callback,
                                      const std::string& uri,
                                      const Headers& headers,
--- a/Framework/Toolbox/OrthancApiClient.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -32,7 +32,7 @@
     Json::Value   response_;
 
     InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest*  request,
-                             const Json::Value& response)
+                                        const Json::Value& response)
       : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady),
         request_(request),
         response_(response)
@@ -58,7 +58,7 @@
   // 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 IObserver,
       public IObservable,
       public Orthanc::IDynamicObject
   {
@@ -71,10 +71,10 @@
   public:
     BaseRequest(
         OrthancApiClient& orthanc,
-                   IObserver& responseObserver,
-                   const std::string& uri,
-                   MessageType messageToEmitWhenResponseReady,
-                   OrthancApiClient::Mode mode)
+        IObserver& responseObserver,
+        const std::string& uri,
+        MessageType messageToEmitWhenResponseReady,
+        OrthancApiClient::Mode mode)
       :
         //IObserver(orthanc.broker_),
         IObservable(orthanc.broker_),
@@ -86,30 +86,30 @@
       // 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);
+      //      // 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);
+      //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());
-//      }
-//    }
+    //    // 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());
+    //      }
+    //    }
 
   };
 
@@ -144,8 +144,8 @@
         }
         else
         {
-//          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-//          that_.EmitMessage(msg);
+          //          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+          //          that_.EmitMessage(msg);
         }
       };  break;
 
@@ -164,9 +164,9 @@
       {
       case OrthancApiClient::Mode_GetJson:
       {
-//        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-//        that_.EmitMessage(msg);
-          // TODO: the request shall send an error message
+        //        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
+        //        that_.EmitMessage(msg);
+        // TODO: the request shall send an error message
       };  break;
 
       default:
@@ -202,4 +202,61 @@
     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_;
+  public:
+    HttpResponseToJsonConverter(MessageBroker& broker,
+                                MessageHandler<OrthancApiClient::NewGetJsonResponseReadyMessage>* orthancApiSuccessCallback,
+                                MessageHandler<OrthancApiClient::NewHttpErrorMessage>* orthancApiFailureCallback)
+      : IObserver(broker),
+        IObservable(broker),
+        orthancApiSuccessCallback_(orthancApiSuccessCallback),
+        orthancApiFailureCallback_(orthancApiFailureCallback)
+    {
+    }
+
+    void ConvertResponseToJson(const IWebService::NewHttpRequestSuccessMessage& message)
+    {
+      Json::Value response;
+      if (MessagingToolbox::ParseJson(response, message.Answer, message.AnswerSize))
+      {
+        if (orthancApiSuccessCallback_.get() != NULL)
+        {
+          orthancApiSuccessCallback_->Apply(OrthancApiClient::NewGetJsonResponseReadyMessage(message.Uri, response));
+        }
+      }
+      else if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::NewHttpErrorMessage(message.Uri));
+      }
+
+      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::NewHttpErrorMessage(message.Uri));
+      }
+
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
+    }
+  };
+
+  void OrthancApiClient::GetJsonAsync(const std::string& uri,
+                                      MessageHandler<NewGetJsonResponseReadyMessage>* successCallback,
+                                      MessageHandler<NewHttpErrorMessage>* failureCallback)
+  {
+    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);
+    orthanc_.GetAsync(uri, IWebService::Headers(), NULL,
+                      new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson),
+                      new Callable<HttpResponseToJsonConverter, IWebService::NewHttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError));
+
+  }
+
 }
--- a/Framework/Toolbox/OrthancApiClient.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.h	Fri Sep 14 16:44:01 2018 +0200
@@ -26,7 +26,7 @@
 
 #include "IWebService.h"
 #include "../Messages/IObservable.h"
-
+#include "../Messages/Promise.h"
 
 namespace OrthancStone
 {
@@ -56,6 +56,32 @@
       }
     };
 
+    struct NewGetJsonResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready>
+    {
+      Json::Value   response_;
+      std::string   uri_;
+
+      NewGetJsonResponseReadyMessage(const std::string& uri,
+                                     const Json::Value& response)
+        : BaseMessage(),
+          response_(response),
+          uri_(uri)
+      {
+      }
+    };
+
+    struct NewHttpErrorMessage : public BaseMessage<MessageType_OrthancApi_GenericHttpError_Ready>
+    {
+      std::string   uri_;
+
+      NewHttpErrorMessage(const std::string& uri)
+        : BaseMessage(),
+          uri_(uri)
+      {
+      }
+    };
+
+
   public:
 
     enum Mode
@@ -69,13 +95,13 @@
     boost::shared_ptr<WebCallback>    webCallback_;  // This is a PImpl pattern
     std::set<BaseRequest*>            requestsInProgress_;
 
-//    int ScheduleGetJsonRequest(const std::string& uri);
+    //    int ScheduleGetJsonRequest(const std::string& uri);
 
     void ReleaseRequest(BaseRequest* request);
 
   public:
     OrthancApiClient(MessageBroker& broker,
-                        IWebService& orthanc);
+                     IWebService& orthanc);
     virtual ~OrthancApiClient() {}
 
     // schedule a GET request expecting a JSON request.
@@ -86,5 +112,11 @@
     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);}
 
+    void GetJsonAsync(const std::string& uri,
+                      MessageHandler<NewGetJsonResponseReadyMessage>* successCallback,
+                      MessageHandler<NewHttpErrorMessage>* failureCallback = NULL);
+
+
+
   };
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -253,7 +253,7 @@
       {
       case Mode_FrameGeometry:
       case Mode_SeriesGeometry:
-        that_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+        that_.EmitMessage(SliceGeometryErrorMessage(that_));
         that_.state_ = State_Error;
         break;
         
@@ -271,11 +271,11 @@
     }
   };
   
-  void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    // forward messages to its own observers
-    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
-  }
+//  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,
@@ -321,12 +321,12 @@
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
+      EmitMessage(SliceGeometryReadyMessage(*this));
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
     }
   }
   
@@ -338,7 +338,7 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
       return;
     }
     
@@ -385,7 +385,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
       return;
     }
     
@@ -412,7 +412,7 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+        EmitMessage(SliceGeometryErrorMessage(*this));
         return;
       }
     }
@@ -430,7 +430,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
       return;
     }
     
@@ -446,12 +446,12 @@
     {
       LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
+      EmitMessage(SliceGeometryErrorMessage(*this));
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
     }
   }
   
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Fri Sep 14 16:44:01 2018 +0200
@@ -32,7 +32,11 @@
   class OrthancSlicesLoader : public IObservable
   {
   public:
-    struct SliceImageReadyMessage : public IMessage
+
+    typedef OriginMessage<MessageType_SliceLoader_GeometryReady, OrthancSlicesLoader> SliceGeometryReadyMessage;
+    typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage;
+
+    struct SliceImageReadyMessage : public BaseMessage<MessageType_SliceLoader_ImageReady>
     {
       unsigned int sliceIndex_;
       const Slice& slice_;
@@ -43,7 +47,7 @@
                         const Slice& slice,
                         std::auto_ptr<Orthanc::ImageAccessor>& image,
                         SliceImageQuality effectiveQuality)
-        : IMessage(MessageType_SliceLoader_ImageReady),
+        : BaseMessage(),
           sliceIndex_(sliceIndex),
           slice_(slice),
           image_(image),
@@ -52,7 +56,7 @@
       }
     };
 
-    struct SliceImageErrorMessage : public IMessage
+    struct SliceImageErrorMessage : public BaseMessage<MessageType_SliceLoader_ImageError>
     {
       const Slice& slice_;
       unsigned int sliceIndex_;
@@ -61,7 +65,7 @@
       SliceImageErrorMessage(unsigned int sliceIndex,
                         const Slice& slice,
                         SliceImageQuality effectiveQuality)
-        : IMessage(MessageType_SliceLoader_ImageError),
+        : BaseMessage(),
           slice_(slice),
           sliceIndex_(sliceIndex),
           effectiveQuality_(effectiveQuality)
@@ -166,6 +170,6 @@
     void ScheduleLoadSliceImage(size_t index,
                                 SliceImageQuality requestedQuality);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
+//    virtual void HandleMessage(IObservable& from, const IMessage& message);
   };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -365,14 +365,14 @@
     IObservable(broker),
     started_(false)
   {
-    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
-    DeclareHandledMessage(MessageType_LayerSource_ContentChanged);
-    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
-    DeclareHandledMessage(MessageType_LayerSource_SliceChanged);
-    DeclareHandledMessage(MessageType_LayerSource_GeometryError);
+//    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
+//    DeclareHandledMessage(MessageType_LayerSource_ContentChanged);
+//    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
+//    DeclareHandledMessage(MessageType_LayerSource_SliceChanged);
+//    DeclareHandledMessage(MessageType_LayerSource_GeometryError);
 
-    DeclareEmittableMessage(MessageType_Widget_GeometryChanged);
-    DeclareEmittableMessage(MessageType_Widget_ContentChanged);
+//    DeclareEmittableMessage(MessageType_Widget_GeometryChanged);
+//    DeclareEmittableMessage(MessageType_Widget_ContentChanged);
 
     SetBackgroundCleared(true);
   }
@@ -400,7 +400,7 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->RegisterObserver(*this);
+//    layer->RegisterObserver(*this);
 
     ResetChangedLayers();
 
@@ -424,7 +424,7 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->RegisterObserver(*this);
+//    layer->RegisterObserver(*this);
 
     InvalidateLayer(index);
   }
@@ -529,7 +529,7 @@
       changedLayers_[i] = true;
       //layers_[i]->ScheduleLayerCreation(slice_);
     }
-    EmitMessage(IMessage(MessageType_Widget_GeometryChanged));
+    EmitMessage(GeometryChangedMessage(*this));
   }
   
   void LayerWidget::InvalidateAllLayers()
--- a/Framework/Widgets/LayerWidget.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Fri Sep 14 16:44:01 2018 +0200
@@ -35,6 +35,10 @@
       public IObserver,
       public IObservable
   {
+  public:
+    typedef OriginMessage<MessageType_Widget_GeometryChanged, LayerWidget> GeometryChangedMessage;
+    typedef OriginMessage<MessageType_Widget_ContentChanged, LayerWidget> ContentChangedMessage;
+
   private:
     class Scene;
     
--- a/Framework/dev.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/dev.h	Fri Sep 14 16:44:01 2018 +0200
@@ -239,7 +239,7 @@
       computeRange_(computeRange),
       pendingSlices_(0)
     {
-        loader_.RegisterObserver(*this);
+        // TODO: replace with new callables loader_.RegisterObserver(*this);
     }
 
     void ScheduleLoadSeries(const std::string& seriesId)
--- a/Platforms/Generic/OracleWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Fri Sep 14 16:44:01 2018 +0200
@@ -59,6 +59,15 @@
       oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload, context_));
     }
 
+    virtual void GetAsync(const std::string& uri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload,
+                          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_));
+    }
+
     virtual void SchedulePostRequest(ICallback& callback,
                                      const std::string& uri,
                                      const Headers& headers,
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -42,7 +42,7 @@
   {
     DeclareEmittableMessage(MessageType_HttpRequestError);
     DeclareEmittableMessage(MessageType_HttpRequestSuccess);
-    RegisterObserver(callback);
+    // TODO ? RegisterObserver(callback);
   }
 
 
@@ -61,4 +61,40 @@
       EmitMessage(message);
     }
   }
+
+  NewWebServiceCommandBase::NewWebServiceCommandBase(MessageBroker& broker,
+                                                     MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
+                                                     MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,
+                                               const Orthanc::WebServiceParameters& parameters,
+                                               const std::string& uri,
+                                               const IWebService::Headers& headers,
+                                               Orthanc::IDynamicObject* payload /* takes ownership */,
+                                               NativeStoneApplicationContext& context) :
+    IObservable(broker),
+    successCallback_(successCallback),
+    failureCallback_(failureCallback),
+    parameters_(parameters),
+    uri_(uri),
+    headers_(headers),
+    payload_(payload),
+    context_(context)
+  {
+  }
+
+
+  void NewWebServiceCommandBase::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_ && successCallback_.get() != NULL)
+    {
+      successCallback_->Apply(IWebService::NewHttpRequestSuccessMessage(uri_, answer_.c_str(), answer_.size(), payload_.release()));
+    }
+    else if (!success_ && failureCallback_.get() != NULL)
+    {
+      successCallback_->Apply(IWebService::NewHttpRequestErrorMessage(uri_, payload_.release()));
+    }
+
+  }
+
 }
--- a/Platforms/Generic/WebServiceCommandBase.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.h	Fri Sep 14 16:44:01 2018 +0200
@@ -25,6 +25,7 @@
 
 #include "../../Framework/Toolbox/IWebService.h"
 #include "../../Framework/Messages/IObservable.h"
+#include "../../Framework/Messages/ICallable.h"
 #include "../../Applications/Generic/NativeStoneApplicationContext.h"
 
 #include <Core/WebServiceParameters.h>
@@ -58,4 +59,33 @@
 
     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_;
+    std::string                             uri_;
+    std::map<std::string, std::string>      headers_;
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+    bool                                    success_;
+    std::string                             answer_;
+    NativeStoneApplicationContext&          context_;
+
+  public:
+    NewWebServiceCommandBase(MessageBroker& broker,
+                          MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,  // takes ownership
+                          MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback,  // takes ownership
+                          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();
+  };
+
 }
--- a/Platforms/Generic/WebServiceGetCommand.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -51,4 +51,31 @@
     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	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Fri Sep 14 16:44:01 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/>.
  **/
@@ -38,4 +38,20 @@
 
     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/Wasm/WasmWebService.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -11,7 +11,13 @@
                                                 const char* uri,
                                                 const char* headersInJsonString,
                                                 void* payload);
-  
+
+  extern void WasmWebService_NewScheduleGetRequest(void* callableSuccess,
+                                                void* callableFailure,
+                                                const char* uri,
+                                                const char* headersInJsonString,
+                                                void* payload);
+
   extern void WasmWebService_SchedulePostRequest(void* callback,
                                                  const char* uri,
                                                  const char* headersInJsonString,
@@ -51,6 +57,38 @@
    }
   }
 
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NewNotifyError(void* failureCallable,
+                                                       const char* uri,
+                                                       void* payload)
+  {
+    if (failureCallable == NULL)
+    {
+      throw;
+    }
+    else
+    {
+      reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::NewHttpRequestErrorMessage>*>(failureCallable)->
+        Apply(OrthancStone::IWebService::NewHttpRequestErrorMessage(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)));
+    }
+  }
+
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NewNotifySuccess(void* successCallable,
+                                                         const char* uri,
+                                                         const void* body,
+                                                         size_t bodySize,
+                                                         void* payload)
+  {
+    if (successCallable == NULL)
+    {
+      throw;
+    }
+    else
+    {
+      reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::NewHttpRequestSuccessMessage>*>(successCallable)->
+        Apply(OrthancStone::IWebService::NewHttpRequestSuccessMessage(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)));
+   }
+  }
+
   void EMSCRIPTEN_KEEPALIVE WasmWebService_SetBaseUri(const char* baseUri)
   {
     OrthancStone::WasmWebService::GetInstance().SetBaseUri(baseUri);
@@ -119,4 +157,17 @@
     WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(),
                                        body.c_str(), body.size(), payload);
   }
+
+   void WasmWebService::GetAsync(const std::string& relativeUri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload,
+                          MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallable,
+                          MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallable)
+  {
+    std::string uri = baseUri_ + relativeUri;
+    std::string headersInJsonString;
+    ToJsonString(headersInJsonString, headers);
+    WasmWebService_NewScheduleGetRequest(successCallable, failureCallable, uri.c_str(), headersInJsonString.c_str(), payload);
+  }
+
 }
--- a/Platforms/Wasm/WasmWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.h	Fri Sep 14 16:44:01 2018 +0200
@@ -48,6 +48,12 @@
                                      const std::string& body,
                                      Orthanc::IDynamicObject* payload);
 
+    virtual void GetAsync(const std::string& relativeUri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload,
+                          MessageHandler<IWebService::NewHttpRequestSuccessMessage>* successCallback,
+                          MessageHandler<IWebService::NewHttpRequestErrorMessage>* failureCallback);
+
     virtual void Start()
     {
     }
--- a/Platforms/Wasm/WasmWebService.js	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.js	Fri Sep 14 16:44:01 2018 +0200
@@ -30,6 +30,40 @@
     xhr.send();
   },
 
+  WasmWebService_NewScheduleGetRequest: function(callableSuccess, callableFailure, url, headersInJsonString, payload) {
+    // Directly use XMLHttpRequest (no jQuery) to retrieve the raw binary data
+    // http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/
+    var xhr = new XMLHttpRequest();
+    var url_ = UTF8ToString(url);
+    var headersInJsonString_ = UTF8ToString(headersInJsonString);
+
+    xhr.open('GET', url_, true);
+    xhr.responseType = 'arraybuffer';
+    var headers = JSON.parse(headersInJsonString_);
+    for (var key in headers) {
+      xhr.setRequestHeader(key, headers[key]);
+    }
+    //console.log(xhr); 
+    xhr.onreadystatechange = function() {
+      if (this.readyState == XMLHttpRequest.DONE) {
+        if (xhr.status === 200) {
+          // TODO - Is "new Uint8Array()" necessary? This copies the
+          // answer to the WebAssembly stack, hence necessitating
+          // increasing the TOTAL_STACK parameter of Emscripten
+          console.log("WasmWebService success", this.response); 
+          WasmWebService_NewNotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
+                                       this.response.byteLength, payload);
+          console.log("WasmWebService success 2", this.response); 
+        } else {
+          console.log("WasmWebService failed"); 
+          WasmWebService_NewNotifyError(callableFailure, url_, payload);
+        }
+      }
+    }
+    
+    xhr.send();
+  },
+
   WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) {
     var xhr = new XMLHttpRequest();
     var url_ = UTF8ToString(url);
--- a/Platforms/Wasm/wasm-application-runner.ts	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/wasm-application-runner.ts	Fri Sep 14 16:44:01 2018 +0200
@@ -10,6 +10,8 @@
 // global functions
 var WasmWebService_NotifyError: Function = null;
 var WasmWebService_NotifySuccess: Function = null;
+var WasmWebService_NewNotifyError: Function = null;
+var WasmWebService_NewNotifySuccess: Function = null;
 var WasmWebService_SetBaseUri: Function = null;
 var NotifyUpdateContent: Function = null;
 var SetStartupParameter: Function = null;
@@ -95,6 +97,8 @@
 
     WasmWebService_NotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NotifySuccess', null, ['number', 'string', 'array', 'number', 'number']);
     WasmWebService_NotifyError = StoneFrameworkModule.cwrap('WasmWebService_NotifyError', null, ['number', 'string', 'number']);
+    WasmWebService_NewNotifySuccess = StoneFrameworkModule.cwrap('WasmWebService_NewNotifySuccess', null, ['number', 'string', 'array', 'number', 'number']);
+    WasmWebService_NewNotifyError = StoneFrameworkModule.cwrap('WasmWebService_NewNotifyError', null, ['number', 'string', 'number']);
     WasmWebService_SetBaseUri = StoneFrameworkModule.cwrap('WasmWebService_SetBaseUri', null, ['string']);
     NotifyUpdateContent = StoneFrameworkModule.cwrap('NotifyUpdateContent', null, []);
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Sep 10 12:22:26 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Sep 14 16:44:01 2018 +0200
@@ -280,11 +280,13 @@
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WidgetBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Widgets/WorldSceneWidget.cpp
 
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/ICallable.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IMessage.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObservable.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/IObserver.h
-  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageBroker.h
   ${ORTHANC_STONE_ROOT}/Framework/Messages/MessageType.h
+  ${ORTHANC_STONE_ROOT}/Framework/Messages/Promise.h
 
   ${ORTHANC_ROOT}/Plugins/Samples/Common/DicomPath.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/IOrthancConnection.cpp
--- a/UnitTestsSources/TestMessageBroker.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/UnitTestsSources/TestMessageBroker.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -1,158 +1,158 @@
-/**
- * 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/>.
- **/
+///**
+// * 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 "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"
+//#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 test1Counter = 0;
-static int test2Counter = 0;
-class MyFullObserver : public OrthancStone::IObserver
-{
+//static int test1Counter = 0;
+//static int test2Counter = 0;
+//class MyFullObserver : public OrthancStone::IObserver
+//{
 
-public:
-  MyFullObserver(OrthancStone::MessageBroker& broker)
-    : OrthancStone::IObserver(broker)
-  {
-    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-    DeclareIgnoredMessage(OrthancStone::MessageType_Test2);
-  }
+//public:
+//  MyFullObserver(OrthancStone::MessageBroker& broker)
+//    : OrthancStone::IObserver(broker)
+//  {
+////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
+////    DeclareIgnoredMessage(OrthancStone::MessageType_Test2);
+//  }
 
 
-  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-    switch (message.GetType())
-    {
-    case OrthancStone::MessageType_Test1:
-      test1Counter++;
-      break;
-    case OrthancStone::MessageType_Test2:
-      test2Counter++;
-      break;
-    default:
-      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-    }
-  }
+//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+//    switch (message.GetType())
+//    {
+//    case OrthancStone::MessageType_Test1:
+//      test1Counter++;
+//      break;
+//    case OrthancStone::MessageType_Test2:
+//      test2Counter++;
+//      break;
+//    default:
+//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
+//    }
+//  }
 
-};
+//};
 
-class MyPartialObserver : public OrthancStone::IObserver
-{
+//class MyPartialObserver : public OrthancStone::IObserver
+//{
 
-public:
-  MyPartialObserver(OrthancStone::MessageBroker& broker)
-    : OrthancStone::IObserver(broker)
-  {
-    DeclareHandledMessage(OrthancStone::MessageType_Test1);
-    // don't declare Test2 on purpose
-  }
+//public:
+//  MyPartialObserver(OrthancStone::MessageBroker& broker)
+//    : OrthancStone::IObserver(broker)
+//  {
+////    DeclareHandledMessage(OrthancStone::MessageType_Test1);
+//    // don't declare Test2 on purpose
+//  }
 
 
-  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-    switch (message.GetType())
-    {
-    case OrthancStone::MessageType_Test1:
-      test1Counter++;
-      break;
-    case OrthancStone::MessageType_Test2:
-      test2Counter++;
-      break;
-    default:
-      throw OrthancStone::MessageNotDeclaredException(message.GetType());
-    }
-  }
+//  void HandleMessage(OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
+//    switch (message.GetType())
+//    {
+//    case OrthancStone::MessageType_Test1:
+//      test1Counter++;
+//      break;
+//    case OrthancStone::MessageType_Test2:
+//      test2Counter++;
+//      break;
+//    default:
+//      throw OrthancStone::MessageNotDeclaredException(message.GetType());
+//    }
+//  }
 
-};
+//};
 
 
-class MyObservable : public OrthancStone::IObservable
-{
+//class MyObservable : public OrthancStone::IObservable
+//{
 
-public:
-  MyObservable(OrthancStone::MessageBroker& broker)
-    : OrthancStone::IObservable(broker)
-  {
-    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
-    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
-  }
+//public:
+//  MyObservable(OrthancStone::MessageBroker& broker)
+//    : OrthancStone::IObservable(broker)
+//  {
+//    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
+//    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
+//  }
 
-};
+//};
 
 
-TEST(MessageBroker, NormalUsage)
-{
-  OrthancStone::MessageBroker broker;
-  MyObservable observable(broker);
+//TEST(MessageBroker, NormalUsage)
+//{
+//  OrthancStone::MessageBroker broker;
+//  MyObservable observable(broker);
 
-  test1Counter = 0;
+//  test1Counter = 0;
 
-  // no observers have been registered -> nothing shall happen
-  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+//  // no observers have been registered -> nothing shall happen
+//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(0, test1Counter);
+//  ASSERT_EQ(0, test1Counter);
 
-  // register an observer, check it is called
-  MyFullObserver fullObserver(broker);
-  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
+//  // register an observer, check it is called
+//  MyFullObserver fullObserver(broker);
+//  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
 
-  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(1, test1Counter);
+//  ASSERT_EQ(1, test1Counter);
 
-  // register an invalid observer, check it raises an exception
-  MyPartialObserver partialObserver(broker);
-  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
+//  // register an invalid observer, check it raises an exception
+//  MyPartialObserver partialObserver(broker);
+//  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
 
-  // check an exception is thrown when the observable emits an undeclared message
-  ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException);
+//  // check an exception is thrown when the observable emits an undeclared message
+//  ASSERT_THROW(observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_LayerSource_GeometryReady)), OrthancStone::MessageNotDeclaredException);
 
-  // unregister the observer, make sure nothing happens afterwards
-  observable.UnregisterObserver(fullObserver);
-  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
-  ASSERT_EQ(1, test1Counter);
-}
+//  // unregister the observer, make sure nothing happens afterwards
+//  observable.UnregisterObserver(fullObserver);
+//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+//  ASSERT_EQ(1, test1Counter);
+//}
 
-TEST(MessageBroker, DeleteObserverWhileRegistered)
-{
-  OrthancStone::MessageBroker broker;
-  MyObservable observable(broker);
+//TEST(MessageBroker, DeleteObserverWhileRegistered)
+//{
+//  OrthancStone::MessageBroker broker;
+//  MyObservable observable(broker);
 
-  test1Counter = 0;
+//  test1Counter = 0;
 
-  {
-    // register an observer, check it is called
-    MyFullObserver observer(broker);
-    observable.RegisterObserver(observer);
+//  {
+//    // register an observer, check it is called
+//    MyFullObserver observer(broker);
+//    observable.RegisterObserver(observer);
 
-    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+//    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-    ASSERT_EQ(1, test1Counter);
-  }
+//    ASSERT_EQ(1, test1Counter);
+//  }
 
-  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
-  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+//  // at this point, the observer has been deleted, the handle shall not be called again (and it shall not crash !)
+//  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(1, test1Counter);
-}
+//  ASSERT_EQ(1, test1Counter);
+//}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker2.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -0,0 +1,691 @@
+/**
+ * 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 <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;
+
+  enum MessageType
+  {
+    MessageType_Test1,
+    MessageType_Test2,
+
+    MessageType_CustomMessage,
+    MessageType_LastGenericStoneMessage
+  };
+
+  struct IMessage  : public boost::noncopyable
+  {
+    MessageType messageType_;
+  public:
+    IMessage(const MessageType& messageType)
+      : messageType_(messageType)
+    {}
+    virtual ~IMessage() {}
+
+    virtual int GetType() const {return messageType_;}
+  };
+
+
+  struct ICustomMessage  : public IMessage
+  {
+    int customMessageType_;
+  public:
+    ICustomMessage(int customMessageType)
+      : IMessage(MessageType_CustomMessage),
+        customMessageType_(customMessageType)
+    {}
+    virtual ~ICustomMessage() {}
+
+    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()
+    {
+    }
+
+    virtual void Apply(const IMessage& message) = 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&);
+
+    TObserver&      observer_;
+    MemberFunction  function_;
+
+  public:
+    Callable(TObserver& observer,
+             MemberFunction function) :
+      observer_(observer),
+      function_(function)
+    {
+    }
+
+    void ApplyInternal(const TMessage& message)
+    {
+      (observer_.*function_) (message);
+    }
+
+    virtual void Apply(const IMessage& message)
+    {
+      ApplyInternal(dynamic_cast<const TMessage&>(message));
+    }
+
+    virtual MessageType GetMessageType() const
+    {
+      return static_cast<MessageType>(TMessage::Type);
+    }
+
+    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
+  {
+
+    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
+
+  public:
+
+    void Register(IObserver& observer)
+    {
+      activeObservers_.insert(&observer);
+    }
+
+    void Unregister(IObserver& observer)
+    {
+      activeObservers_.erase(&observer);
+    }
+
+    bool IsActive(IObserver* observer)
+    {
+      return activeObservers_.find(observer) != activeObservers_.end();
+    }
+  };
+
+
+  class Promise : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+    ICallable* successCallable_;
+    ICallable* failureCallable_;
+
+  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 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& 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_;
+
+  public:
+    IObserver(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IObserver()
+    {
+      broker_.Unregister(*this);
+    }
+
+  };
+
+
+  class IObservable : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                     broker_;
+
+    typedef std::map<int, std::set<ICallable*> >   Callables;
+    Callables  callables_;
+  public:
+
+    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;
+        }
+      }
+    }
+
+    void Register(ICallable* callable)
+    {
+      MessageType messageType = callable->GetMessageType();
+
+      callables_[messageType].insert(callable);
+    }
+
+    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);
+          }
+        }
+      }
+    }
+
+  };
+
+
+  enum CustomMessageType
+  {
+    CustomMessageType_First = MessageType_LastGenericStoneMessage + 1,
+
+    CustomMessageType_Completed,
+    CustomMessageType_Increment
+  };
+
+  class MyObservable : public IObservable
+  {
+  public:
+    struct MyCustomMessage: public ICustomMessage
+    {
+      int payload_;
+      enum
+      {
+        Type = CustomMessageType_Completed
+      };
+
+      MyCustomMessage(int payload)
+        : ICustomMessage(Type),
+          payload_(payload)
+      {}
+    };
+
+    MyObservable(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+
+  };
+
+  class MyObserver : public IObserver
+  {
+  public:
+    MyObserver(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void HandleCompletedMessage(const MyObservable::MyCustomMessage& message)
+    {
+      testCounter += message.payload_;
+    }
+
+  };
+
+
+  class MyPromiseSource : public IObservable
+  {
+    Promise* currentPromise_;
+  public:
+    struct MyPromiseMessage: public ICustomMessage
+    {
+      int increment;
+      enum
+      {
+        Type = CustomMessageType_Increment
+      };
+
+      MyPromiseMessage(int increment)
+        : ICustomMessage(Type),
+          increment(increment)
+      {}
+    };
+
+    MyPromiseSource(MessageBroker& broker)
+      : IObservable(broker),
+        currentPromise_(NULL)
+    {}
+
+    Promise& StartSomethingAsync()
+    {
+      currentPromise_ = new Promise(broker_);
+      return *currentPromise_;
+    }
+
+    void CompleteSomethingAsyncWithSuccess(int payload)
+    {
+      currentPromise_->Success(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
+
+    void CompleteSomethingAsyncWithFailure(int payload)
+    {
+      currentPromise_->Failure(MyPromiseMessage(payload));
+      delete currentPromise_;
+    }
+  };
+
+
+  class MyPromiseTarget : public IObserver
+  {
+  public:
+    MyPromiseTarget(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+
+    void IncrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter += args.increment;
+    }
+
+    void DecrementCounter(const MyPromiseSource::MyPromiseMessage& args)
+    {
+      testCounter -= args.increment;
+    }
+  };
+}
+
+
+TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver    observer(broker);
+
+  // create a permanent connection between an observable and an observer
+  observable.Register(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, TestPermanentConnectionDeleteObserver)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  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));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
+
+  // 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(0, testCounter);
+}
+
+
+TEST(MessageBroker2, TestPromiseSuccessFailure)
+{
+  MessageBroker broker;
+  MyPromiseSource  source(broker);
+  MyPromiseTarget target(broker);
+
+  // test a successful promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(10, testCounter);
+
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  ASSERT_EQ(-15, testCounter);
+}
+
+TEST(MessageBroker2, TestPromiseDeleteTarget)
+{
+  MessageBroker broker;
+  MyPromiseSource source(broker);
+  MyPromiseTarget* target = new MyPromiseTarget(broker);
+
+  // create the promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
+
+  // delete the promise target
+  delete target;
+
+  // trigger the promise, make sure it does not throw and does not call the callback
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess(10);
+  ASSERT_EQ(0, testCounter);
+
+  // test a failing promise
+  source.StartSomethingAsync()
+      .Then(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::IncrementCounter))
+      .Else(new Callable<MyPromiseTarget, MyPromiseSource::MyPromiseMessage>(*target, &MyPromiseTarget::DecrementCounter));
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure(15);
+  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);
+//}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker2_connect_ok.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -0,0 +1,226 @@
+/**
+ * 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 <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+#include <string>
+#include <map>
+#include <set>
+
+int testCounter = 0;
+namespace {
+
+  enum MessageType
+  {
+    // used in unit tests only
+    MessageType_Test1,
+    MessageType_Test2,
+
+    MessageType_LastGenericStoneMessage
+  };
+
+  struct IMessage  : public boost::noncopyable
+  {
+    MessageType messageType_;
+  public:
+    IMessage(const MessageType& messageType)
+      : messageType_(messageType)
+    {}
+    virtual ~IMessage() {}
+
+    MessageType GetType() const {return messageType_;}
+  };
+
+
+  class IObserver;
+  class IObservable;
+
+  /*
+   * 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 dead observer.
+   */
+  class MessageBroker : public boost::noncopyable
+  {
+
+    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
+
+  public:
+
+    void Register(IObserver& observer)
+    {
+      activeObservers_.insert(&observer);
+    }
+
+    void Unregister(IObserver& observer)
+    {
+      activeObservers_.erase(&observer);
+    }
+
+    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
+  };
+
+
+  class IObserver : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+  public:
+    IObserver(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IObserver()
+    {
+      broker_.Unregister(*this);
+    }
+
+    void HandleMessage_(IObservable &from, const IMessage &message)
+    {
+
+      HandleMessage(from, message);
+    }
+
+    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
+
+
+  protected:
+
+
+  };
+
+//  struct ICallableObserver
+//  {
+//    IObserver* observer;
+//  };
+
+//  typedef void (IObserver::*ObserverSingleMesssageHandler)(IObservable& from, const IMessage& message);
+
+//  template <typename TObserver>
+//  struct CallableObserver : public ICallableObserver
+//  {
+//    void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message);
+//  };
+
+  struct CallableObserver
+  {
+    IObserver* observer;
+    boost::function<void (IObservable& from, const IMessage& message)> f;
+  };
+
+  class IObservable : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                     broker_;
+
+    std::set<IObserver*>              observers_;
+
+    std::map<MessageType, std::set<CallableObserver*> > callables_;
+  public:
+
+    IObservable(MessageBroker& broker)
+      : broker_(broker)
+    {
+    }
+    virtual ~IObservable()
+    {
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      //broker_.EmitMessage(*this, observers_, message);
+
+      // TODO check if observer is still alive and call !
+      CallableObserver* callable = *(callables_[message.GetType()].begin());
+      callable->f(*this, message);
+    }
+
+    void RegisterObserver(IObserver& observer)
+    {
+      observers_.insert(&observer);
+    }
+
+    void UnregisterObserver(IObserver& observer)
+    {
+      observers_.erase(&observer);
+    }
+
+
+    //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message))
+    void Connect(MessageType messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f)
+    {
+      callables_[messageType] = std::set<CallableObserver*>();
+      CallableObserver* callable = new CallableObserver();
+      callable->observer = &observer;
+      callable->f = f;
+      callables_[messageType].insert(callable);
+    }
+  };
+
+
+  class MyObservable : public IObservable
+  {
+  public:
+    MyObservable(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+  };
+
+  class MyObserver : public IObserver
+  {
+  public:
+    MyObserver(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+    virtual void HandleMessage(IObservable& from, const IMessage& message) {}
+    void HandleSpecificMessage(IObservable& from, const IMessage& message)
+    {
+      testCounter++;
+    }
+
+  };
+
+}
+
+//#define STONE_CONNECT(observabe, messageType, observerPtr, observerMemberFnPtr)
+
+TEST(MessageBroker2, Test1)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver    observer(broker);
+
+
+  observable.Connect(MessageType_Test1, observer, boost::bind(&MyObserver::HandleSpecificMessage, &observer, _1, _2));
+  //STONE_CONNECT(observable, MessageType_Test1, observer, &MyObserver::HandleSpecificMessage)
+  observable.EmitMessage(IMessage(MessageType_Test1));
+
+  ASSERT_EQ(1, testCounter);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker2_promise_and_connect_ok.cpp	Fri Sep 14 16:44:01 2018 +0200
@@ -0,0 +1,520 @@
+/**
+ * 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 <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+#include <string>
+#include <map>
+#include <set>
+
+int testCounter = 0;
+namespace {
+
+  enum MessageType
+  {
+    MessageType_Test1,
+    MessageType_Test2,
+
+    MessageType_CustomMessage,
+    MessageType_LastGenericStoneMessage
+  };
+
+  struct IMessage  : public boost::noncopyable
+  {
+    MessageType messageType_;
+  public:
+    IMessage(const MessageType& messageType)
+      : messageType_(messageType)
+    {}
+    virtual ~IMessage() {}
+
+    virtual int GetType() const {return messageType_;}
+  };
+
+
+  struct ICustomMessage  : public IMessage
+  {
+    int customMessageType_;
+  public:
+    ICustomMessage(int customMessageType)
+      : IMessage(MessageType_CustomMessage),
+        customMessageType_(customMessageType)
+    {}
+    virtual ~ICustomMessage() {}
+
+    virtual int GetType() const {return customMessageType_;}
+  };
+
+
+  class IObserver;
+  class IObservable;
+  class IPromiseTarget;
+  class IPromiseSource;
+  class Promise;
+
+  /*
+   * 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.
+   * It does the same book-keeping for the IPromiseTarget and IPromiseSource
+   */
+  class MessageBroker : public boost::noncopyable
+  {
+
+    std::set<IObserver*> activeObservers_;  // the list of observers that are currently alive (that have not been deleted)
+    std::set<IPromiseTarget*> activePromiseTargets_;
+    std::set<IPromiseSource*> activePromiseSources_;
+
+  public:
+
+    void Register(IObserver& observer)
+    {
+      activeObservers_.insert(&observer);
+    }
+
+    void Unregister(IObserver& observer)
+    {
+      activeObservers_.erase(&observer);
+    }
+
+    void Register(IPromiseTarget& target)
+    {
+      activePromiseTargets_.insert(&target);
+    }
+
+    void Unregister(IPromiseTarget& target)
+    {
+      activePromiseTargets_.erase(&target);
+    }
+
+    void Register(IPromiseSource& source)
+    {
+      activePromiseSources_.insert(&source);
+    }
+
+    void Unregister(IPromiseSource& source)
+    {
+      activePromiseSources_.erase(&source);
+    }
+
+    void EmitMessage(IObservable& from, std::set<IObserver*> observers, const IMessage& message);
+
+    bool IsActive(IPromiseTarget* target)
+    {
+      return activePromiseTargets_.find(target) != activePromiseTargets_.end();
+    }
+
+    bool IsActive(IPromiseSource* source)
+    {
+      return activePromiseSources_.find(source) != activePromiseSources_.end();
+    }
+
+    bool IsActive(IObserver* observer)
+    {
+      return activeObservers_.find(observer) != activeObservers_.end();
+    }
+  };
+
+  struct IPromiseArgs
+  {
+public:
+    virtual ~IPromiseArgs() {}
+  };
+
+  class EmptyPromiseArguments : public IPromiseArgs
+  {
+
+  };
+
+  class Promise : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+    IPromiseTarget*                                           successTarget_;
+    boost::function<void (const IPromiseArgs& message)>       successCallable_;
+
+    IPromiseTarget*                                           failureTarget_;
+    boost::function<void (const IPromiseArgs& message)>       failureCallable_;
+
+  public:
+    Promise(MessageBroker& broker)
+      : broker_(broker),
+        successTarget_(NULL),
+        failureTarget_(NULL)
+    {
+    }
+
+    void Success(const IPromiseArgs& message)
+    {
+      // check the target is still alive in the broker
+      if (broker_.IsActive(successTarget_))
+      {
+        successCallable_(message);
+      }
+    }
+
+    void Failure(const IPromiseArgs& message)
+    {
+      // check the target is still alive in the broker
+      if (broker_.IsActive(failureTarget_))
+      {
+        failureCallable_(message);
+      }
+    }
+
+    Promise& Then(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f)
+    {
+      if (successTarget_ != NULL)
+      {
+        // TODO: throw throw new "Promise may only have a single success target"
+      }
+      successTarget_ = target;
+      successCallable_ = f;
+      return *this;
+    }
+
+    Promise& Else(IPromiseTarget* target, boost::function<void (const IPromiseArgs& message)> f)
+    {
+      if (failureTarget_ != NULL)
+      {
+        // TODO: throw throw new "Promise may only have a single failure target"
+      }
+      failureTarget_ = target;
+      failureCallable_ = f;
+      return *this;
+    }
+
+  };
+
+  class IObserver : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+  public:
+    IObserver(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IObserver()
+    {
+      broker_.Unregister(*this);
+    }
+
+  };
+
+  class IPromiseTarget : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+  public:
+    IPromiseTarget(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IPromiseTarget()
+    {
+      broker_.Unregister(*this);
+    }
+  };
+
+  class IPromiseSource : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+  public:
+    IPromiseSource(MessageBroker& broker)
+      : broker_(broker)
+    {
+      broker_.Register(*this);
+    }
+
+    virtual ~IPromiseSource()
+    {
+      broker_.Unregister(*this);
+    }
+  };
+
+
+  struct CallableObserver
+  {
+    IObserver* observer;
+    boost::function<void (IObservable& from, const IMessage& message)> f;
+  };
+
+  class IObservable : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                     broker_;
+
+    std::set<IObserver*>              observers_;
+
+    std::map<int, std::set<CallableObserver*> > callables_;
+  public:
+
+    IObservable(MessageBroker& broker)
+      : broker_(broker)
+    {
+    }
+    virtual ~IObservable()
+    {
+    }
+
+    void EmitMessage(const IMessage& message)
+    {
+      //broker_.EmitMessage(*this, observers_, message);
+      int messageType = message.GetType();
+      if (callables_.find(messageType) != callables_.end())
+      {
+        for (std::set<CallableObserver*>::iterator observer = callables_[messageType].begin(); observer != callables_[messageType].end(); observer++)
+        {
+          CallableObserver* callable = *observer;
+          if (broker_.IsActive(callable->observer))
+          {
+            callable->f(*this, message);
+          }
+        }
+      }
+
+    }
+
+    void RegisterObserver(IObserver& observer)
+    {
+      observers_.insert(&observer);
+    }
+
+    void UnregisterObserver(IObserver& observer)
+    {
+      observers_.erase(&observer);
+    }
+
+    //template<typename TObserver> void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message))
+    void Connect(int messageType, IObserver& observer, boost::function<void (IObservable& from, const IMessage& message)> f)
+    {
+      callables_[messageType] = std::set<CallableObserver*>();
+      CallableObserver* callable = new CallableObserver();
+      callable->observer = &observer;
+      callable->f = f;
+      callables_[messageType].insert(callable);
+    }
+  };
+
+
+  enum CustomMessageType
+  {
+    CustomMessageType_First = MessageType_LastGenericStoneMessage + 1,
+
+    CustomMessageType_Completed
+  };
+
+  class MyObservable : public IObservable
+  {
+  public:
+    struct MyCustomMessage: public ICustomMessage
+    {
+      int payload_;
+      MyCustomMessage(int payload)
+        : ICustomMessage(CustomMessageType_Completed),
+          payload_(payload)
+      {}
+    };
+
+    MyObservable(MessageBroker& broker)
+      : IObservable(broker)
+    {}
+
+  };
+
+  class MyObserver : public IObserver
+  {
+  public:
+    MyObserver(MessageBroker& broker)
+      : IObserver(broker)
+    {}
+    void HandleCompletedMessage(IObservable& from, const IMessage& message)
+    {
+      const MyObservable::MyCustomMessage& msg = dynamic_cast<const MyObservable::MyCustomMessage&>(message);
+      testCounter += msg.payload_;
+    }
+
+  };
+
+
+  class MyPromiseSource : public IPromiseSource
+  {
+    Promise* currentPromise_;
+  public:
+    struct MyPromiseArgs : public IPromiseArgs
+    {
+      int increment;
+    };
+
+    MyPromiseSource(MessageBroker& broker)
+      : IPromiseSource(broker),
+        currentPromise_(NULL)
+    {}
+
+    Promise& StartSomethingAsync()
+    {
+      currentPromise_ = new Promise(broker_);
+      return *currentPromise_;
+    }
+
+    void CompleteSomethingAsyncWithSuccess()
+    {
+      currentPromise_->Success(EmptyPromiseArguments());
+      delete currentPromise_;
+    }
+
+    void CompleteSomethingAsyncWithFailure()
+    {
+      currentPromise_->Failure(EmptyPromiseArguments());
+      delete currentPromise_;
+    }
+  };
+
+
+  class MyPromiseTarget : public IPromiseTarget
+  {
+  public:
+    MyPromiseTarget(MessageBroker& broker)
+      : IPromiseTarget(broker)
+    {}
+
+    void IncrementCounter(const IPromiseArgs& args)
+    {
+      testCounter++;
+    }
+
+    void DecrementCounter(const IPromiseArgs& args)
+    {
+      testCounter--;
+    }
+  };
+}
+
+#define CONNECT_MESSAGES(observablePtr, messageType, observerPtr, observerFnPtr) (observablePtr)->Connect(messageType, *(observerPtr), boost::bind(observerFnPtr, observerPtr, _1, _2))
+#define PTHEN(targetPtr, targetFnPtr) Then(targetPtr, boost::bind(targetFnPtr, targetPtr, _1))
+#define PELSE(targetPtr, targetFnPtr) Else(targetPtr, boost::bind(targetFnPtr, targetPtr, _1))
+
+
+TEST(MessageBroker2, TestPermanentConnectionSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver    observer(broker);
+
+  // create a permanent connection between an observable and an observer
+  CONNECT_MESSAGES(&observable, CustomMessageType_Completed, &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, TestPermanentConnectionDeleteObserver)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
+
+  // create a permanent connection between an observable and an observer
+  CONNECT_MESSAGES(&observable, CustomMessageType_Completed, observer, &MyObserver::HandleCompletedMessage);
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // delete the observer and check that the callback is not called anymore
+  delete observer;
+
+  // 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(0, testCounter);
+}
+
+
+TEST(MessageBroker2, TestPromiseSuccessFailure)
+{
+  MessageBroker broker;
+  MyPromiseSource  source(broker);
+  MyPromiseTarget target(broker);
+
+  // test a successful promise
+  source.StartSomethingAsync()
+      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
+      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithSuccess();
+  ASSERT_EQ(1, testCounter);
+
+  // test a failing promise
+  source.StartSomethingAsync()
+      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
+      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
+
+  testCounter = 0;
+  source.CompleteSomethingAsyncWithFailure();
+  ASSERT_EQ(-1, testCounter);
+}
+
+//TEST(MessageBroker2, TestPromiseDeleteTarget)
+//{
+//  MessageBroker broker;
+//  MyPromiseSource  source(broker);
+//  MyPromiseTarget target(broker);
+
+//  // test a successful promise
+//  source.StartSomethingAsync()
+//      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
+//      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
+
+//  testCounter = 0;
+//  source.CompleteSomethingAsyncWithSuccess();
+//  ASSERT_EQ(1, testCounter);
+
+//  // test a failing promise
+//  source.StartSomethingAsync()
+//      .PTHEN(&target, &MyPromiseTarget::IncrementCounter)
+//      .PELSE(&target, &MyPromiseTarget::DecrementCounter);
+
+//  testCounter = 0;
+//  source.CompleteSomethingAsyncWithFailure();
+//  ASSERT_EQ(-1, testCounter);
+//}