changeset 312:a91ad36b684c am-2

Merged am-callable-and-promise into am-2
author Alain Mazy <am@osimis.io>
date Tue, 02 Oct 2018 10:15:36 +0200
parents f58bfb7bbcc9 (current diff) 3b29c9c77d9b (diff)
children 8bdc6112bc2e
files Applications/Commands/BaseCommandFactory.cpp Applications/Commands/BaseCommandFactory.h Applications/Commands/ICommandFactory.h Framework/Messages/MessageBroker.cpp Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h
diffstat 67 files changed, 2923 insertions(+), 1498 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Commands/BaseCommandBuilder.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,63 @@
+/**
+ * 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 "BaseCommandBuilder.h"
+#include "Core/OrthancException.h"
+#include <iostream>
+#include "Framework/StoneException.h"
+
+namespace OrthancStone
+{
+  ICommand* BaseCommandBuilder::CreateFromJson(const Json::Value& commandJson)
+  {
+    if (!commandJson.isObject() || !commandJson["command"].isString())
+    {
+      throw StoneException(ErrorCode_CommandJsonInvalidFormat);
+    }
+
+    if (commandJson["commandType"].isString() && commandJson["commandType"].asString() == "simple")
+    {
+        printf("creating a simple command\n");
+        return new SimpleCommand(commandJson["command"].asString().c_str());
+    }
+
+    return NULL;
+    // std::string commandName = commandJson["command"].asString();
+
+
+
+
+    // CommandCreationFunctions::const_iterator it = commands_.find(commandName);
+    // if (it == commands_.end())
+    // {
+    //   throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);  // TODO: use StoneException ?
+    // }
+
+    // // call the CreateCommandFn to build the command
+    // ICommand* command = it->second();
+    // if (commandJson["args"].isObject())
+    // {
+    //   command->Configure(commandJson["args"]);
+    // }
+
+    // return command;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Commands/BaseCommandBuilder.h	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,38 @@
+/**
+ * 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 <map>
+#include <memory>
+
+#include "ICommand.h"
+#include "../../Applications/Commands/ICommandBuilder.h"
+
+// TODO: must be reworked completely (check trello)
+
+namespace OrthancStone
+{
+  class BaseCommandBuilder : public ICommandBuilder
+  {
+  public:
+    virtual ICommand* CreateFromJson(const Json::Value& commandJson);
+  };
+}
--- a/Applications/Commands/BaseCommandFactory.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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 "BaseCommandFactory.h"
-#include "Core/OrthancException.h"
-
-namespace OrthancStone
-{
-  ICommand* BaseCommandFactory::CreateFromJson(const Json::Value& commandJson)
-  {
-    if (!commandJson.isObject() || !commandJson["command"].isString())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);  // TODO: use StoneException ?
-    }
-
-    std::string commandName = commandJson["command"].asString();
-    CommandCreationFunctions::const_iterator it = commands_.find(commandName);
-    if (it == commands_.end())
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);  // TODO: use StoneException ?
-    }
-
-    // call the CreateCommandFn to build the command
-    ICommand* command = it->second();
-    if (commandJson["args"].isObject())
-    {
-      command->Configure(commandJson["args"]);
-    }
-
-    return command;
-  }
-
-}
--- a/Applications/Commands/BaseCommandFactory.h	Mon Sep 10 12:22:26 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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/>.
- **/
-
-#pragma once
-
-#include <map>
-#include <memory>
-
-#include "ICommand.h"
-#include "../../Applications/Commands/ICommandFactory.h"
-
-// TODO: must be reworked completely (check trello)
-
-namespace OrthancStone
-{
-  class BaseCommandFactory
-  {
-    typedef ICommand* (*CommandCreationFn)(void);
-    typedef std::map<std::string, CommandCreationFn> CommandCreationFunctions;
-    CommandCreationFunctions commands_;
-
-  public:
-    virtual ICommand* CreateFromJson(const Json::Value& commandJson);
-
-    template<typename TCommand> void RegisterCommandClass()
-    {
-      // create the command only to get its name
-      std::auto_ptr<ICommand> command(TCommand::Create());
-
-      commands_[command->GetName()] = &TCommand::Create;
-    }
-  };
-}
--- a/Applications/Commands/ICommand.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Commands/ICommand.h	Tue Oct 02 10:15:36 2018 +0200
@@ -30,14 +30,14 @@
   class ICommand  // TODO noncopyable
   {
   protected:
-    const char* name_;
-    ICommand(const char* name)
+    std::string name_;
+    ICommand(const std::string& name)
       : name_(name)
     {}
   public:
     virtual void Execute() = 0;
-    virtual void Configure(const Json::Value& arguments) = 0;
-    const char* GetName()
+//    virtual void Configure(const Json::Value& arguments) = 0;
+    const std::string& GetName() const
     {
       return name_;
     }
@@ -45,10 +45,10 @@
 
 
   template <typename TCommand>
-  class BaseCommand : ICommand
+  class BaseCommand : public ICommand
   {
   protected:
-    BaseCommand(const char* name)
+    BaseCommand(const std::string& name)
       : ICommand(name)
     {}
 
@@ -70,5 +70,13 @@
     virtual void Execute() {}
   };
 
+  class SimpleCommand : public BaseCommand<SimpleCommand>
+  {
+  public:
+    SimpleCommand(const std::string& name)
+      : BaseCommand(name)
+    {}
+    virtual void Execute() {} // TODO currently not used but this is not nice at all !
+  };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Commands/ICommandBuilder.h	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,37 @@
+/**
+ * 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 <boost/noncopyable.hpp>
+#include <json/json.h>
+
+#include "ICommand.h"
+
+namespace OrthancStone
+{
+
+  class ICommandBuilder : public boost::noncopyable
+  {
+  public:
+    virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0;
+  };
+}
--- a/Applications/Commands/ICommandFactory.h	Mon Sep 10 12:22:26 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +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/>.
- **/
-
-
-#pragma once
-
-#include <boost/noncopyable.hpp>
-#include <json/json.h>
-
-#include "ICommand.h"
-
-namespace OrthancStone
-{
-
-  class ICommandFactory : public boost::noncopyable
-  {
-  public:
-    virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0;
-    template<typename TCommand> void RegisterCommandClass();
-  };
-}
--- a/Applications/IStoneApplication.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/IStoneApplication.h	Tue Oct 02 10:15:36 2018 +0200
@@ -25,6 +25,8 @@
 #include <boost/program_options.hpp>
 #include "../Framework/Viewport/WidgetViewport.h"
 #include "json/json.h"
+#include "Commands/ICommand.h"
+#include "Commands/BaseCommandBuilder.h"
 
 namespace OrthancStone
 {
@@ -53,6 +55,12 @@
     virtual IWidget* GetCentralWidget() = 0;
 
     virtual void Finalize() = 0;
+
+    virtual BaseCommandBuilder& GetCommandBuilder() = 0;
+
+    virtual void ExecuteCommand(ICommand& command)
+    {
+    }
   };
 
 }
--- a/Applications/Samples/CMakeLists.txt	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/CMakeLists.txt	Tue Oct 02 10:15:36 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/SampleApplicationBase.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/SampleApplicationBase.h	Tue Oct 02 10:15:36 2018 +0200
@@ -29,6 +29,8 @@
   {
     class SampleApplicationBase : public IStoneApplication
     {
+    protected:
+      BaseCommandBuilder commandBuilder_;
     public:
       virtual void Initialize(StoneApplicationContext* context,
                               IStatusBar& statusBar,
@@ -57,6 +59,8 @@
         tool2 = "tool2";
       }
 
+      virtual BaseCommandBuilder& GetCommandBuilder() {return commandBuilder_;}
+
     };
   }
 }
--- a/Applications/Samples/SimpleViewerApplication.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Tue Oct 02 10:15:36 2018 +0200
@@ -32,7 +32,7 @@
 #include "../../Framework/SmartLoader.h"
 
 #if ORTHANC_ENABLE_WASM==1
-#include "../../Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h"
+#include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
 #include "../../Platforms/Wasm/Defaults.h"
 #endif
 #include <Core/Logging.h>
@@ -43,9 +43,6 @@
   {
     class SimpleViewerApplication :
         public SampleApplicationBase,
-#if ORTHANC_ENABLE_WASM==1
-        public IStoneApplicationToWebApplicationAdapter,
-#endif
         public IObserver
     {
     private:
@@ -172,6 +169,38 @@
         }
       };
 
+
+#if ORTHANC_ENABLE_WASM==1
+      class SimpleViewerApplicationAdapter : public WasmPlatformApplicationAdapter
+      {
+      public:
+        SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application)
+          : WasmPlatformApplicationAdapter(broker, application)
+        {
+
+        }
+
+        virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
+          if (input == "select-tool:line-measure")
+          {
+            application.currentTool_ = Tools_LineMeasure;
+            NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
+          }
+          else if (input == "select-tool:circle-measure")
+          {
+            application.currentTool_ = Tools_CircleMeasure;
+            NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
+          }
+
+          output = "ok";
+        }
+
+        virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
+          UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
+        }
+
+      };
+#endif
       enum Tools {
         Tools_LineMeasure,
         Tools_CircleMeasure
@@ -204,12 +233,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,14 +273,14 @@
           thumbnailsLayout_->SetVertical();
 
           mainWidget_ = new LayerWidget(broker_, "main-viewport");
-          mainWidget_->RegisterObserver(*this);
+          //mainWidget_->RegisterObserver(*this);
 
           // hierarchy
           mainLayout_->AddWidget(thumbnailsLayout_);
           mainLayout_->AddWidget(mainWidget_);
 
           // sources
-          smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
+          smartLoader_.reset(new SmartLoader(IObserver::broker_, context_->GetWebService()));
           smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
 
           mainLayout_->SetTransmitMouseOver(true);
@@ -268,12 +292,12 @@
         statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
         statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport");
 
-        orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService()));
+        orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
 
         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::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
         }
         else
         {
@@ -281,26 +305,32 @@
         }
       }
 
-      void OnStudyListReceived(const Json::Value& response)
+      void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& 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::JsonResponseReadyMessage& 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::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
           }
         }
       }
 
-      void OnSeriesReceived(const Json::Value& response)
+      void OnSeriesReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
       {
+        const Json::Value& response = message.Response;
+
         if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
         {
           // keep track of all instances IDs
@@ -327,37 +357,22 @@
       void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
       {
         LOG(INFO) << "Loading thumbnail for series " << seriesId;
-        LayerWidget* thumbnailWidget = new LayerWidget(broker_, "thumbnail-series-" + seriesId);
+        LayerWidget* thumbnailWidget = new LayerWidget(IObserver::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::JsonResponseReadyMessage>(*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)
@@ -385,25 +400,6 @@
       }
 
 #if ORTHANC_ENABLE_WASM==1
-      virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
-        if (input == "select-tool:line-measure")
-        {
-          currentTool_ = Tools_LineMeasure;
-          NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
-        }
-        else if (input == "select-tool:circle-measure")
-        {
-          currentTool_ = Tools_CircleMeasure;
-          NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
-        }
-
-        output = "ok";
-      }
-
-      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
-        UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
-      }
-
       virtual void InitializeWasm() {
 
         AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
--- a/Applications/Samples/Web/simple-viewer.ts	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/Web/simple-viewer.ts	Tue Oct 02 10:15:36 2018 +0200
@@ -3,11 +3,23 @@
 InitializeWasmApplication("OrthancStoneSimpleViewer", "/orthanc");
 
 function SelectTool(toolName: string) {
-    SendMessageToStoneApplication("select-tool:" + toolName);
+    var command = {
+        command: "selectTool",
+        args: {
+            toolName: toolName
+        }
+    };
+    SendMessageToStoneApplication(JSON.stringify(command));
+
 }
 
-function PerformAction(actionName: string) {
-    SendMessageToStoneApplication("perform-action:" + actionName);
+function PerformAction(commandName: string) {
+    var command = {
+        command: commandName,
+        commandType: "simple",
+        args: {}
+    };
+    SendMessageToStoneApplication(JSON.stringify(command));
 }
 
 //initializes the buttons
--- a/Applications/Samples/build-wasm.sh	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/build-wasm.sh	Tue Oct 02 10:15:36 2018 +0200
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+set -e
+
 currentDir=$(pwd)
 samplesRootDir=$(pwd)
 
--- a/Applications/Samples/build-web.sh	Mon Sep 10 12:22:26 2018 +0200
+++ b/Applications/Samples/build-web.sh	Tue Oct 02 10:15:36 2018 +0200
@@ -1,5 +1,7 @@
 #!/bin/bash
 
+set -e
+
 # this script currently assumes that the wasm code has been built on its side and is availabie in Wasm/build/
 
 currentDir=$(pwd)
--- a/Framework/Layers/ILayerSource.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Tue Oct 02 10:15:36 2018 +0200
@@ -31,62 +31,39 @@
   class ILayerSource : public IObservable
   {
   public:
-    struct SliceChangedMessage : public IMessage
+
+    typedef OriginMessage<MessageType_LayerSource_GeometryReady, ILayerSource> GeometryReadyMessage;
+    typedef OriginMessage<MessageType_LayerSource_GeometryError, ILayerSource> GeometryErrorMessage;
+    typedef OriginMessage<MessageType_LayerSource_ContentChanged, ILayerSource> ContentChangedMessage;
+
+    struct SliceChangedMessage : public OriginMessage<MessageType_LayerSource_SliceChanged, ILayerSource>
     {
       const Slice& slice_;
-      SliceChangedMessage(const Slice& slice)
-        : IMessage(MessageType_LayerSource_SliceChanged),
+      SliceChangedMessage(ILayerSource& origin, const Slice& slice)
+        : OriginMessage(origin),
           slice_(slice)
       {
       }
     };
 
-    struct LayerReadyMessage : public IMessage
+    struct LayerReadyMessage : public OriginMessage<MessageType_LayerSource_LayerReady,ILayerSource>
     {
-      std::auto_ptr<ILayerRenderer>& layer_;
+      std::auto_ptr<ILayerRenderer>& renderer_;
       const CoordinateSystem3D& slice_;
       bool isError_;
 
-      LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
+      LayerReadyMessage(ILayerSource& origin,
+                        std::auto_ptr<ILayerRenderer>& layer,
                         const CoordinateSystem3D& slice,
-                        bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
-        : IMessage(MessageType_LayerSource_LayerReady),
-          layer_(layer),
+                        bool isError  // TODO Shouldn't this be separate as NotifyLayerError?
+                        )
+        : OriginMessage(origin),
+          renderer_(layer),
           slice_(slice),
           isError_(isError)
       {
       }
     };
-
-    //    class IObserver : public boost::noncopyable
-    //    {
-    //    public:
-    //      virtual ~IObserver()
-    //      {
-    //      }
-
-    //      // Triggered as soon as the source has enough information to
-    //      // answer to "GetExtent()"
-    //      virtual void NotifyGeometryReady(const ILayerSource& source) = 0;
-
-    //      virtual void NotifyGeometryError(const ILayerSource& source) = 0;
-
-    //      // Triggered if the content of several slices in the source
-    //      // volume has changed
-    //      virtual void NotifyContentChange(const ILayerSource& source) = 0;
-
-    //      // Triggered if the content of some individual slice in the
-    //      // source volume has changed
-    //      virtual void NotifySliceChange(const ILayerSource& source,
-    //                                     const Slice& slice) = 0;
-
-    //      // The layer must be deleted by the observer that releases the
-    //      // std::auto_ptr
-    //      virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer,
-    //                                    const ILayerSource& source,
-    //                                    const CoordinateSystem3D& slice,
-    //                                    bool isError) = 0;  // TODO Shouldn't this be separate as NotifyLayerError?
-    //    };
     
     ILayerSource(MessageBroker& broker)
       : IObservable(broker)
@@ -96,8 +73,6 @@
     {
     }
 
-    //    virtual void Register(IObserver& observer) = 0;
-
     virtual bool GetExtent(std::vector<Vector>& points,
                            const CoordinateSystem3D& viewportSlice) = 0;
 
--- a/Framework/Layers/LayerSourceBase.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -27,22 +27,22 @@
 {
   void LayerSourceBase::NotifyGeometryReady()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_GeometryReady));
+    EmitMessage(ILayerSource::GeometryReadyMessage(*this));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_GeometryError));
+    EmitMessage(ILayerSource::GeometryErrorMessage(*this));
   }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    EmitMessage(IMessage(MessageType_LayerSource_ContentChanged));
+    EmitMessage(ILayerSource::ContentChangedMessage(*this));
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
   {
-    EmitMessage(ILayerSource::SliceChangedMessage(slice));
+    EmitMessage(ILayerSource::SliceChangedMessage(*this, slice));
   }
 
   void LayerSourceBase::NotifyLayerReady(ILayerRenderer* layer,
@@ -50,7 +50,7 @@
                                          bool isError)
   {
     std::auto_ptr<ILayerRenderer> renderer(layer);
-    EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError));
+    EmitMessage(ILayerSource::LayerReadyMessage(*this, renderer, slice, isError));
   }
 
 }
--- a/Framework/Layers/LayerSourceBase.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Tue Oct 02 10:15:36 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	Tue Oct 02 10:15:36 2018 +0200
@@ -31,59 +31,46 @@
 
 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();
+  }
 
-  OrthancFrameLayerSource::OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc) :
+  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, OrthancApiClient& orthanc) :
     LayerSourceBase(broker),
     IObserver(broker),
-    //OrthancSlicesLoader::ISliceLoaderObserver(broker),
     loader_(broker, orthanc),
     quality_(SliceImageQuality_FullPng)
   {
-    DeclareHandledMessage(MessageType_SliceLoader_GeometryReady);
-    DeclareHandledMessage(MessageType_SliceLoader_GeometryError);
-    DeclareHandledMessage(MessageType_SliceLoader_ImageReady);
-    DeclareHandledMessage(MessageType_SliceLoader_ImageError);
-    loader_.RegisterObserver(*this);
+    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	Tue Oct 02 10:15:36 2018 +0200
@@ -24,6 +24,7 @@
 #include "LayerSourceBase.h"
 #include "../Toolbox/IWebService.h"
 #include "../Toolbox/OrthancSlicesLoader.h"
+#include "../Toolbox/OrthancApiClient.h"
 
 namespace OrthancStone
 {  
@@ -40,7 +41,7 @@
     SliceImageQuality    quality_;
 
   public:
-    OrthancFrameLayerSource(MessageBroker& broker, IWebService& orthanc);
+    OrthancFrameLayerSource(MessageBroker& broker, OrthancApiClient& orthanc);
 
     void LoadSeries(const std::string& seriesId);
 
@@ -69,6 +70,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	Tue Oct 02 10:15:36 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	Tue Oct 02 10:15:36 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:
-    IMessage(const MessageType& messageType)
+    int messageType_;
+  protected:
+    IMessage(const int& messageType)
       : messageType_(messageType)
     {}
+  public:
     virtual ~IMessage() {}
 
-    MessageType GetType() const {return messageType_;}
+    virtual int GetType() const {return messageType_;}
+  };
+
+
+  // base class to derive from to implement your own messages
+  // it handles the message type for you
+  template <int type>
+  struct BaseMessage : public IMessage
+  {
+    enum
+    {
+      Type = type
+    };
+
+    BaseMessage()
+      : IMessage(static_cast<int>(Type))
+    {}
+  };
+
+  // simple message implementation when no payload is needed
+  // sample usage:
+  // typedef NoPayloadMessage<MessageType_LayerSource_GeometryReady> GeometryReadyMessage;
+  template <int 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 <int 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	Tue Oct 02 10:15:36 2018 +0200
@@ -25,31 +25,28 @@
 #include <assert.h>
 #include <algorithm>
 #include <iostream>
+#include <map>
+
 
 #include "MessageBroker.h"
 #include "MessageType.h"
+#include "ICallable.h"
 #include "IObserver.h"
+#include "MessageForwarder.h"
 
 namespace OrthancStone {
 
-  class MessageNotDeclaredException : public std::logic_error
-  {
-    MessageType messageType_;
-  public:
-    MessageNotDeclaredException(MessageType messageType)
-      : std::logic_error("Message not declared by observer."),
-        messageType_(messageType)
-    {
-    }
-  };
 
   class IObservable : public boost::noncopyable
   {
   protected:
     MessageBroker&                     broker_;
 
-    std::set<IObserver*>              observers_;
-    std::set<MessageType>             emittableMessages_;
+    typedef std::map<int, std::set<ICallable*> >   Callables;
+    Callables                         callables_;
+
+    typedef std::set<IMessageForwarder*>      Forwarders;
+    Forwarders                        forwarders_;
 
   public:
 
@@ -59,53 +56,54 @@
     }
     virtual ~IObservable()
     {
+      // delete all callables (this will also unregister them from the broker)
+      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;
+        }
+      }
+
+      // unregister the forwarders but don't delete them (they'll be deleted by the observable they are observing as any other callable)
+      for (Forwarders::iterator it = forwarders_.begin();
+           it != forwarders_.end(); ++it)
+      {
+        broker_.Unregister(dynamic_cast<IObserver&>(**it));
+      }
+    }
+
+    void RegisterObserverCallback(ICallable* callable)
+    {
+      MessageType messageType = callable->GetMessageType();
+
+      callables_[messageType].insert(callable);
     }
 
     void EmitMessage(const IMessage& message)
     {
-      if (emittableMessages_.find(message.GetType()) == emittableMessages_.end())
-      {
-        throw MessageNotDeclaredException(message.GetType());
-      }
-
-      broker_.EmitMessage(*this, observers_, message);
-    }
-
-    void RegisterObserver(IObserver& observer)
-    {
-      CheckObserverDeclaredAllObservableMessages(observer);
-      observers_.insert(&observer);
-    }
-
-    void UnregisterObserver(IObserver& observer)
-    {
-      observers_.erase(&observer);
-    }
+      Callables::const_iterator found = callables_.find(message.GetType());
 
-    const std::set<MessageType>& GetEmittableMessages() const
-    {
-      return emittableMessages_;
-    }
-
-  protected:
-
-    void DeclareEmittableMessage(MessageType messageType)
-    {
-      emittableMessages_.insert(messageType);
-    }
-
-    void CheckObserverDeclaredAllObservableMessages(IObserver& observer)
-    {
-      for (std::set<MessageType>::const_iterator it = emittableMessages_.begin(); it != emittableMessages_.end(); it++)
+      if (found != callables_.end())
       {
-        // the observer must have "declared" all observable messages
-        if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end()
-            && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end())
+        for (std::set<ICallable*>::const_iterator
+               it = found->second.begin(); it != found->second.end(); ++it)
         {
-          throw MessageNotDeclaredException(*it);
+          if (broker_.IsActive((*it)->GetObserver()))
+          {
+            (*it)->Apply(message);
+          }
         }
       }
     }
+
+    void RegisterForwarder(IMessageForwarder* forwarder)
+    {
+      forwarders_.insert(forwarder);
+    }
+
   };
 
 }
--- a/Framework/Messages/IObserver.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/IObserver.h	Tue Oct 02 10:15:36 2018 +0200
@@ -34,8 +34,6 @@
   {
   protected:
     MessageBroker&                    broker_;
-    std::set<MessageType>             handledMessages_;
-    std::set<MessageType>             ignoredMessages_;
 
   public:
     IObserver(MessageBroker& broker)
@@ -48,41 +46,6 @@
     {
       broker_.Unregister(*this);
     }
-
-    void HandleMessage_(IObservable &from, const IMessage &message)
-    {
-      assert(handledMessages_.find(message.GetType()) != handledMessages_.end()); // please declare the messages that you're handling
-
-      HandleMessage(from, message);
-    }
-
-    virtual void HandleMessage(IObservable& from, const IMessage& message) = 0;
-
-
-    const std::set<MessageType>& GetHandledMessages() const
-    {
-      return handledMessages_;
-    }
-
-    const std::set<MessageType>& GetIgnoredMessages() const
-    {
-      return ignoredMessages_;
-    }
-
-  protected:
-
-    // when you connect an IObserver to an IObservable, the observer must handle all observable messages (this is checked during the registration)
-    // so, all messages that may be emitted by the observable must be declared "handled" or "ignored" by the observer
-    void DeclareHandledMessage(MessageType messageType)
-    {
-      handledMessages_.insert(messageType);
-    }
-
-    void DeclareIgnoredMessage(MessageType messageType)
-    {
-      ignoredMessages_.insert(messageType);
-    }
-
   };
 
 }
--- 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	Tue Oct 02 10:15:36 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();
+    }
   };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageForwarder.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,38 @@
+/**
+ * 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 "MessageForwarder.h"
+
+#include "IObservable.h"
+
+namespace OrthancStone
+{
+
+  void IMessageForwarder::ForwardMessageInternal(const IMessage& message)
+  {
+    emitter_.EmitMessage(message);
+  }
+
+  void IMessageForwarder::RegisterForwarderInEmitter()
+  {
+    emitter_.RegisterForwarder(this);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/MessageForwarder.h	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,87 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ICallable.h"
+#include "IObserver.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace OrthancStone
+{
+
+  class IObservable;
+
+  class IMessageForwarder : public boost::noncopyable
+  {
+    IObservable& emitter_;
+  public:
+    IMessageForwarder(IObservable& emitter)
+      : emitter_(emitter)
+    {}
+    virtual ~IMessageForwarder() {}
+
+  protected:
+    void ForwardMessageInternal(const IMessage& message);
+    void RegisterForwarderInEmitter();
+
+  };
+
+  /* When an Observer (B) simply needs to re-emit a message it has received, instead of implementing
+   * a specific member function to forward the message, it can create a MessageForwarder.
+   * The MessageForwarder will re-emit the message "in the name of (B)"
+   *
+   * Consider the chain where
+   * A is an observable
+   * |
+   * B is an observer of A and observable
+   * |
+   * C is an observer of B and knows that B is re-emitting many messages from A
+   *
+   * instead of implementing a callback, B will create a MessageForwarder that will emit the messages in his name:
+   * A.RegisterObserverCallback(new MessageForwarder<A::MessageType>(broker, *this)  // where this is B
+   *
+   * in C:
+   * B.RegisterObserverCallback(new Callable<C, A:MessageTyper>(*this, &B::MyCallback))   // where this is C
+   */
+  template<typename TMessage>
+  class MessageForwarder : public IMessageForwarder, public IObserver, public Callable<MessageForwarder<TMessage>, TMessage>
+  {
+  public:
+    MessageForwarder(MessageBroker& broker,
+                     IObservable& emitter // the object that will emit the messages to forward
+                     )
+      : IMessageForwarder(emitter),
+        IObserver(broker),
+        Callable<MessageForwarder<TMessage>, TMessage>(*this, &MessageForwarder::ForwardMessage)
+    {
+      RegisterForwarderInEmitter();
+    }
+
+protected:
+    void ForwardMessage(const TMessage& message)
+    {
+      ForwardMessageInternal(message);
+    }
+
+  };
+}
--- a/Framework/Messages/MessageType.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Messages/MessageType.h	Tue Oct 02 10:15:36 2018 +0200
@@ -44,12 +44,17 @@
     MessageType_OrthancApi_InternalGetJsonResponseReady,
     MessageType_OrthancApi_InternalGetJsonResponseError,
 
+    MessageType_OrthancApi_GenericGetJson_Ready,
+    MessageType_OrthancApi_GenericGetBinary_Ready,
+    MessageType_OrthancApi_GenericHttpError_Ready,
     MessageType_OrthancApi_GetStudyIds_Ready,
     MessageType_OrthancApi_GetStudy_Ready,
     MessageType_OrthancApi_GetSeries_Ready,
 
     // used in unit tests only
     MessageType_Test1,
-    MessageType_Test2
+    MessageType_Test2,
+
+    MessageType_CustomMessage // Custom messages ids ust be greater than this (this one must remain in last position)
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Messages/Promise.h	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,88 @@
+/**
+ * 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>
+#include <memory>
+
+namespace OrthancStone {
+
+  class Promise : public boost::noncopyable
+  {
+  protected:
+    MessageBroker&                    broker_;
+
+    std::auto_ptr<ICallable> successCallable_;
+    std::auto_ptr<ICallable> failureCallable_;
+
+  public:
+    Promise(MessageBroker& broker)
+      : broker_(broker)
+    {
+    }
+
+    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_.get() != NULL)
+      {
+        // TODO: throw throw new "Promise may only have a single success target"
+      }
+      successCallable_.reset(successCallable);
+      return *this;
+    }
+
+    Promise& Else(ICallable* failureCallable)
+    {
+      if (failureCallable_.get() != NULL)
+      {
+        // TODO: throw throw new "Promise may only have a single failure target"
+      }
+      failureCallable_.reset(failureCallable);
+      return *this;
+    }
+
+  };
+
+
+}
--- a/Framework/SmartLoader.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/SmartLoader.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -21,6 +21,7 @@
 
 #include "SmartLoader.h"
 #include "Layers/OrthancFrameLayerSource.h"
+#include "Messages/MessageForwarder.h"
 
 namespace OrthancStone
 {
@@ -31,41 +32,6 @@
     webService_(webService),
     orthancApiClient_(broker, webService)
   {
-    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
-    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
-    DeclareIgnoredMessage(MessageType_LayerSource_GeometryError);
-    DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged);
-    DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged);
-
-//    DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-//    DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
-  }
-
-  void SmartLoader::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    switch (message.GetType()) {
-    case MessageType_LayerSource_GeometryReady:
-    {
-      //const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
-      // TODO keep track of objects that have been loaded already
-    }; break;
-    case MessageType_LayerSource_LayerReady:
-    {
-      //const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
-      // TODO keep track of objects that have been loaded already
-    }; break;
-//    case MessageType_OrthancApi_GetStudyIds_Ready:
-//    {
-
-//      const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast<OrthancApiClient::GetJsonResponseReadyMessage&>(message);
-
-//    }; break;
-    default:
-      VLOG("unhandled message type" << message.GetType());
-    }
-
-    // forward messages to its own observers
-    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
   }
 
   ILayerSource* SmartLoader::GetFrame(const std::string& instanceId, unsigned int frame)
@@ -76,9 +42,10 @@
     // - if currently loading, we need to return an object that will observe the existing LayerSource and forward
     //   the messages to its observables
     // in both cases, we must be carefull about objects lifecycle !!!
-    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_));
+    std::auto_ptr<OrthancFrameLayerSource> layerSource (new OrthancFrameLayerSource(IObserver::broker_, orthancApiClient_));
     layerSource->SetImageQuality(imageQuality_);
-    layerSource->RegisterObserver(*this);
+    layerSource->RegisterObserverCallback(new MessageForwarder<ILayerSource::GeometryReadyMessage>(IObserver::broker_, *this));
+    layerSource->RegisterObserverCallback(new MessageForwarder<ILayerSource::LayerReadyMessage>(IObserver::broker_, *this));
     layerSource->LoadFrame(instanceId, frame);
 
     return layerSource.release();
--- a/Framework/SmartLoader.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/SmartLoader.h	Tue Oct 02 10:15:36 2018 +0200
@@ -40,8 +40,6 @@
   public:
     SmartLoader(MessageBroker& broker, IWebService& webService);  // TODO: add maxPreloadStorageSizeInBytes
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
-
     void PreloadStudy(const std::string studyId);
     void PreloadSeries(const std::string seriesId);
     void LoadStudyList();
--- a/Framework/StoneException.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/StoneException.h	Tue Oct 02 10:15:36 2018 +0200
@@ -32,9 +32,11 @@
     ErrorCode_OrthancError, // this StoneException is actually an OrthancException with an Orthanc error code
     ErrorCode_ApplicationException, // this StoneException is specific to an application (and should have its own internal error code)
     ErrorCode_NotImplemented, // case not implemented
+
     ErrorCode_PromiseSingleSuccessHandler, // a Promise can only have a single success handler
     ErrorCode_PromiseSingleFailureHandler, // a Promise can only have a single failure handler
 
+    ErrorCode_CommandJsonInvalidFormat,
     ErrorCode_Last
   };
 
--- a/Framework/Toolbox/IWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Tue Oct 02 10:15:36 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,81 +42,37 @@
   public:
     typedef std::map<std::string, std::string> Headers;
 
-    class ICallback : public IObserver
+    struct HttpRequestSuccessMessage: public BaseMessage<MessageType_HttpRequestSuccess>
     {
-    public:
-      struct HttpRequestSuccessMessage: public IMessage
-      {
-        const std::string& Uri;
-        const void* Answer;
-        size_t AnswerSize;
-        Orthanc::IDynamicObject* Payload;
-        HttpRequestSuccessMessage(const std::string& uri,
-                                  const void* answer,
-                                  size_t answerSize,
-                                  Orthanc::IDynamicObject* payload)
-          : IMessage(MessageType_HttpRequestSuccess),
-            Uri(uri),
-            Answer(answer),
-            AnswerSize(answerSize),
-            Payload(payload)
-        {}
-      };
-
-      struct HttpRequestErrorMessage: public IMessage
-      {
-        const std::string& Uri;
-        Orthanc::IDynamicObject* Payload;
-        HttpRequestErrorMessage(const std::string& uri,
+      const std::string& uri_;
+      const void* answer_;
+      size_t answerSize_;
+      Orthanc::IDynamicObject* payload_;
+      HttpRequestSuccessMessage(const std::string& uri,
+                                const void* answer,
+                                size_t answerSize,
                                 Orthanc::IDynamicObject* payload)
-          : IMessage(MessageType_HttpRequestError),
-            Uri(uri),
-            Payload(payload)
-        {}
-      };
+        : BaseMessage(),
+          uri_(uri),
+          answer_(answer),
+          answerSize_(answerSize),
+          payload_(payload)
+      {}
+    };
 
-      ICallback(MessageBroker& broker)
-        : IObserver(broker)
-      {
-        DeclareHandledMessage(MessageType_HttpRequestError);
-        DeclareHandledMessage(MessageType_HttpRequestSuccess);
-      }
-      virtual ~ICallback()
-      {
-      }
-
-      virtual void HandleMessage(IObservable& from, const IMessage& message)
-      {
-        switch(message.GetType())
-        {
-        case MessageType_HttpRequestError:
-        {
-          const HttpRequestErrorMessage& msg = dynamic_cast<const HttpRequestErrorMessage&>(message);
-          OnHttpRequestError(msg.Uri,
-                             msg.Payload);
-        }; break;
+    struct HttpRequestErrorMessage: public BaseMessage<MessageType_HttpRequestError>
+    {
+      const std::string& uri_;
+      Orthanc::IDynamicObject* payload_;
+      HttpRequestErrorMessage(const std::string& uri,
+                              Orthanc::IDynamicObject* payload)
+        : BaseMessage(),
+          uri_(uri),
+          payload_(payload)
+      {}
+    };
 
-        case MessageType_HttpRequestSuccess:
-        {
-          const HttpRequestSuccessMessage& msg = dynamic_cast<const HttpRequestSuccessMessage&>(message);
-          OnHttpRequestSuccess(msg.Uri,
-                               msg.Answer,
-                               msg.AnswerSize,
-                               msg.Payload);
-        }; break;
-        default:
-          VLOG("unhandled message type" << message.GetType());
-        }
-      }
 
-      virtual void OnHttpRequestError(const std::string& uri,
-                                      Orthanc::IDynamicObject* payload) = 0;
-
-      virtual void OnHttpRequestSuccess(const std::string& uri,
-                                        const void* answer,
-                                        size_t answerSize,
-                                        Orthanc::IDynamicObject* payload) = 0;
-    };
 
     IWebService(MessageBroker& broker)
       : broker_(broker)
@@ -124,15 +82,20 @@
     {
     }
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    const Headers& headers,
-                                    Orthanc::IDynamicObject* payload) = 0;
+    virtual void GetAsync(const std::string& uri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60) = 0;
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const Headers& headers,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload) = 0;
+    virtual void PostAsync(const std::string& uri,
+                           const Headers& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload,
+                           MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                           MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                           unsigned int timeoutInSeconds = 60) = 0;
+
   };
 }
--- a/Framework/Toolbox/MessagingToolbox.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/MessagingToolbox.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -31,6 +31,7 @@
 
 #include <boost/lexical_cast.hpp>
 #include <json/reader.h>
+#include <json/writer.h>
 
 namespace OrthancStone
 {
@@ -114,6 +115,12 @@
                           target);
     }
 
+    void JsonToString(std::string& target,
+                      const Json::Value& source)
+    {
+      Json::FastWriter writer;
+      target = writer.write(source);
+    }
 
     static void ParseJsonException(Json::Value& target,
                                    const std::string& source)
--- a/Framework/Toolbox/MessagingToolbox.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/MessagingToolbox.h	Tue Oct 02 10:15:36 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,6 +38,10 @@
                    const void* content,
                    size_t size);
 
+    void JsonToString(std::string& target,
+                      const Json::Value& source);
+
+
     void RestApiGet(Json::Value& target,
                     OrthancPlugins::IOrthancConnection& orthanc,
                     const std::string& uri);
--- a/Framework/Toolbox/OrthancApiClient.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -22,184 +22,151 @@
 
 #include "MessagingToolbox.h"
 #include <Core/OrthancException.h>
+#include "Framework/Toolbox/MessagingToolbox.h"
 
 namespace OrthancStone {
 
-  struct OrthancApiClient::InternalGetJsonResponseReadyMessage :
-      public IMessage
+  OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc)
+    : IObservable(broker),
+      orthanc_(orthanc)
   {
-    OrthancApiClient::BaseRequest*  request_;
-    Json::Value   response_;
+  }
 
-    InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest*  request,
-                             const Json::Value& response)
-      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady),
-        request_(request),
-        response_(response)
+  // 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::JsonResponseReadyMessage>> orthancApiSuccessCallback_;
+    std::auto_ptr<MessageHandler<OrthancApiClient::HttpErrorMessage>> orthancApiFailureCallback_;
+  public:
+    HttpResponseToJsonConverter(MessageBroker& broker,
+                                MessageHandler<OrthancApiClient::JsonResponseReadyMessage>* orthancApiSuccessCallback,
+                                MessageHandler<OrthancApiClient::HttpErrorMessage>* orthancApiFailureCallback)
+      : IObserver(broker),
+        IObservable(broker),
+        orthancApiSuccessCallback_(orthancApiSuccessCallback),
+        orthancApiFailureCallback_(orthancApiFailureCallback)
     {
     }
 
-  };
+    void ConvertResponseToJson(const IWebService::HttpRequestSuccessMessage& message)
+    {
+      Json::Value response;
+      if (MessagingToolbox::ParseJson(response, message.answer_, message.answerSize_))
+      {
+        if (orthancApiSuccessCallback_.get() != NULL)
+        {
+          orthancApiSuccessCallback_->Apply(OrthancApiClient::JsonResponseReadyMessage(message.uri_, response, message.payload_));
+        }
+      }
+      else if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.uri_, message.payload_));
+      }
 
-  struct OrthancApiClient::InternalGetJsonResponseErrorMessage :
-      public IMessage
-  {
-    OrthancApiClient::BaseRequest*  request_;
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
+    }
 
-    InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request)
-      : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError),
-        request_(request)
+    void ConvertError(const IWebService::HttpRequestErrorMessage& message)
     {
+      if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.uri_));
+      }
+
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
     }
   };
 
-
-  // this class handles a single request to the OrthancApiClient.
-  // Once the response is ready, it will emit a message to the responseObserver
-  // the responseObserver must handle only that message (and not all messages from the OrthancApiClient)
-  class OrthancApiClient::BaseRequest:
-//      public IObserver,
-      public IObservable,
-      public Orthanc::IDynamicObject
+  // performs the translation between IWebService messages and OrthancApiClient messages
+  // TODO: handle destruction of this object (with shared_ptr ?::delete_later ???)
+  class HttpResponseToBinaryConverter : public IObserver, IObservable
   {
-  public:
-    std::string               uri_;
-    OrthancApiClient&         orthanc_;
-    MessageType               messageToEmitWhenResponseReady_;
-    OrthancApiClient::Mode    mode_;
-
+    std::auto_ptr<MessageHandler<OrthancApiClient::BinaryResponseReadyMessage>> orthancApiSuccessCallback_;
+    std::auto_ptr<MessageHandler<OrthancApiClient::HttpErrorMessage>> orthancApiFailureCallback_;
   public:
-    BaseRequest(
-        OrthancApiClient& orthanc,
-                   IObserver& responseObserver,
-                   const std::string& uri,
-                   MessageType messageToEmitWhenResponseReady,
-                   OrthancApiClient::Mode mode)
-      :
-        //IObserver(orthanc.broker_),
-        IObservable(orthanc.broker_),
-        uri_(uri),
-        orthanc_(orthanc),
-        messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady),
-        mode_(mode)
-    {
-      // this object will emit only a single message, the one the final responseObserver is expecting
-      DeclareEmittableMessage(messageToEmitWhenResponseReady);
-
-//      // this object is observing the OrthancApi so it must handle all messages
-//      DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-//      DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
-
-      //orthanc_.RegisterObserver(*this);
-      this->RegisterObserver(responseObserver);
-    }
-    virtual ~BaseRequest() {}
-
-//    // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver
-//    virtual void HandleMessage(IObservable& from, const IMessage& message)
-//    {
-//      switch (message.GetType())
-//      {
-//        case MessageType_OrthancApi_InternalGetJsonResponseReady:
-//      {
-//        const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast<const OrthancApiClient::InternalGetJsonResponseReadyMessage&>(message);
-//        EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_));
-//        orthanc_.ReleaseRequest(messageReceived.request_);
-//      }; break;
-//      default:
-//        throw MessageNotDeclaredException(message.GetType());
-//      }
-//    }
-
-  };
-
-
-  class OrthancApiClient::WebCallback : public IWebService::ICallback
-  {
-  private:
-    OrthancApiClient&  that_;
-
-  public:
-    WebCallback(MessageBroker& broker, OrthancApiClient&  that) :
-      IWebService::ICallback(broker),
-      that_(that)
+    HttpResponseToBinaryConverter(MessageBroker& broker,
+                                  MessageHandler<OrthancApiClient::BinaryResponseReadyMessage>* orthancApiSuccessCallback,
+                                  MessageHandler<OrthancApiClient::HttpErrorMessage>* orthancApiFailureCallback)
+      : IObserver(broker),
+        IObservable(broker),
+        orthancApiSuccessCallback_(orthancApiSuccessCallback),
+        orthancApiFailureCallback_(orthancApiFailureCallback)
     {
     }
 
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                                      const void* answer,
-                                      size_t answerSize,
-                                      Orthanc::IDynamicObject* payload)
+    void ConvertResponseToBinary(const IWebService::HttpRequestSuccessMessage& message)
     {
-      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
-
-      switch (request->mode_)
-      {
-      case OrthancApiClient::Mode_GetJson:
+      if (orthancApiSuccessCallback_.get() != NULL)
       {
-        Json::Value response;
-        if (MessagingToolbox::ParseJson(response, answer, answerSize))
-        {
-          request->EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(request->messageToEmitWhenResponseReady_, request->uri_, response));
-        }
-        else
-        {
-//          OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-//          that_.EmitMessage(msg);
-        }
-      };  break;
+        orthancApiSuccessCallback_->Apply(OrthancApiClient::BinaryResponseReadyMessage(message.uri_, message.answer_, message.answerSize_, message.payload_));
+      }
+      else if (orthancApiFailureCallback_.get() != NULL)
+      {
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.uri_, message.payload_));
+      }
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      that_.ReleaseRequest(request);
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
     }
 
-    virtual void OnHttpRequestError(const std::string& uri,
-                                    Orthanc::IDynamicObject* payload)
+    void ConvertError(const IWebService::HttpRequestErrorMessage& message)
     {
-      OrthancApiClient::BaseRequest* request = dynamic_cast<OrthancApiClient::BaseRequest*>(payload);  // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed"
-
-      switch (request->mode_)
-      {
-      case OrthancApiClient::Mode_GetJson:
+      if (orthancApiFailureCallback_.get() != NULL)
       {
-//        OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request);
-//        that_.EmitMessage(msg);
-          // TODO: the request shall send an error message
-      };  break;
+        orthancApiFailureCallback_->Apply(OrthancApiClient::HttpErrorMessage(message.uri_));
+      }
 
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-      that_.ReleaseRequest(request);
+      delete this; // hack untill we find someone to take ownership of this object (https://isocpp.org/wiki/faq/freestore-mgmt#delete-this)
     }
   };
 
-  OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc)
-    : IObservable(broker),
-      orthanc_(orthanc),
-      webCallback_(new OrthancApiClient::WebCallback(broker, *this))
+  void OrthancApiClient::GetJsonAsync(const std::string& uri,
+                                      MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                      MessageHandler<HttpErrorMessage>* failureCallback,
+                                      Orthanc::IDynamicObject* payload)
   {
-    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady);
-    DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError);
+    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.GetAsync(uri, IWebService::Headers(), payload,
+                      new Callable<HttpResponseToJsonConverter, IWebService::HttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson),
+                      new Callable<HttpResponseToJsonConverter, IWebService::HttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError));
+
+  }
+
+  void OrthancApiClient::GetBinaryAsync(const std::string& uri,
+                                        const IWebService::Headers& headers,
+                                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                                        MessageHandler<HttpErrorMessage>* failureCallback,
+                                        Orthanc::IDynamicObject* payload)
+  {
+    HttpResponseToBinaryConverter* converter = new HttpResponseToBinaryConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.GetAsync(uri, headers, payload,
+                      new Callable<HttpResponseToBinaryConverter, IWebService::HttpRequestSuccessMessage>(*converter, &HttpResponseToBinaryConverter::ConvertResponseToBinary),
+                      new Callable<HttpResponseToBinaryConverter, IWebService::HttpRequestErrorMessage>(*converter, &HttpResponseToBinaryConverter::ConvertError));
   }
 
-  void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady)
+  void OrthancApiClient::PostBinaryAsyncExpectJson(const std::string& uri,
+                                                   const std::string& body,
+                                                   MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                                   MessageHandler<HttpErrorMessage>* failureCallback,
+                                                   Orthanc::IDynamicObject* payload)
   {
-    OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this,
-                                                                               responseObserver,
-                                                                               uri,
-                                                                               messageToEmitWhenResponseReady,
-                                                                               OrthancApiClient::Mode_GetJson);
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request);
-    requestsInProgress_.insert(request);
+    HttpResponseToJsonConverter* converter = new HttpResponseToJsonConverter(broker_, successCallback, failureCallback);  // it is currently deleting itself after being used
+    orthanc_.PostAsync(uri, IWebService::Headers(), body, payload,
+                       new Callable<HttpResponseToJsonConverter, IWebService::HttpRequestSuccessMessage>(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson),
+                       new Callable<HttpResponseToJsonConverter, IWebService::HttpRequestErrorMessage>(*converter, &HttpResponseToJsonConverter::ConvertError));
+
   }
 
-  void OrthancApiClient::ReleaseRequest(BaseRequest* request)
+  void OrthancApiClient::PostJsonAsyncExpectJson(const std::string& uri,
+                                                 const Json::Value& data,
+                                                 MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                                 MessageHandler<HttpErrorMessage>* failureCallback,
+                                                 Orthanc::IDynamicObject* payload)
   {
-    requestsInProgress_.erase(request);
-    delete request;
+    std::string body;
+    MessagingToolbox::JsonToString(body, data);
+    return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload);
   }
 
+
 }
--- a/Framework/Toolbox/OrthancApiClient.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancApiClient.h	Tue Oct 02 10:15:36 2018 +0200
@@ -26,36 +26,68 @@
 
 #include "IWebService.h"
 #include "../Messages/IObservable.h"
-
+#include "../Messages/Promise.h"
 
 namespace OrthancStone
 {
   class OrthancApiClient:
       public IObservable
   {
-  protected:
-    class BaseRequest;
-    class GetJsonRequest;
+  public:
 
-    struct InternalGetJsonResponseReadyMessage;
-    struct InternalGetJsonResponseErrorMessage;
-
-  public:
-    struct GetJsonResponseReadyMessage : public IMessage
+    struct JsonResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetJson_Ready>
     {
-      Json::Value   response_;
-      std::string   uri_;
+      Json::Value   Response;
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
 
-      GetJsonResponseReadyMessage(MessageType messageType,
-                                  const std::string& uri,
-                                  const Json::Value& response)
-        : IMessage(messageType),
-          response_(response),
-          uri_(uri)
+      JsonResponseReadyMessage(const std::string& uri,
+                               const Json::Value& response,
+                               Orthanc::IDynamicObject*  payload = NULL)
+        : BaseMessage(),
+          Response(response),
+          Uri(uri),
+          Payload(payload)
       {
       }
     };
 
+    struct HttpErrorMessage : public BaseMessage<MessageType_OrthancApi_GenericHttpError_Ready>
+    {
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
+
+      HttpErrorMessage(const std::string& uri,
+                       Orthanc::IDynamicObject*  payload = NULL)
+        : BaseMessage(),
+          Uri(uri),
+          Payload(payload)
+      {
+      }
+    };
+
+    struct BinaryResponseReadyMessage : public BaseMessage<MessageType_OrthancApi_GenericGetBinary_Ready>
+    {
+      const void* Answer;
+      size_t AnswerSize;
+      std::string   Uri;
+      Orthanc::IDynamicObject*  Payload;
+
+      BinaryResponseReadyMessage(const std::string& uri,
+                                 const void* answer,
+                                 size_t answerSize,
+                                 Orthanc::IDynamicObject*  payload = NULL)
+        : BaseMessage(),
+          Answer(answer),
+          AnswerSize(answerSize),
+          Uri(uri),
+          Payload(payload)
+      {
+      }
+    };
+
+
+
   public:
 
     enum Mode
@@ -65,26 +97,50 @@
 
   protected:
     IWebService&                      orthanc_;
-    class WebCallback;
-    boost::shared_ptr<WebCallback>    webCallback_;  // This is a PImpl pattern
-    std::set<BaseRequest*>            requestsInProgress_;
-
-//    int ScheduleGetJsonRequest(const std::string& uri);
-
-    void ReleaseRequest(BaseRequest* request);
 
   public:
     OrthancApiClient(MessageBroker& broker,
-                        IWebService& orthanc);
+                     IWebService& orthanc);
     virtual ~OrthancApiClient() {}
 
-    // schedule a GET request expecting a JSON request.
-    // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call
-    void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady);
+    // schedule a GET request expecting a JSON response.
+    void GetJsonAsync(const std::string& uri,
+                      MessageHandler<JsonResponseReadyMessage>* successCallback,
+                      MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                      Orthanc::IDynamicObject* payload = NULL);
+
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const std::string& contentType,
+                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL)
+    {
+      IWebService::Headers headers;
+      headers["Accept"] = contentType;
+      GetBinaryAsync(uri, headers, successCallback, failureCallback, payload);
+    }
 
-    void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);}
-    void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);}
-    void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);}
+    // schedule a GET request expecting a binary response.
+    void GetBinaryAsync(const std::string& uri,
+                        const IWebService::Headers& headers,
+                        MessageHandler<BinaryResponseReadyMessage>* successCallback,
+                        MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                        Orthanc::IDynamicObject* payload = NULL);
+
+    // schedule a POST request expecting a JSON response.
+    void PostBinaryAsyncExpectJson(const std::string& uri,
+                                   const std::string& body,
+                                   MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                   MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                                   Orthanc::IDynamicObject* payload = NULL);
+
+    // schedule a POST request expecting a JSON response.
+    void PostJsonAsyncExpectJson(const std::string& uri,
+                                 const Json::Value& data,
+                                 MessageHandler<JsonResponseReadyMessage>* successCallback,
+                                 MessageHandler<HttpErrorMessage>* failureCallback = NULL,
+                                 Orthanc::IDynamicObject* payload = NULL);
 
   };
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -79,32 +79,32 @@
     const Slice*       slice_;
     std::string        instanceId_;
     SliceImageQuality  quality_;
-    
+
     Operation(Mode mode) :
       mode_(mode)
     {
     }
-    
+
   public:
     Mode GetMode() const
     {
       return mode_;
     }
-    
+
     SliceImageQuality GetQuality() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return quality_;
     }
-    
+
     unsigned int GetSliceIndex() const
     {
       assert(mode_ == Mode_LoadImage ||
              mode_ == Mode_LoadRawImage);
       return sliceIndex_;
     }
-    
+
     const Slice& GetSlice() const
     {
       assert(mode_ == Mode_LoadImage ||
@@ -112,7 +112,7 @@
       assert(slice_ != NULL);
       return *slice_;
     }
-    
+
     unsigned int GetFrame() const
     {
       assert(mode_ == Mode_FrameGeometry);
@@ -126,18 +126,13 @@
       return instanceId_;
     }
 
-    static Operation* DownloadSeriesGeometry()
-    {
-      return new Operation(Mode_SeriesGeometry);
-    }
-    
     static Operation* DownloadInstanceGeometry(const std::string& instanceId)
     {
       std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
       operation->instanceId_ = instanceId;
       return operation.release();
     }
-    
+
     static Operation* DownloadFrameGeometry(const std::string& instanceId,
                                             unsigned int frame)
     {
@@ -146,7 +141,7 @@
       operation->frame_ = frame;
       return operation.release();
     }
-    
+
     static Operation* DownloadSliceImage(unsigned int  sliceIndex,
                                          const Slice&  slice,
                                          SliceImageQuality quality)
@@ -157,7 +152,7 @@
       tmp->quality_ = quality;
       return tmp.release();
     }
-    
+
     static Operation* DownloadSliceRawImage(unsigned int  sliceIndex,
                                             const Slice&  slice)
     {
@@ -177,107 +172,6 @@
 
   };
 
-  
-  class OrthancSlicesLoader::WebCallback : public IWebService::ICallback
-  {
-  private:
-    OrthancSlicesLoader&  that_;
-    
-  public:
-    WebCallback(MessageBroker& broker, OrthancSlicesLoader&  that) :
-      IWebService::ICallback(broker),
-      that_(that)
-    {
-    }
-    
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                                      const void* answer,
-                                      size_t answerSize,
-                                      Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      
-      switch (operation->GetMode())
-      {
-      case Mode_SeriesGeometry:
-        that_.ParseSeriesGeometry(answer, answerSize);
-        break;
-        
-      case Mode_InstanceGeometry:
-        that_.ParseInstanceGeometry(operation->GetInstanceId(), answer, answerSize);
-        break;
-        
-      case Mode_FrameGeometry:
-        that_.ParseFrameGeometry(operation->GetInstanceId(),
-                                 operation->GetFrame(), answer, answerSize);
-        break;
-        
-      case Mode_LoadImage:
-        switch (operation->GetQuality())
-        {
-        case SliceImageQuality_FullPng:
-          that_.ParseSliceImagePng(*operation, answer, answerSize);
-          break;
-        case SliceImageQuality_FullPam:
-          that_.ParseSliceImagePam(*operation, answer, answerSize);
-          break;
-
-        case SliceImageQuality_Jpeg50:
-        case SliceImageQuality_Jpeg90:
-        case SliceImageQuality_Jpeg95:
-          that_.ParseSliceImageJpeg(*operation, answer, answerSize);
-          break;
-          
-        default:
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-        }
-
-        break;
-        
-      case Mode_LoadRawImage:
-        that_.ParseSliceRawImage(*operation, answer, answerSize);
-        break;
-        
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-    
-    virtual void OnHttpRequestError(const std::string& uri,
-                                    Orthanc::IDynamicObject* payload)
-    {
-      std::auto_ptr<Operation> operation(dynamic_cast<Operation*>(payload));
-      LOG(ERROR) << "Cannot download " << uri;
-      
-      switch (operation->GetMode())
-      {
-      case Mode_FrameGeometry:
-      case Mode_SeriesGeometry:
-        that_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
-        that_.state_ = State_Error;
-        break;
-        
-      case Mode_LoadImage:
-      {
-        OrthancSlicesLoader::SliceImageErrorMessage msg(operation->GetSliceIndex(),
-                                   operation->GetSlice(),
-                                   operation->GetQuality());
-        that_.EmitMessage(msg);
-      }; break;
-
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-      }
-    }
-  };
-  
-  void OrthancSlicesLoader::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    // forward messages to its own observers
-    IObservable::broker_.EmitMessage(from, IObservable::observers_, message);
-  }
-
-  
   void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
                                                     std::auto_ptr<Orthanc::ImageAccessor>& image)
   {
@@ -321,27 +215,30 @@
     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));
     }
   }
   
-  
-  void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
-                                                size_t size)
+  void OrthancSlicesLoader::OnGeometryError(const OrthancApiClient::HttpErrorMessage& message)
+  {
+    EmitMessage(SliceGeometryErrorMessage(*this));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::OnSliceImageError(const OrthancApiClient::HttpErrorMessage& message)
   {
-    Json::Value series;
-    if (!MessagingToolbox::ParseJson(series, answer, size) ||
-        series.type() != Json::objectValue)
-    {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
-      return;
-    }
-    
+    NotifySliceImageError(dynamic_cast<const Operation&>(*(message.Payload)));
+    state_ = State_Error;
+  }
+
+  void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    Json::Value series = message.Response;
     Json::Value::Members instances = series.getMemberNames();
     
     slices_.Reserve(instances.size());
@@ -376,19 +273,11 @@
     SortAndFinalizeSlices();
   }
   
-  
-  void OrthancSlicesLoader::ParseInstanceGeometry(const std::string& instanceId,
-                                                  const void* answer,
-                                                  size_t size)
+  void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value tags;
-    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
-        tags.type() != Json::objectValue)
-    {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
-      return;
-    }
-    
+    Json::Value tags = message.Response;
+    const std::string& instanceId = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetInstanceId();
+
     OrthancPlugins::FullOrthancDataset dataset(tags);
     
     Orthanc::DicomMap dicom;
@@ -412,7 +301,7 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+        EmitMessage(SliceGeometryErrorMessage(*this));
         return;
       }
     }
@@ -421,19 +310,12 @@
   }
   
   
-  void OrthancSlicesLoader::ParseFrameGeometry(const std::string& instanceId,
-                                               unsigned int frame,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value tags;
-    if (!MessagingToolbox::ParseJson(tags, answer, size) ||
-        tags.type() != Json::objectValue)
-    {
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
-      return;
-    }
-    
+    Json::Value tags = message.Response;
+    const std::string& instanceId = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetInstanceId();
+    unsigned int frame = dynamic_cast<OrthancSlicesLoader::Operation*>(message.Payload)->GetFrame();
+
     OrthancPlugins::FullOrthancDataset dataset(tags);
     
     state_ = State_GeometryReady;
@@ -446,26 +328,25 @@
     {
       LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
+      EmitMessage(SliceGeometryReadyMessage(*this));
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
+      EmitMessage(SliceGeometryErrorMessage(*this));
     }
   }
   
   
-  void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     std::auto_ptr<Orthanc::ImageAccessor>  image;
     
     try
     {
       image.reset(new Orthanc::PngReader);
-      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer, size);
+      dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.Answer, message.AnswerSize);
     }
     catch (Orthanc::OrthancException&)
     {
@@ -497,16 +378,15 @@
     NotifySliceImageSuccess(operation, image);
   }
   
-  void OrthancSlicesLoader::ParseSliceImagePam(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     std::auto_ptr<Orthanc::ImageAccessor>  image;
 
     try
     {
       image.reset(new Orthanc::PamReader);
-      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(answer), size));
+      dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(std::string(reinterpret_cast<const char*>(message.Answer), message.AnswerSize));
     }
     catch (Orthanc::OrthancException&)
     {
@@ -539,13 +419,12 @@
   }
 
 
-  void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
-                                                const void* answer,
-                                                size_t size)
+  void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message)
   {
-    Json::Value encoded;
-    if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
-        encoded.type() != Json::objectValue ||
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
+
+    Json::Value encoded = message.Response;
+    if (encoded.type() != Json::objectValue ||
         !encoded.isMember("Orthanc") ||
         encoded["Orthanc"].type() != Json::objectValue)
     {
@@ -714,14 +593,13 @@
     }
   };
   
-  void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation,
-                                               const void* answer,
-                                               size_t size)
+  void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message)
   {
+    const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(*message.Payload);
     Orthanc::GzipCompressor compressor;
     
     std::string raw;
-    compressor.Uncompress(raw, answer, size);
+    compressor.Uncompress(raw, message.Answer, message.AnswerSize);
     
     const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation();
     
@@ -776,18 +654,12 @@
   
   
   OrthancSlicesLoader::OrthancSlicesLoader(MessageBroker& broker,
-                                           //ISliceLoaderObserver& callback,
-                                           IWebService& orthanc) :
+                                           OrthancApiClient& orthanc) :
     IObservable(broker),
-    webCallback_(new WebCallback(broker, *this)),
-    //userCallback_(callback),
+    IObserver(broker),
     orthanc_(orthanc),
     state_(State_Initialization)
   {
-    DeclareEmittableMessage(MessageType_SliceLoader_GeometryReady);
-    DeclareEmittableMessage(MessageType_SliceLoader_GeometryError);
-    DeclareEmittableMessage(MessageType_SliceLoader_ImageError);
-    DeclareEmittableMessage(MessageType_SliceLoader_ImageReady);
   }
   
   
@@ -800,12 +672,13 @@
     else
     {
       state_ = State_LoadingGeometry;
-      std::string uri = "/series/" + seriesId + "/instances-tags";
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), Operation::DownloadSeriesGeometry());
+      orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            NULL);
     }
   }
   
-  
   void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
   {
     if (state_ != State_Initialization)
@@ -818,9 +691,10 @@
       
       // Tag "3004-000c" is "Grid Frame Offset Vector", which is
       // mandatory to read RT DOSE, but is too long to be returned by default
-      std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3004-000c";
-      orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadInstanceGeometry(instanceId));
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadInstanceGeometry(instanceId));
     }
   }
   
@@ -835,9 +709,11 @@
     else
     {
       state_ = State_LoadingGeometry;
-      std::string uri = "/instances/" + instanceId + "/tags";
-      orthanc_.ScheduleGetRequest
-          (*webCallback_, uri, IWebService::Headers(), Operation::DownloadFrameGeometry(instanceId, frame));
+
+      orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
+                            Operation::DownloadFrameGeometry(instanceId, frame));
     }
   }
   
@@ -906,10 +782,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
     
-    IWebService::Headers headers;
-    headers["Accept"] = "image/png";
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
+    orthanc_.GetBinaryAsync(uri, "image/png",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImagePng),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                            Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPng));
   }
   
   void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
@@ -936,10 +812,10 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
-    IWebService::Headers headers;
-    headers["Accept"] = "image/x-portable-arbitrarymap";
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, headers,
-                                Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
+    orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImagePam),
+                            new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                            Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam));
   }
 
 
@@ -974,8 +850,10 @@
                        "-" + slice.GetOrthancInstanceId() + "_" +
                        boost::lexical_cast<std::string>(slice.GetFrame()));
 
-    orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                Operation::DownloadSliceImage(index, slice, quality));
+    orthanc_.GetJsonAsync(uri,
+                          new Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
+                          new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                          Operation::DownloadSliceImage(index, slice, quality));
   }
   
   
@@ -1008,8 +886,10 @@
     {
       std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
                          boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
-      orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(),
-                                  Operation::DownloadSliceRawImage(index, slice));
+      orthanc_.GetBinaryAsync(uri, IWebService::Headers(),
+                              new Callable<OrthancSlicesLoader, OrthancApiClient::BinaryResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSliceRawImage),
+                              new Callable<OrthancSlicesLoader, OrthancApiClient::HttpErrorMessage>(*this, &OrthancSlicesLoader::OnSliceImageError),
+                              Operation::DownloadSliceRawImage(index, slice));
     }
   }
 }
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Tue Oct 02 10:15:36 2018 +0200
@@ -26,13 +26,18 @@
 #include "../StoneEnumerations.h"
 #include "../Messages/IObservable.h"
 #include <boost/shared_ptr.hpp>
+#include "OrthancApiClient.h"
 
 namespace OrthancStone
 {
-  class OrthancSlicesLoader : public IObservable
+  class OrthancSlicesLoader : public IObservable, public IObserver
   {
   public:
-    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 +48,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 +57,7 @@
       }
     };
 
-    struct SliceImageErrorMessage : public IMessage
+    struct SliceImageErrorMessage : public BaseMessage<MessageType_SliceLoader_ImageError>
     {
       const Slice& slice_;
       unsigned int sliceIndex_;
@@ -61,7 +66,7 @@
       SliceImageErrorMessage(unsigned int sliceIndex,
                         const Slice& slice,
                         SliceImageQuality effectiveQuality)
-        : IMessage(MessageType_SliceLoader_ImageError),
+        : BaseMessage(),
           slice_(slice),
           sliceIndex_(sliceIndex),
           effectiveQuality_(effectiveQuality)
@@ -89,11 +94,8 @@
     };
 
     class Operation;
-    class WebCallback;
 
-    boost::shared_ptr<WebCallback>  webCallback_;  // This is a PImpl pattern
-
-    IWebService&  orthanc_;
+    OrthancApiClient&  orthanc_;
     State         state_;
     SlicesSorter  slices_;
 
@@ -101,34 +103,23 @@
                                  std::auto_ptr<Orthanc::ImageAccessor>& image);
   
     void NotifySliceImageError(const Operation& operation);
-    
-    void ParseSeriesGeometry(const void* answer,
-                             size_t size);
+
+    void OnGeometryError(const OrthancApiClient::HttpErrorMessage& message);
+    void OnSliceImageError(const OrthancApiClient::HttpErrorMessage& message);
 
-    void ParseInstanceGeometry(const std::string& instanceId,
-                               const void* answer,
-                               size_t size);
+    void ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseFrameGeometry(const std::string& instanceId,
-                            unsigned int frame,
-                            const void* answer,
-                            size_t size);
+    void ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseSliceImagePng(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message);
 
-    void ParseSliceImagePam(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
-    void ParseSliceImageJpeg(const Operation& operation,
-                             const void* answer,
-                             size_t size);
+    void ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
-    void ParseSliceRawImage(const Operation& operation,
-                            const void* answer,
-                            size_t size);
+    void ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message);
 
     void ScheduleSliceImagePng(const Slice& slice,
                                size_t index);
@@ -145,7 +136,7 @@
   public:
     OrthancSlicesLoader(MessageBroker& broker,
                         //ISliceLoaderObserver& callback,
-                        IWebService& orthanc);
+                        OrthancApiClient& orthancApi);
 
     void ScheduleLoadSeries(const std::string& seriesId);
 
@@ -166,6 +157,6 @@
     void ScheduleLoadSliceImage(size_t index,
                                 SliceImageQuality requestedQuality);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
+
   };
 }
--- a/Framework/Volumes/StructureSetLoader.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -27,131 +27,63 @@
 
 namespace OrthancStone
 {
-  class StructureSetLoader::Operation : public Orthanc::IDynamicObject
-  {
-  public:
-    enum Type
-    {
-      Type_LoadStructureSet,
-      Type_LookupSopInstanceUid,
-      Type_LoadReferencedSlice
-    };
-    
-  private:
-    Type         type_;
-    std::string  value_;
-
-  public:
-    Operation(Type type,
-              const std::string& value) :
-      type_(type),
-      value_(value)
-    {
-    }
-
-    Type GetType() const
-    {
-      return type_;
-    }
-
-    const std::string& GetIdentifier() const
-    {
-      return value_;
-    }
-  };
-
-
-  void StructureSetLoader::OnHttpRequestError(const std::string& uri,
-                                       Orthanc::IDynamicObject* payload)
-  {
-    // TODO
-  }
-
   
-  void StructureSetLoader::OnHttpRequestSuccess(const std::string& uri,
-                                         const void* answer,
-                                         size_t answerSize,
-                                         Orthanc::IDynamicObject* payload)
-  {
-    std::auto_ptr<Operation> op(dynamic_cast<Operation*>(payload));
-
-    switch (op->GetType())
-    {
-      case Operation::Type_LoadStructureSet:
-      {
-        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
-        structureSet_.reset(new DicomStructureSet(dataset));
-
-        std::set<std::string> instances;
-        structureSet_->GetReferencedInstances(instances);
-
-        for (std::set<std::string>::const_iterator it = instances.begin();
-             it != instances.end(); ++it)
-        {
-          orthanc_.SchedulePostRequest(*this, "/tools/lookup", IWebService::Headers(), *it,
-                                       new Operation(Operation::Type_LookupSopInstanceUid, *it));
-        }
-        
-        VolumeLoaderBase::NotifyGeometryReady();
-
-        break;
-      }
-        
-      case Operation::Type_LookupSopInstanceUid:
-      {
-        Json::Value lookup;
-        
-        if (MessagingToolbox::ParseJson(lookup, answer, answerSize))
-        {
-          if (lookup.type() != Json::arrayValue ||
-              lookup.size() != 1 ||
-              !lookup[0].isMember("Type") ||
-              !lookup[0].isMember("Path") ||
-              lookup[0]["Type"].type() != Json::stringValue ||
-              lookup[0]["ID"].type() != Json::stringValue ||
-              lookup[0]["Type"].asString() != "Instance")
-          {
-            throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
-          }
-
-          const std::string& instance = lookup[0]["ID"].asString();
-          orthanc_.ScheduleGetRequest(*this, "/instances/" + instance + "/tags", IWebService::Headers(),
-                                      new Operation(Operation::Type_LoadReferencedSlice, instance));
-        }
-        else
-        {
-          // TODO
-        }
-        
-        break;
-      }
-
-      case Operation::Type_LoadReferencedSlice:
-      {
-        OrthancPlugins::FullOrthancDataset dataset(answer, answerSize);
-
-        Orthanc::DicomMap slice;
-        MessagingToolbox::ConvertDataset(slice, dataset);
-        structureSet_->AddReferencedSlice(slice);
-
-        VolumeLoaderBase::NotifyContentChange();
-
-        break;
-      }
-      
-      default:
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-    }
-  } 
-
-  
-  StructureSetLoader::StructureSetLoader(MessageBroker& broker, IWebService& orthanc) :
-    IWebService::ICallback(broker),
+  StructureSetLoader::StructureSetLoader(MessageBroker& broker, OrthancApiClient& orthanc) :
+    OrthancStone::IObserver(broker),
     orthanc_(orthanc)
   {
   }
   
 
+  void StructureSetLoader::OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.Response);
+
+    Orthanc::DicomMap slice;
+    MessagingToolbox::ConvertDataset(slice, dataset);
+    structureSet_->AddReferencedSlice(slice);
+
+    VolumeLoaderBase::NotifyContentChange();
+  }
+
+  void StructureSetLoader::OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    OrthancPlugins::FullOrthancDataset dataset(message.Response);
+    structureSet_.reset(new DicomStructureSet(dataset));
+
+    std::set<std::string> instances;
+    structureSet_->GetReferencedInstances(instances);
+
+    for (std::set<std::string>::const_iterator it = instances.begin();
+         it != instances.end(); ++it)
+    {
+      orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it,
+                            new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted));
+    }
+
+    VolumeLoaderBase::NotifyGeometryReady();
+  }
+
+  void StructureSetLoader::OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message)
+  {
+    Json::Value lookup = message.Response;
+
+    if (lookup.type() != Json::arrayValue ||
+        lookup.size() != 1 ||
+        !lookup[0].isMember("Type") ||
+        !lookup[0].isMember("Path") ||
+        lookup[0]["Type"].type() != Json::stringValue ||
+        lookup[0]["ID"].type() != Json::stringValue ||
+        lookup[0]["Type"].asString() != "Instance")
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    const std::string& instance = lookup[0]["ID"].asString();
+    orthanc_.GetJsonAsync("/instances/" + instance + "/tags",
+                          new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnReferencedSliceLoaded));
+  }
+
   void StructureSetLoader::ScheduleLoadInstance(const std::string& instance)
   {
     if (structureSet_.get() != NULL)
@@ -160,8 +92,8 @@
     }
     else
     {
-      const std::string uri = "/instances/" + instance + "/tags?ignore-length=3006-0050";
-      orthanc_.ScheduleGetRequest(*this, uri, IWebService::Headers(), new Operation(Operation::Type_LoadStructureSet, instance));
+      orthanc_.GetJsonAsync("/instances/" + instance + "/tags?ignore-length=3006-0050",
+                            new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnStructureSetLoaded));
     }
   }
 
--- a/Framework/Volumes/StructureSetLoader.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Volumes/StructureSetLoader.h	Tue Oct 02 10:15:36 2018 +0200
@@ -22,31 +22,22 @@
 #pragma once
 
 #include "../Toolbox/DicomStructureSet.h"
-#include "../Toolbox/IWebService.h"
+#include "../Toolbox/OrthancApiClient.h"
 #include "VolumeLoaderBase.h"
 
 namespace OrthancStone
 {
   class StructureSetLoader :
     public VolumeLoaderBase,
-    private IWebService::ICallback
+    public OrthancStone::IObserver
   {
   private:
-    class Operation;
-    
-    virtual void OnHttpRequestError(const std::string& uri,
-                             Orthanc::IDynamicObject* payload);
 
-    virtual void OnHttpRequestSuccess(const std::string& uri,
-                               const void* answer,
-                               size_t answerSize,
-                               Orthanc::IDynamicObject* payload);
-
-    IWebService&                      orthanc_;
+    OrthancApiClient&                      orthanc_;
     std::auto_ptr<DicomStructureSet>  structureSet_;
 
   public:
-    StructureSetLoader(MessageBroker& broker, IWebService& orthanc);
+    StructureSetLoader(MessageBroker& broker, OrthancApiClient& orthanc);
 
     void ScheduleLoadInstance(const std::string& instance);
 
@@ -56,5 +47,12 @@
     }
 
     DicomStructureSet& GetStructureSet();
+
+  protected:
+    void OnReferencedSliceLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnStructureSetLoaded(const OrthancApiClient::JsonResponseReadyMessage& message);
+
+    void OnLookupCompleted(const OrthancApiClient::JsonResponseReadyMessage& message);
   };
 }
--- a/Framework/Widgets/LayerWidget.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Tue Oct 02 10:15:36 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);
   }
@@ -386,6 +386,15 @@
     }
   }
   
+  void LayerWidget::ObserveLayer(ILayerSource& layer)
+  {
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryReadyMessage>(*this, &LayerWidget::OnGeometryReady));
+    // currently ignore errors layer->RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::GeometryErrorMessage>(*this, &LayerWidget::...));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::SliceChangedMessage>(*this, &LayerWidget::OnSliceChanged));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::ContentChangedMessage>(*this, &LayerWidget::OnContentChanged));
+    layer.RegisterObserverCallback(new Callable<LayerWidget, ILayerSource::LayerReadyMessage>(*this, &LayerWidget::OnLayerReady));
+  }
+
 
   size_t LayerWidget::AddLayer(ILayerSource* layer)  // Takes ownership
   {
@@ -400,7 +409,8 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->RegisterObserver(*this);
+
+    ObserveLayer(*layer);
 
     ResetChangedLayers();
 
@@ -424,7 +434,8 @@
     layersIndex_[layer] = index;
 
     ResetPendingScene();
-    layer->RegisterObserver(*this);
+
+    ObserveLayer(*layer);
 
     InvalidateLayer(index);
   }
@@ -491,45 +502,17 @@
     }
   }
 
-  void LayerWidget::HandleMessage(IObservable& from, const IMessage& message)
-  {
-    switch (message.GetType()) {
-    case MessageType_LayerSource_GeometryReady:
-      OnGeometryReady(dynamic_cast<const ILayerSource&>(from));
-      break;
-    case MessageType_LayerSource_GeometryError:
-      LOG(ERROR) << "Cannot get geometry";
-      break;
-    case MessageType_LayerSource_ContentChanged:
-      OnContentChanged(dynamic_cast<const ILayerSource&>(from));
-      break;
-    case MessageType_LayerSource_SliceChanged:
-      OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
-      break;
-    case MessageType_LayerSource_LayerReady:
-    {
-      const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
-      OnLayerReady(layerReadyMessage.layer_,
-                   dynamic_cast<const ILayerSource&>(from),
-                   layerReadyMessage.slice_,
-                   layerReadyMessage.isError_);
-    }; break;
-    default:
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
-    }
-  }
-
-  void LayerWidget::OnGeometryReady(const ILayerSource& source)
+  void LayerWidget::OnGeometryReady(const ILayerSource::GeometryReadyMessage& message)
   {
     size_t i;
-    if (LookupLayer(i, source))
+    if (LookupLayer(i, message.origin_))
     {
       LOG(INFO) << ": Geometry ready for layer " << i << " in " << GetName();
 
       changedLayers_[i] = true;
       //layers_[i]->ScheduleLayerCreation(slice_);
     }
-    EmitMessage(IMessage(MessageType_Widget_GeometryChanged));
+    EmitMessage(GeometryChangedMessage(*this));
   }
   
   void LayerWidget::InvalidateAllLayers()
@@ -558,39 +541,37 @@
   }
 
 
-  void LayerWidget::OnContentChanged(const ILayerSource& source)
+  void LayerWidget::OnContentChanged(const ILayerSource::ContentChangedMessage& message)
   {
     size_t index;
-    if (LookupLayer(index, source))
+    if (LookupLayer(index, message.origin_))
     {
       InvalidateLayer(index);
     }
+    EmitMessage(LayerWidget::ContentChangedMessage(*this));
   }
   
 
-  void LayerWidget::OnSliceChanged(const ILayerSource& source,
-                                   const Slice& slice)
+  void LayerWidget::OnSliceChanged(const ILayerSource::SliceChangedMessage& message)
   {
-    if (slice.ContainsPlane(slice_))
+    if (message.slice_.ContainsPlane(slice_))
     {
       size_t index;
-      if (LookupLayer(index, source))
+      if (LookupLayer(index, message.origin_))
       {
         InvalidateLayer(index);
       }
     }
+    EmitMessage(LayerWidget::ContentChangedMessage(*this));
   }
   
   
-  void LayerWidget::OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                 const ILayerSource& source,
-                                 const CoordinateSystem3D& slice,
-                                 bool isError)
+  void LayerWidget::OnLayerReady(const ILayerSource::LayerReadyMessage& message)
   {
     size_t index;
-    if (LookupLayer(index, source))
+    if (LookupLayer(index, message.origin_))
     {
-      if (isError)
+      if (message.isError_)
       {
         LOG(ERROR) << "Using error renderer on layer " << index;
       }
@@ -599,16 +580,17 @@
         LOG(INFO) << "Renderer ready for layer " << index;
       }
       
-      if (renderer.get() != NULL)
+      if (message.renderer_.get() != NULL)
       {
-        UpdateLayer(index, renderer.release(), slice);
+        UpdateLayer(index, message.renderer_.release(), message.slice_);
       }
-      else if (isError)
+      else if (message.isError_)
       {
         // TODO
         //UpdateLayer(index, new SliceOutlineRenderer(slice), slice);
       }
     }
+    EmitMessage(LayerWidget::ContentChangedMessage(*this));
   }
 
 
--- a/Framework/Widgets/LayerWidget.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Tue Oct 02 10:15:36 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;
     
@@ -55,26 +59,21 @@
     void GetLayerExtent(Extent2D& extent,
                         ILayerSource& source) const;
 
-    void OnGeometryReady(const ILayerSource& source);
+    void OnGeometryReady(const ILayerSource::GeometryReadyMessage& message);
 
-    virtual void OnContentChanged(const ILayerSource& source);
+    virtual void OnContentChanged(const ILayerSource::ContentChangedMessage& message);
 
-    virtual void OnSliceChanged(const ILayerSource& source,
-                                const Slice& slice);
+    virtual void OnSliceChanged(const ILayerSource::SliceChangedMessage& message);
 
-    virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                              const ILayerSource& source,
-                              const CoordinateSystem3D& slice,
-                              bool isError);
+    virtual void OnLayerReady(const ILayerSource::LayerReadyMessage& message);
 
+    void ObserveLayer(ILayerSource& source);
 
     void ResetChangedLayers();
 
   public:
     LayerWidget(MessageBroker& broker, const std::string& name);
 
-    virtual void HandleMessage(IObservable& from, const IMessage& message);
-
     virtual Extent2D GetSceneExtent();
 
   protected:
--- a/Framework/dev.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Framework/dev.h	Tue Oct 02 10:15:36 2018 +0200
@@ -232,14 +232,14 @@
 
   public:
     OrthancVolumeImage(MessageBroker& broker,
-                       IWebService& orthanc,
+                       OrthancApiClient& orthanc,
                        bool computeRange) : 
       OrthancStone::IObserver(broker),
       loader_(broker, orthanc),
       computeRange_(computeRange),
       pendingSlices_(0)
     {
-        loader_.RegisterObserver(*this);
+        // TODO: replace with new callables loader_.RegisterObserver(*this);
     }
 
     void ScheduleLoadSeries(const std::string& seriesId)
--- a/Platforms/Generic/Oracle.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/Oracle.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -66,7 +66,15 @@
         if (item.get() != NULL)
         {
           IOracleCommand& command = dynamic_cast<IOracleCommand&>(*item);
-          command.Execute();
+          try
+          {
+            command.Execute();
+          }
+          catch (Orthanc::OrthancException& ex)
+          {
+            // this is probably a curl error that has been triggered.  We may just ignore it.
+            // The command.success_ will stay at false and this will be handled in the command.Commit
+          }
 
           // Random sleeping to test
           //boost::this_thread::sleep(boost::posix_time::milliseconds(50 * (1 + rand() % 10)));
--- a/Platforms/Generic/OracleWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/OracleWebService.h	Tue Oct 02 10:15:36 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/>.
  **/
@@ -51,31 +51,35 @@
     {
     }
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    const Headers& headers,
-                                    Orthanc::IDynamicObject* payload)
+    virtual void GetAsync(const std::string& uri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload, // takes ownership
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,   // takes ownership
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,// takes ownership
+                          unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServiceGetCommand(broker_, callback, parameters_, uri, headers, payload, context_));
+      oracle_.Submit(new WebServiceGetCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
     }
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const Headers& headers,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload)
+    virtual void PostAsync(const std::string& uri,
+                           const Headers& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload, // takes ownership
+                           MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback, // takes ownership
+                           MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, // takes ownership
+                           unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServicePostCommand(broker_, callback, parameters_, uri, headers, body, payload, context_));
+      oracle_.Submit(new WebServicePostCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_));
     }
 
     void Start()
     {
-        oracle_.Start();
+      oracle_.Start();
     }
 
     void Stop()
     {
-        oracle_.Stop();
+      oracle_.Stop();
     }
   };
 }
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -26,23 +26,24 @@
 namespace OrthancStone
 {
   WebServiceCommandBase::WebServiceCommandBase(MessageBroker& broker,
-                                               IWebService::ICallback& callback,
+                                               MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                                               MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
                                                const IWebService::Headers& headers,
+                                               unsigned int timeoutInSeconds,
                                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                                NativeStoneApplicationContext& context) :
     IObservable(broker),
-    callback_(callback),
+    successCallback_(successCallback),
+    failureCallback_(failureCallback),
     parameters_(parameters),
     uri_(uri),
     headers_(headers),
     payload_(payload),
-    context_(context)
+    context_(context),
+    timeoutInSeconds_(timeoutInSeconds)
   {
-    DeclareEmittableMessage(MessageType_HttpRequestError);
-    DeclareEmittableMessage(MessageType_HttpRequestSuccess);
-    RegisterObserver(callback);
   }
 
 
@@ -50,15 +51,15 @@
   {
     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_)
+    if (success_ && successCallback_.get() != NULL)
     {
-      IWebService::ICallback::HttpRequestSuccessMessage message(uri_, answer_.c_str(), answer_.size(), payload_.release());
-      EmitMessage(message);
+      successCallback_->Apply(IWebService::HttpRequestSuccessMessage(uri_, answer_.c_str(), answer_.size(), payload_.release()));
     }
-    else
+    else if (!success_ && failureCallback_.get() != NULL)
     {
-      IWebService::ICallback::HttpRequestErrorMessage message(uri_, payload_.release());
-      EmitMessage(message);
+      failureCallback_->Apply(IWebService::HttpRequestErrorMessage(uri_, payload_.release()));
     }
+
   }
+
 }
--- a/Platforms/Generic/WebServiceCommandBase.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.h	Tue Oct 02 10:15:36 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>
@@ -36,7 +37,8 @@
   class WebServiceCommandBase : public IOracleCommand, IObservable
   {
   protected:
-    IWebService::ICallback&                 callback_;
+    std::auto_ptr<MessageHandler<IWebService::HttpRequestSuccessMessage>>                              successCallback_;
+    std::auto_ptr<MessageHandler<IWebService::HttpRequestErrorMessage>>                                failureCallback_;
     Orthanc::WebServiceParameters           parameters_;
     std::string                             uri_;
     std::map<std::string, std::string>      headers_;
@@ -44,18 +46,23 @@
     bool                                    success_;
     std::string                             answer_;
     NativeStoneApplicationContext&          context_;
+    unsigned int                            timeoutInSeconds_;
 
   public:
     WebServiceCommandBase(MessageBroker& broker,
-                          IWebService::ICallback& callback,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
                           const std::map<std::string, std::string>& headers,
+                          unsigned int timeoutInSeconds,
                           Orthanc::IDynamicObject* payload /* takes ownership */,
-                          NativeStoneApplicationContext& context);
+                          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	Tue Oct 02 10:15:36 2018 +0200
@@ -13,7 +13,7 @@
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Affero General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
@@ -25,14 +25,17 @@
 
 namespace OrthancStone
 {
+
   WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker,
-                                             IWebService::ICallback& callback,
+                                             MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                                             MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                                              const Orthanc::WebServiceParameters& parameters,
                                              const std::string& uri,
                                              const IWebService::Headers& headers,
+                                             unsigned int timeoutInSeconds,
                                              Orthanc::IDynamicObject* payload /* takes ownership */,
                                              NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context)
+    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, timeoutInSeconds, payload, context)
   {
   }
 
@@ -40,7 +43,7 @@
   void WebServiceGetCommand::Execute()
   {
     Orthanc::HttpClient client(parameters_, uri_);
-    client.SetTimeout(60);
+    client.SetTimeout(timeoutInSeconds_);
     client.SetMethod(Orthanc::HttpMethod_Get);
 
     for (IWebService::Headers::const_iterator it = headers_.begin(); it != headers_.end(); it++ )
--- a/Platforms/Generic/WebServiceGetCommand.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServiceGetCommand.h	Tue Oct 02 10:15:36 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/>.
  **/
@@ -29,13 +29,16 @@
   {
   public:
     WebServiceGetCommand(MessageBroker& broker,
-                         IWebService::ICallback& callback,
+                         MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                         MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                          const Orthanc::WebServiceParameters& parameters,
                          const std::string& uri,
                          const IWebService::Headers& headers,
+                         unsigned int timeoutInSeconds,
                          Orthanc::IDynamicObject* payload /* takes ownership */,
                          NativeStoneApplicationContext& context);
 
     virtual void Execute();
   };
+
 }
--- a/Platforms/Generic/WebServicePostCommand.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -26,14 +26,16 @@
 namespace OrthancStone
 {
   WebServicePostCommand::WebServicePostCommand(MessageBroker& broker,
-                                               IWebService::ICallback& callback,
+                                               MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                                               MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                                                const Orthanc::WebServiceParameters& parameters,
                                                const std::string& uri,
                                                const IWebService::Headers& headers,
+                                               unsigned int timeoutInSeconds,
                                                const std::string& body,
                                                Orthanc::IDynamicObject* payload /* takes ownership */,
                                                NativeStoneApplicationContext& context) :
-    WebServiceCommandBase(broker, callback, parameters, uri, headers, payload, context),
+    WebServiceCommandBase(broker, successCallback, failureCallback, parameters, uri, headers, timeoutInSeconds, payload, context),
     body_(body)
   {
   }
@@ -41,7 +43,7 @@
   void WebServicePostCommand::Execute()
   {
     Orthanc::HttpClient client(parameters_, uri_);
-    client.SetTimeout(60);
+    client.SetTimeout(timeoutInSeconds_);
     client.SetMethod(Orthanc::HttpMethod_Post);
     client.GetBody().swap(body_);
 
--- a/Platforms/Generic/WebServicePostCommand.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Generic/WebServicePostCommand.h	Tue Oct 02 10:15:36 2018 +0200
@@ -32,10 +32,12 @@
 
   public:
     WebServicePostCommand(MessageBroker& broker,
-                          IWebService::ICallback& callback,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,  // takes ownership
                           const Orthanc::WebServiceParameters& parameters,
                           const std::string& uri,
                           const IWebService::Headers& headers,
+                          unsigned int timeoutInSeconds,
                           const std::string& body,
                           Orthanc::IDynamicObject* payload /* takes ownership */,
                           NativeStoneApplicationContext& context);
--- a/Platforms/Wasm/Defaults.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/Defaults.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -7,7 +7,7 @@
 #include <Framework/Widgets/LayerWidget.h>
 #include <algorithm>
 #include "Applications/Wasm/StartupParametersBuilder.h"
-#include "Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h"
+#include "Platforms/Wasm/WasmPlatformApplicationAdapter.h"
 
 static unsigned int width_ = 0;
 static unsigned int height_ = 0;
@@ -15,7 +15,7 @@
 /**********************************/
 
 static std::unique_ptr<OrthancStone::IStoneApplication> application;
-static OrthancStone::IStoneApplicationToWebApplicationAdapter* applicationWebAdapter = NULL;
+static std::unique_ptr<OrthancStone::WasmPlatformApplicationAdapter> applicationWasmAdapter = NULL;
 static std::unique_ptr<OrthancStone::StoneApplicationContext> context;
 static OrthancStone::StartupParametersBuilder startupParametersBuilder;
 static OrthancStone::MessageBroker broker;
@@ -69,7 +69,7 @@
     printf("CreateWasmApplication\n");
 
     application.reset(CreateUserApplication(broker));
-    applicationWebAdapter = dynamic_cast<OrthancStone::IStoneApplicationToWebApplicationAdapter*>(application.get()); 
+    applicationWasmAdapter.reset(CreateWasmApplicationAdapter(broker, application.get())); 
     WasmWebService::SetBroker(broker);
 
     startupParametersBuilder.Clear();
@@ -261,12 +261,16 @@
   {
     static std::string output; // we don't want the string to be deallocated when we return to JS code so we always use the same string (this is fine since JS is single-thread)
 
-    if (applicationWebAdapter != NULL) {
-      printf("sending message to C++");
-      applicationWebAdapter->HandleMessageFromWeb(output, std::string(message));
+    printf("SendMessageToStoneApplication\n");
+    printf("%s", message);
+
+    if (applicationWasmAdapter.get() != NULL) {
+      printf("sending message to C++\n");
+      applicationWasmAdapter->HandleMessageFromWeb(output, std::string(message));
       return output.c_str();
     }
-    return "This stone application does not have a Web Adapter";
+    printf("This stone application does not have a Web Adapter");
+    return NULL;
   }
 
 
--- a/Platforms/Wasm/Defaults.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/Defaults.h	Tue Oct 02 10:15:36 2018 +0200
@@ -7,6 +7,7 @@
 #include <Framework/Widgets/LayerWidget.h>
 #include <Framework/Widgets/LayoutWidget.h>
 #include <Applications/IStoneApplication.h>
+#include <Platforms/Wasm/WasmPlatformApplicationAdapter.h>
 
 typedef OrthancStone::WidgetViewport* ViewportHandle; // the objects exchanged between JS and C++
 
@@ -27,7 +28,9 @@
 }
 #endif
 
+// these methods must be implemented in the custom app "mainWasm.cpp"
 extern OrthancStone::IStoneApplication* CreateUserApplication(OrthancStone::MessageBroker& broker);
+extern OrthancStone::WasmPlatformApplicationAdapter* CreateWasmApplicationAdapter(OrthancStone::MessageBroker& broker, OrthancStone::IStoneApplication* application);
 
 namespace OrthancStone {
 
--- a/Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h	Mon Sep 10 12:22:26 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#pragma once
-
-#include <string>
-
-namespace OrthancStone
-{
-  class IStoneApplicationToWebApplicationAdapter
-  {
-    public:
-      virtual void HandleMessageFromWeb(std::string& output, const std::string& input) = 0;
-      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) = 0;
-  };
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,42 @@
+#include "WasmPlatformApplicationAdapter.h"
+
+#include "Framework/Toolbox/MessagingToolbox.h"
+#include "Framework/StoneException.h"
+#include <Applications/Commands/BaseCommandBuilder.h>
+#include <stdio.h>
+#include "Platforms/Wasm/Defaults.h"
+
+namespace OrthancStone
+{
+    WasmPlatformApplicationAdapter::WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application)
+    : IObserver(broker),
+      application_(application)
+    {
+    }
+
+    void WasmPlatformApplicationAdapter::HandleMessageFromWeb(std::string& output, const std::string& input)
+    {
+      try
+      {
+        Json::Value inputJson;
+        if (MessagingToolbox::ParseJson(inputJson, input.c_str(), input.size()))
+        {
+            std::unique_ptr<ICommand> command(application_.GetCommandBuilder().CreateFromJson(inputJson));
+            application_.ExecuteCommand(*command);
+        }
+      }
+      catch (StoneException& exc)
+      {
+        printf("Error while handling message from web (error code = %d):\n", exc.GetErrorCode());
+        printf("While interpreting input: '%s'\n", input.c_str());
+      }
+    }
+
+    void WasmPlatformApplicationAdapter::NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage)
+    {
+      printf("NotifyStatusUpdateFromCppToWeb (TODO)\n");
+      UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
+      printf("NotifyStatusUpdateFromCppToWeb (DONE)\n");
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Wasm/WasmPlatformApplicationAdapter.h	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <string>
+#include <Framework/Messages/IObserver.h>
+#include <Applications/IStoneApplication.h>
+
+namespace OrthancStone
+{
+  class WasmPlatformApplicationAdapter : public IObserver
+  {
+      IStoneApplication&  application_;
+    public:
+      WasmPlatformApplicationAdapter(MessageBroker& broker, IStoneApplication& application);
+
+      virtual void HandleMessageFromWeb(std::string& output, const std::string& input);
+      virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage);
+  };
+}
\ No newline at end of file
--- a/Platforms/Wasm/WasmWebService.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -7,47 +7,52 @@
 extern "C" {
 #endif
 
-  extern void WasmWebService_ScheduleGetRequest(void* callback,
-                                                const char* uri,
-                                                const char* headersInJsonString,
-                                                void* payload);
-  
-  extern void WasmWebService_SchedulePostRequest(void* callback,
-                                                 const char* uri,
-                                                 const char* headersInJsonString,
-                                                 const void* body,
-                                                 size_t bodySize,
-                                                 void* payload);
+  extern void WasmWebService_GetAsync(void* callableSuccess,
+                                      void* callableFailure,
+                                      const char* uri,
+                                      const char* headersInJsonString,
+                                      void* payload,
+                                      unsigned int timeoutInSeconds);
 
-  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* callback,
+  extern void WasmWebService_PostAsync(void* callableSuccess,
+                                       void* callableFailure,
+                                       const char* uri,
+                                       const char* headersInJsonString,
+                                       const void* body,
+                                       size_t bodySize,
+                                       void* payload,
+                                       unsigned int timeoutInSeconds);
+
+
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifyError(void* failureCallable,
                                                        const char* uri,
                                                        void* payload)
   {
-    if (callback == NULL)
+    if (failureCallable == NULL)
     {
       throw;
     }
     else
     {
-      reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)->
-        OnHttpRequestError(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload));
+      reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::HttpRequestErrorMessage>*>(failureCallable)->
+        Apply(OrthancStone::IWebService::HttpRequestErrorMessage(uri, reinterpret_cast<Orthanc::IDynamicObject*>(payload)));
     }
   }
 
-  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* callback,
+  void EMSCRIPTEN_KEEPALIVE WasmWebService_NotifySuccess(void* successCallable,
                                                          const char* uri,
                                                          const void* body,
                                                          size_t bodySize,
                                                          void* payload)
   {
-    if (callback == NULL)
+    if (successCallable == NULL)
     {
       throw;
     }
     else
     {
-      reinterpret_cast<OrthancStone::IWebService::ICallback*>(callback)->
-        OnHttpRequestSuccess(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)); 
+      reinterpret_cast<OrthancStone::MessageHandler<OrthancStone::IWebService::HttpRequestSuccessMessage>*>(successCallable)->
+        Apply(OrthancStone::IWebService::HttpRequestSuccessMessage(uri, body, bodySize, reinterpret_cast<Orthanc::IDynamicObject*>(payload)));
    }
   }
 
@@ -96,27 +101,32 @@
     output = outputStr.str();
   }
 
-  void WasmWebService::ScheduleGetRequest(ICallback& callback,
-                                          const std::string& relativeUri,
-                                          const Headers& headers,
-                                          Orthanc::IDynamicObject* payload)
+  void WasmWebService::PostAsync(const std::string& relativeUri,
+                                 const Headers& headers,
+                                 const std::string& body,
+                                 Orthanc::IDynamicObject* payload,
+                                 MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
+                                 MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable,
+                                 unsigned int timeoutInSeconds)
   {
     std::string uri = baseUri_ + relativeUri;
     std::string headersInJsonString;
     ToJsonString(headersInJsonString, headers);
-    WasmWebService_ScheduleGetRequest(&callback, uri.c_str(), headersInJsonString.c_str(), payload);
+    WasmWebService_PostAsync(successCallable, failureCallable, uri.c_str(), headersInJsonString.c_str(),
+                                       body.c_str(), body.size(), payload, timeoutInSeconds);
   }
 
-  void WasmWebService::SchedulePostRequest(ICallback& callback,
-                                           const std::string& relativeUri,
-                                           const Headers& headers,
-                                           const std::string& body,
-                                           Orthanc::IDynamicObject* payload)
+   void WasmWebService::GetAsync(const std::string& relativeUri,
+                                 const Headers& headers,
+                                 Orthanc::IDynamicObject* payload,
+                                 MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
+                                 MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable,
+                                 unsigned int timeoutInSeconds)
   {
     std::string uri = baseUri_ + relativeUri;
     std::string headersInJsonString;
     ToJsonString(headersInJsonString, headers);
-    WasmWebService_SchedulePostRequest(&callback, uri.c_str(), headersInJsonString.c_str(),
-                                       body.c_str(), body.size(), payload);
+    WasmWebService_GetAsync(successCallable, failureCallable, uri.c_str(), headersInJsonString.c_str(), payload, timeoutInSeconds);
   }
+
 }
--- a/Platforms/Wasm/WasmWebService.h	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.h	Tue Oct 02 10:15:36 2018 +0200
@@ -37,16 +37,20 @@
 
     void SetBaseUri(const std::string baseUri);
 
-    virtual void ScheduleGetRequest(ICallback& callback,
-                                    const std::string& uri,
-                                    const Headers& headers,
-                                    Orthanc::IDynamicObject* payload);
+    virtual void GetAsync(const std::string& uri,
+                          const Headers& headers,
+                          Orthanc::IDynamicObject* payload,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
+                          unsigned int timeoutInSeconds = 60);
 
-    virtual void SchedulePostRequest(ICallback& callback,
-                                     const std::string& uri,
-                                     const Headers& headers,
-                                     const std::string& body,
-                                     Orthanc::IDynamicObject* payload);
+    virtual void PostAsync(const std::string& uri,
+                           const Headers& headers,
+                           const std::string& body,
+                           Orthanc::IDynamicObject* payload,
+                           MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
+                           MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
+                           unsigned int timeoutInSeconds = 60);
 
     virtual void Start()
     {
--- a/Platforms/Wasm/WasmWebService.js	Mon Sep 10 12:22:26 2018 +0200
+++ b/Platforms/Wasm/WasmWebService.js	Tue Oct 02 10:15:36 2018 +0200
@@ -1,5 +1,5 @@
 mergeInto(LibraryManager.library, {
-  WasmWebService_ScheduleGetRequest: function(callback, url, headersInJsonString, payload) {
+  WasmWebService_GetAsync: function(callableSuccess, callableFailure, url, headersInJsonString, payload, timeoutInSeconds) {
     // 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();
@@ -8,21 +8,22 @@
 
     xhr.open('GET', url_, true);
     xhr.responseType = 'arraybuffer';
+    xhr.timeout = timeoutInSeconds * 1000;
     var headers = JSON.parse(headersInJsonString_);
     for (var key in headers) {
       xhr.setRequestHeader(key, headers[key]);
     }
-    // console.log(xhr); 
+    //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
-          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
+          WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
                                        this.response.byteLength, payload);
         } else {
-          WasmWebService_NotifyError(callback, url_, payload);
+          WasmWebService_NotifyError(callableFailure, url_, payload);
         }
       }
     }
@@ -30,11 +31,12 @@
     xhr.send();
   },
 
-  WasmWebService_SchedulePostRequest: function(callback, url, headersInJsonString, body, bodySize, payload) {
+  WasmWebService_PostAsync: function(callableSuccess, callableFailure, url, headersInJsonString, body, bodySize, payload, timeoutInSeconds) {
     var xhr = new XMLHttpRequest();
     var url_ = UTF8ToString(url);
     var headersInJsonString_ = UTF8ToString(headersInJsonString);
     xhr.open('POST', url_, true);
+    xhr.timeout = timeoutInSeconds * 1000;
     xhr.responseType = 'arraybuffer';
     xhr.setRequestHeader('Content-type', 'application/octet-stream');
 
@@ -46,10 +48,10 @@
     xhr.onreadystatechange = function() {
       if (this.readyState == XMLHttpRequest.DONE) {
         if (xhr.status === 200) {
-          WasmWebService_NotifySuccess(callback, url_, new Uint8Array(this.response),
+          WasmWebService_NotifySuccess(callableSuccess, url_, new Uint8Array(this.response),
                                        this.response.byteLength, payload);
         } else {
-          WasmWebService_NotifyError(callback, url_, payload);
+          WasmWebService_NotifyError(callableFailure, url_, payload);
         }
       }
     }
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Sep 10 12:22:26 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Oct 02 10:15:36 2018 +0200
@@ -164,10 +164,10 @@
 set(APPLICATIONS_SOURCES
     ${ORTHANC_STONE_ROOT}/Applications/IStoneApplication.h
     ${ORTHANC_STONE_ROOT}/Applications/StoneApplicationContext.cpp
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/BaseCommandFactory.cpp
+    ${ORTHANC_STONE_ROOT}/Applications/Commands/BaseCommandBuilder.cpp
     ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommand.h
     ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandExecutor.h
-    ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandFactory.h
+    ${ORTHANC_STONE_ROOT}/Applications/Commands/ICommandBuilder.h
     )
 
 if (NOT ORTHANC_SANDBOXED)
@@ -203,7 +203,7 @@
     ${ORTHANC_STONE_ROOT}/Platforms/Wasm/Defaults.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmWebService.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmViewport.cpp
-    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h
+    ${ORTHANC_STONE_ROOT}/Platforms/Wasm/WasmPlatformApplicationAdapter.cpp
     ${AUTOGENERATED_DIR}/WasmWebService.c
     ${AUTOGENERATED_DIR}/default-library.c
   )
@@ -280,11 +280,14 @@
   ${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/MessageForwarder.cpp
   ${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/TestCommands.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/UnitTestsSources/TestCommands.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -1,108 +1,108 @@
-/**
- * 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 "../Applications/Commands/BaseCommandFactory.h"
-#include "Core/OrthancException.h"
+//#include "../Applications/Commands/BaseCommandFactory.h"
+//#include "Core/OrthancException.h"
 
-class CommandIncrement: public OrthancStone::BaseCommand<CommandIncrement>
-{
-public:
-  static int counter;
-  int increment_;
-public:
-  CommandIncrement()
-    : OrthancStone::BaseCommand<CommandIncrement>("increment"),
-      increment_(0)
-  {}
+//class CommandIncrement: public OrthancStone::BaseCommand<CommandIncrement>
+//{
+//public:
+//  static int counter;
+//  int increment_;
+//public:
+//  CommandIncrement()
+//    : OrthancStone::BaseCommand<CommandIncrement>("increment"),
+//      increment_(0)
+//  {}
 
-  virtual void Execute()
-  {
-    counter += increment_;
-  }
-  virtual void Configure(const Json::Value& arguments)
-  {
-    increment_ = arguments["increment"].asInt();
-  }
-};
+//  virtual void Execute()
+//  {
+//    counter += increment_;
+//  }
+//  virtual void Configure(const Json::Value& arguments)
+//  {
+//    increment_ = arguments["increment"].asInt();
+//  }
+//};
 
-// COMMAND("name", "arg1", "int", "arg2", "string")
-// COMMAND(name, arg1, arg2)
+//// COMMAND("name", "arg1", "int", "arg2", "string")
+//// COMMAND(name, arg1, arg2)
 
 
-int CommandIncrement::counter = 0;
+//int CommandIncrement::counter = 0;
 
-TEST(Commands, CreateNoop)
-{
-  OrthancStone::BaseCommandFactory factory;
+//TEST(Commands, CreateNoop)
+//{
+//  OrthancStone::BaseCommandFactory factory;
 
-  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
+//  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
 
-  Json::Value cmdJson;
-  cmdJson["command"] = "noop";
+//  Json::Value cmdJson;
+//  cmdJson["command"] = "noop";
 
-  std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson));
+//  std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson));
 
-  ASSERT_TRUE(command.get() != NULL);
-  ASSERT_EQ("noop", command->GetName());
-}
+//  ASSERT_TRUE(command.get() != NULL);
+//  ASSERT_EQ("noop", command->GetName());
+//}
 
-TEST(Commands, Execute)
-{
-  OrthancStone::BaseCommandFactory factory;
+//TEST(Commands, Execute)
+//{
+//  OrthancStone::BaseCommandFactory factory;
 
-  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
-  factory.RegisterCommandClass<CommandIncrement>();
+//  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
+//  factory.RegisterCommandClass<CommandIncrement>();
 
-  Json::Value cmdJson;
-  cmdJson["command"] = "increment";
-  cmdJson["args"]["increment"] = 2;
+//  Json::Value cmdJson;
+//  cmdJson["command"] = "increment";
+//  cmdJson["args"]["increment"] = 2;
 
-  std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson));
+//  std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson));
 
-  ASSERT_TRUE(command.get() != NULL);
-  CommandIncrement::counter = 0;
-  command->Execute();
-  ASSERT_EQ(2, CommandIncrement::counter);
-}
+//  ASSERT_TRUE(command.get() != NULL);
+//  CommandIncrement::counter = 0;
+//  command->Execute();
+//  ASSERT_EQ(2, CommandIncrement::counter);
+//}
 
-TEST(Commands, TryCreateUnknowCommand)
-{
-  OrthancStone::BaseCommandFactory factory;
-  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
+//TEST(Commands, TryCreateUnknowCommand)
+//{
+//  OrthancStone::BaseCommandFactory factory;
+//  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
 
-  Json::Value cmdJson;
-  cmdJson["command"] = "unknown";
+//  Json::Value cmdJson;
+//  cmdJson["command"] = "unknown";
 
-  ASSERT_THROW(std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException);
-}
+//  ASSERT_THROW(std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException);
+//}
 
-TEST(Commands, TryCreateCommandFromInvalidJson)
-{
-  OrthancStone::BaseCommandFactory factory;
-  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
+//TEST(Commands, TryCreateCommandFromInvalidJson)
+//{
+//  OrthancStone::BaseCommandFactory factory;
+//  factory.RegisterCommandClass<OrthancStone::NoopCommand>();
 
-  Json::Value cmdJson;
-  cmdJson["command-name"] = "noop";
+//  Json::Value cmdJson;
+//  cmdJson["command-name"] = "noop";
 
-  ASSERT_THROW(std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException);
-}
+//  ASSERT_THROW(std::auto_ptr<OrthancStone::ICommand> command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException);
+//}
--- a/UnitTestsSources/TestMessageBroker.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/UnitTestsSources/TestMessageBroker.cpp	Tue Oct 02 10:15:36 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	Tue Oct 02 10:15:36 2018 +0200
@@ -0,0 +1,564 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "gtest/gtest.h"
+
+#include "Framework/Messages/MessageBroker.h"
+#include "Framework/Messages/Promise.h"
+#include "Framework/Messages/IObservable.h"
+#include "Framework/Messages/IObserver.h"
+#include "Framework/Messages/MessageForwarder.h"
+
+
+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);
+//          }
+//        }
+//      }
+//    }
+
+//  };
+  using namespace OrthancStone;
+
+
+  enum CustomMessageType
+  {
+    CustomMessageType_First = MessageType_CustomMessage + 1,
+
+    CustomMessageType_Completed,
+    CustomMessageType_Increment
+  };
+
+
+  class MyObservable : public IObservable
+  {
+  public:
+    struct MyCustomMessage: public BaseMessage<CustomMessageType_Completed>
+    {
+      int payload_;
+
+      MyCustomMessage(int payload)
+        : BaseMessage(),
+          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 MyIntermediate : public IObserver, public IObservable
+  {
+    IObservable& observedObject_;
+  public:
+    MyIntermediate(MessageBroker& broker, IObservable& observedObject)
+      : IObserver(broker),
+        IObservable(broker),
+        observedObject_(observedObject)
+    {
+      observedObject_.RegisterObserverCallback(new MessageForwarder<MyObservable::MyCustomMessage>(broker, *this));
+    }
+  };
+
+
+  class MyPromiseSource : public IObservable
+  {
+    Promise* currentPromise_;
+  public:
+    struct MyPromiseMessage: public BaseMessage<MessageType_Test1>
+    {
+      int increment;
+
+      MyPromiseMessage(int increment)
+        : BaseMessage(),
+          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.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+TEST(MessageBroker2, TestPermanentConnectionDeleteObserver)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyObserver*   observer = new MyObserver(broker);
+
+  // create a permanent connection between an observable and an observer
+  observable.RegisterObserverCallback(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, TestMessageForwarderSimpleUseCase)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+
+TEST(MessageBroker2, TestMessageForwarderDeleteIntermediate)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate* intermediate = new MyIntermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  delete intermediate;
+
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(12, testCounter);
+}
+
+TEST(MessageBroker2, TestCustomMessage)
+{
+  MessageBroker broker;
+  MyObservable  observable(broker);
+  MyIntermediate intermediate(broker, observable);
+  MyObserver    observer(broker);
+
+  // let the observer observers the intermediate that is actually forwarding the messages from the observable
+  intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage));
+
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(12));
+  ASSERT_EQ(12, testCounter);
+
+  // the connection is permanent; if we emit the same message again, the observer will be notified again
+  testCounter = 0;
+  observable.EmitMessage(MyObservable::MyCustomMessage(20));
+  ASSERT_EQ(20, testCounter);
+}
+
+
+TEST(MessageBroker2, TestPromiseSuccessFailure)
+{
+  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);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/TestMessageBroker2_connect_ok.cpp	Tue Oct 02 10:15:36 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	Tue Oct 02 10:15:36 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);
+//}
--- a/UnitTestsSources/UnitTestsMain.cpp	Mon Sep 10 12:22:26 2018 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Oct 02 10:15:36 2018 +0200
@@ -26,6 +26,7 @@
 #include "../Framework/Layers/LayerSourceBase.h"
 #include "../Framework/Toolbox/DownloadStack.h"
 #include "../Framework/Toolbox/FiniteProjectiveCamera.h"
+#include "../Framework/Toolbox/MessagingToolbox.h"
 #include "../Framework/Toolbox/OrthancSlicesLoader.h"
 #include "../Framework/Volumes/ImageBuffer3D.h"
 #include "../Framework/Volumes/SlicedVolumeBase.h"
@@ -724,6 +725,12 @@
   */
 }
 
+TEST(MessagingToolbox, ParseJson)
+{
+  Json::Value response;
+  std::string source = "{\"command\":\"panel:takeDarkImage\",\"commandType\":\"simple\",\"args\":{}}";
+  ASSERT_TRUE(OrthancStone::MessagingToolbox::ParseJson(response, source.c_str(), source.size()));
+}
 
 int main(int argc, char **argv)
 {