changeset 267:89d02de83c03 am-2

added declaretion of messages handled/emitted
author am@osimis.io
date Wed, 22 Aug 2018 14:59:20 +0200
parents c9cf95b49a86
children 5bd4161bf11b
files Applications/Samples/SimpleViewerApplication.h Framework/Layers/ILayerSource.h Framework/Layers/LayerSourceBase.cpp Framework/Layers/LayerSourceBase.h Framework/Layers/OrthancFrameLayerSource.cpp Framework/Messages/IObservable.h Framework/Messages/IObserver.h Framework/Messages/MessageBroker.cpp Framework/Messages/MessageType.h Framework/SmartLoader.cpp Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancSlicesLoader.cpp Framework/Toolbox/OrthancSlicesLoader.h Framework/Widgets/LayerWidget.cpp Framework/Widgets/LayerWidget.h Framework/dev.h Platforms/Generic/WebServiceCommandBase.cpp Resources/CMake/OrthancStoneConfiguration.cmake UnitTestsSources/TestMessageBroker.cpp
diffstat 19 files changed, 284 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SimpleViewerApplication.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Applications/Samples/SimpleViewerApplication.h	Wed Aug 22 14:59:20 2018 +0200
@@ -177,8 +177,8 @@
 
       virtual void HandleMessage(const IObservable& from, const IMessage& message) {
         switch (message.GetType()) {
-        case MessageType_GeometryReady:
-          mainLayout_->SetDefaultView();
+        case MessageType_Widget_GeometryChanged:
+          //TODO remove constness !! dynamic_cast<const LayerWidget&>(from).SetDefaultView();
           break;
         default:
           VLOG("unhandled message type" << message.GetType());
@@ -208,6 +208,8 @@
         wasmViewport2_(NULL),
         slice_(0)
       {
+        DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
+        DeclareHandledMessage(MessageType_Widget_GeometryChanged);
       }
 
       virtual void Finalize() {}
@@ -267,6 +269,9 @@
         mainViewport_ = new LayerWidget(broker_);
         thumbnails_.push_back(new LayerWidget(broker_));
         thumbnails_.push_back(new LayerWidget(broker_));
+        mainViewport_->RegisterObserver(*this);
+        thumbnails_[0]->RegisterObserver(*this);
+        thumbnails_[1]->RegisterObserver(*this);
 
         // hierarchy
         mainLayout_->AddWidget(thumbnailsLayout_);
@@ -277,7 +282,6 @@
         // sources
         smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService()));
         smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
-        smartLoader_->RegisterObserver(*this);
 
         mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0));
         thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0));
--- a/Framework/Layers/ILayerSource.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Layers/ILayerSource.h	Wed Aug 22 14:59:20 2018 +0200
@@ -35,7 +35,7 @@
     {
       const Slice& slice_;
       SliceChangedMessage(const Slice& slice)
-        : IMessage(MessageType_SliceChanged),
+        : IMessage(MessageType_LayerSource_SliceChanged),
           slice_(slice)
       {
       }
@@ -50,7 +50,7 @@
       LayerReadyMessage(std::auto_ptr<ILayerRenderer>& layer,
                         const CoordinateSystem3D& slice,
                         bool isError)  // TODO Shouldn't this be separate as NotifyLayerError?
-        : IMessage(MessageType_LayerReady),
+        : IMessage(MessageType_LayerSource_LayerReady),
           layer_(layer),
           slice_(slice),
           isError_(isError)
--- a/Framework/Layers/LayerSourceBase.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -27,17 +27,17 @@
 {
   void LayerSourceBase::NotifyGeometryReady()
   {
-    EmitMessage(IMessage(MessageType_GeometryReady));
+    EmitMessage(IMessage(MessageType_LayerSource_GeometryReady));
   }
     
   void LayerSourceBase::NotifyGeometryError()
   {
-    EmitMessage(IMessage(MessageType_GeometryError));
+    EmitMessage(IMessage(MessageType_LayerSource_GeometryError));
   }
     
   void LayerSourceBase::NotifyContentChange()
   {
-    EmitMessage(IMessage(MessageType_ContentChanged));
+    EmitMessage(IMessage(MessageType_LayerSource_ContentChanged));
   }
 
   void LayerSourceBase::NotifySliceChange(const Slice& slice)
--- a/Framework/Layers/LayerSourceBase.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Layers/LayerSourceBase.h	Wed Aug 22 14:59:20 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/>.
  **/
@@ -43,7 +43,13 @@
 
     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);
+    }
 
   };
 }
--- a/Framework/Layers/OrthancFrameLayerSource.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Layers/OrthancFrameLayerSource.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -35,7 +35,7 @@
   {
     switch (message.GetType())
     {
-    case MessageType_SliceGeometryReady:
+    case MessageType_SliceLoader_GeometryReady:
     {
       const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
       if (loader.GetSliceCount() > 0)
@@ -48,12 +48,12 @@
       }
 
     }; break;
-    case MessageType_SliceGeometryError:
+    case MessageType_SliceLoader_GeometryError:
     {
       const OrthancSlicesLoader& loader = dynamic_cast<const OrthancSlicesLoader&>(from);
       LayerSourceBase::NotifyGeometryError();
     }; break;
-    case MessageType_SliceImageReady:
+    case MessageType_SliceLoader_ImageReady:
     {
       const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
       bool isFull = (msg.effectiveQuality_ == SliceImageQuality_FullPng || msg.effectiveQuality_ == SliceImageQuality_FullPam);
@@ -61,7 +61,7 @@
                                         msg.slice_.GetGeometry(), false);
 
     }; break;
-    case MessageType_SliceImageError:
+    case MessageType_SliceLoader_ImageError:
     {
       const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
       LayerSourceBase::NotifyLayerReady(NULL, msg.slice_.GetGeometry(), true);
@@ -79,6 +79,10 @@
     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);
   }
 
--- a/Framework/Messages/IObservable.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Messages/IObservable.h	Wed Aug 22 14:59:20 2018 +0200
@@ -21,17 +21,35 @@
 
 #pragma once
 
+#include <set>
+#include <assert.h>
+#include <algorithm>
+#include <iostream>
+
 #include "MessageBroker.h"
-#include <set>
+#include "MessageType.h"
+#include "IObserver.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_;
 
   public:
 
@@ -45,11 +63,17 @@
 
     void EmitMessage(const IMessage& message) const
     {
+      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);
     }
 
@@ -57,6 +81,31 @@
     {
       observers_.erase(&observer);
     }
+
+    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++)
+      {
+        // the observer must have "declared" all observable messages
+        if (observer.GetHandledMessages().find(*it) == observer.GetHandledMessages().end()
+            && observer.GetIgnoredMessages().find(*it) == observer.GetIgnoredMessages().end())
+        {
+          throw MessageNotDeclaredException(*it);
+        }
+      }
+    }
   };
 
 }
--- a/Framework/Messages/IObserver.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Messages/IObserver.h	Wed Aug 22 14:59:20 2018 +0200
@@ -23,7 +23,8 @@
 
 #include "MessageBroker.h"
 #include "IMessage.h"
-#include "IObservable.h"
+#include <set>
+#include <assert.h>
 
 namespace OrthancStone {
 
@@ -33,6 +34,8 @@
   {
   protected:
     MessageBroker&                    broker_;
+    std::set<MessageType>             handledMessages_;
+    std::set<MessageType>             ignoredMessages_;
 
   public:
     IObserver(MessageBroker& broker)
@@ -46,7 +49,40 @@
       broker_.Unregister(*this);
     }
 
+    void HandleMessage_(const 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(const 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	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Messages/MessageBroker.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -42,7 +42,14 @@
 
     for (std::vector<IObserver*>::iterator observer = activeObservers.begin(); observer != activeObservers.end(); observer++)
     {
-      (*observer)->HandleMessage(from, message);
+      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/MessageType.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Messages/MessageType.h	Wed Aug 22 14:59:20 2018 +0200
@@ -24,21 +24,26 @@
 
   enum MessageType
   {
-    MessageType_Generic,
+    MessageType_Widget_GeometryChanged,
+    MessageType_Widget_ContentChanged,
 
-    MessageType_GeometryReady,
-    MessageType_GeometryError,
-    MessageType_ContentChanged,
-    MessageType_SliceChanged,
-    MessageType_LayerReady,
+    MessageType_LayerSource_GeometryReady,
+    MessageType_LayerSource_GeometryError,
+    MessageType_LayerSource_ContentChanged,
+    MessageType_LayerSource_SliceChanged,
+    MessageType_LayerSource_LayerReady,
 
-    MessageType_SliceGeometryReady,
-    MessageType_SliceGeometryError,
-    MessageType_SliceImageReady,
-    MessageType_SliceImageError,
+    MessageType_SliceLoader_GeometryReady,
+    MessageType_SliceLoader_GeometryError,
+    MessageType_SliceLoader_ImageReady,
+    MessageType_SliceLoader_ImageError,
 
     MessageType_HttpRequestSuccess,
-    MessageType_HttpRequestError
+    MessageType_HttpRequestError,
+
 
+    // used in unit tests only
+    MessageType_Test1,
+    MessageType_Test2
   };
 }
--- a/Framework/SmartLoader.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/SmartLoader.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -30,17 +30,23 @@
     IObserver(broker),
     imageQuality_(SliceImageQuality_FullPam),
     webService_(webService)
-  {}
+  {
+    DeclareHandledMessage(MessageType_LayerSource_GeometryReady);
+    DeclareHandledMessage(MessageType_LayerSource_LayerReady);
+    DeclareIgnoredMessage(MessageType_LayerSource_GeometryError);
+    DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged);
+    DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged);
+  }
 
   void SmartLoader::HandleMessage(const IObservable& from, const IMessage& message)
   {
     switch (message.GetType()) {
-    case MessageType_SliceGeometryReady:
+    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_SliceImageReady:
+    case MessageType_LayerSource_LayerReady:
     {
       const OrthancFrameLayerSource* layerSource=dynamic_cast<const OrthancFrameLayerSource*>(&from);
       // TODO keep track of objects that have been loaded already
--- a/Framework/Toolbox/IWebService.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Toolbox/IWebService.h	Wed Aug 22 14:59:20 2018 +0200
@@ -70,7 +70,10 @@
 
             ICallback(MessageBroker& broker)
                 : IObserver(broker)
-            {}
+            {
+                DeclareHandledMessage(MessageType_HttpRequestError);
+                DeclareHandledMessage(MessageType_HttpRequestSuccess);
+            }
             virtual ~ICallback()
             {
             }
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -253,7 +253,7 @@
       {
       case Mode_FrameGeometry:
       case Mode_SeriesGeometry:
-        that_.EmitMessage(IMessage(MessageType_SliceGeometryError));
+        that_.EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
         that_.state_ = State_Error;
         break;
         
@@ -321,12 +321,12 @@
     if (ok)
     {
       LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)";
-      EmitMessage(IMessage(MessageType_SliceGeometryReady));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
     }
     else
     {
       LOG(ERROR) << "This series is empty";
-      EmitMessage(IMessage(MessageType_SliceGeometryError));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
     }
   }
   
@@ -338,7 +338,7 @@
     if (!MessagingToolbox::ParseJson(series, answer, size) ||
         series.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceGeometryError));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       return;
     }
     
@@ -385,7 +385,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceGeometryError));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       return;
     }
     
@@ -412,7 +412,7 @@
       else
       {
         LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
-        EmitMessage(IMessage(MessageType_SliceGeometryError));
+        EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
         return;
       }
     }
@@ -430,7 +430,7 @@
     if (!MessagingToolbox::ParseJson(tags, answer, size) ||
         tags.type() != Json::objectValue)
     {
-      EmitMessage(IMessage(MessageType_SliceGeometryError));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
       return;
     }
     
@@ -446,12 +446,12 @@
     {
       LOG(INFO) << "Loaded instance geometry " << instanceId;
       slices_.AddSlice(slice.release());
-      EmitMessage(IMessage(MessageType_SliceGeometryReady));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryReady));
     }
     else
     {
       LOG(WARNING) << "Skipping invalid instance " << instanceId;
-      EmitMessage(IMessage(MessageType_SliceGeometryError));
+      EmitMessage(IMessage(MessageType_SliceLoader_GeometryError));
     }
   }
   
@@ -784,6 +784,10 @@
     orthanc_(orthanc),
     state_(State_Initialization)
   {
+    DeclareEmittableMessage(MessageType_SliceLoader_GeometryReady);
+    DeclareEmittableMessage(MessageType_SliceLoader_GeometryError);
+    DeclareEmittableMessage(MessageType_SliceLoader_ImageError);
+    DeclareEmittableMessage(MessageType_SliceLoader_ImageReady);
   }
   
   
--- a/Framework/Toolbox/OrthancSlicesLoader.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Toolbox/OrthancSlicesLoader.h	Wed Aug 22 14:59:20 2018 +0200
@@ -24,7 +24,7 @@
 #include "IWebService.h"
 #include "SlicesSorter.h"
 #include "../StoneEnumerations.h"
-
+#include "../Messages/IObservable.h"
 #include <boost/shared_ptr.hpp>
 
 namespace OrthancStone
@@ -43,7 +43,7 @@
                         const Slice& slice,
                         std::auto_ptr<Orthanc::ImageAccessor>& image,
                         SliceImageQuality effectiveQuality)
-        : IMessage(MessageType_SliceImageReady),
+        : IMessage(MessageType_SliceLoader_ImageReady),
           sliceIndex_(sliceIndex),
           slice_(slice),
           image_(image),
@@ -61,7 +61,7 @@
       SliceImageErrorMessage(unsigned int sliceIndex,
                         const Slice& slice,
                         SliceImageQuality effectiveQuality)
-        : IMessage(MessageType_SliceImageError),
+        : IMessage(MessageType_SliceLoader_ImageError),
           slice_(slice),
           sliceIndex_(sliceIndex),
           effectiveQuality_(effectiveQuality)
--- a/Framework/Widgets/LayerWidget.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Widgets/LayerWidget.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -361,8 +361,18 @@
   
   LayerWidget::LayerWidget(MessageBroker& broker) :
     IObserver(broker),
+    IObservable(broker),
     started_(false)
   {
+    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);
+
     SetBackgroundCleared(true);
   }
   
@@ -483,19 +493,19 @@
   void LayerWidget::HandleMessage(const IObservable& from, const IMessage& message)
   {
     switch (message.GetType()) {
-    case MessageType_GeometryReady:
+    case MessageType_LayerSource_GeometryReady:
       OnGeometryReady(dynamic_cast<const ILayerSource&>(from));
       break;
-    case MessageType_GeometryError:
+    case MessageType_LayerSource_GeometryError:
       LOG(ERROR) << "Cannot get geometry";
       break;
-    case MessageType_ContentChanged:
+    case MessageType_LayerSource_ContentChanged:
       OnContentChanged(dynamic_cast<const ILayerSource&>(from));
       break;
-    case MessageType_SliceChanged:
+    case MessageType_LayerSource_SliceChanged:
       OnSliceChanged(dynamic_cast<const ILayerSource&>(from), dynamic_cast<const ILayerSource::SliceChangedMessage&>(message).slice_);
       break;
-    case MessageType_LayerReady:
+    case MessageType_LayerSource_LayerReady:
     {
       const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast<const ILayerSource::LayerReadyMessage&>(message);
       OnLayerReady(layerReadyMessage.layer_,
@@ -518,6 +528,7 @@
       changedLayers_[i] = true;
       //layers_[i]->ScheduleLayerCreation(slice_);
     }
+    EmitMessage(IMessage(MessageType_Widget_GeometryChanged));
   }
   
   void LayerWidget::InvalidateAllLayers()
--- a/Framework/Widgets/LayerWidget.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/Widgets/LayerWidget.h	Wed Aug 22 14:59:20 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/>.
  **/
@@ -31,8 +31,9 @@
 namespace OrthancStone
 {
   class LayerWidget :
-    public WorldSceneWidget,
-    public IObserver
+      public WorldSceneWidget,
+      public IObserver,
+      public IObservable
   {
   private:
     class Scene;
@@ -59,12 +60,12 @@
     virtual void OnContentChanged(const ILayerSource& source);
 
     virtual void OnSliceChanged(const ILayerSource& source,
-                                   const Slice& slice);
+                                const Slice& slice);
 
     virtual void OnLayerReady(std::auto_ptr<ILayerRenderer>& renderer,
-                                  const ILayerSource& source,
-                                  const CoordinateSystem3D& slice,
-                                  bool isError);
+                              const ILayerSource& source,
+                              const CoordinateSystem3D& slice,
+                              bool isError);
 
 
     void ResetChangedLayers();
@@ -75,7 +76,7 @@
     virtual void HandleMessage(const IObservable& from, const IMessage& message);
 
     virtual Extent2D GetSceneExtent();
- 
+
   protected:
     virtual bool RenderScene(CairoContext& context,
                              const ViewportGeometry& view);
--- a/Framework/dev.h	Tue Aug 21 18:14:22 2018 +0200
+++ b/Framework/dev.h	Wed Aug 22 14:59:20 2018 +0200
@@ -203,14 +203,14 @@
     {
       switch (message.GetType())
       {
-      case MessageType_SliceGeometryReady:
+      case MessageType_SliceLoader_GeometryReady:
         OnSliceGeometryReady(dynamic_cast<const OrthancSlicesLoader&>(from));
-      case MessageType_SliceGeometryError:
+      case MessageType_SliceLoader_GeometryError:
       {
         LOG(ERROR) << "Unable to download a volume image";
         SlicedVolumeBase::NotifyGeometryError();
       }; break;
-      case MessageType_SliceImageReady:
+      case MessageType_SliceLoader_ImageReady:
       {
         const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageReadyMessage&>(message);
         OnSliceImageReady(dynamic_cast<const OrthancSlicesLoader&>(from),
@@ -219,7 +219,7 @@
                           msg.image_,
                           msg.effectiveQuality_);
       }; break;
-      case MessageType_SliceImageError:
+      case MessageType_SliceLoader_ImageError:
       {
           const OrthancSlicesLoader::SliceImageErrorMessage& msg = dynamic_cast<const OrthancSlicesLoader::SliceImageErrorMessage&>(message);
           LOG(ERROR) << "Cannot download slice " << msg.sliceIndex_ << " in a volume image";
--- a/Platforms/Generic/WebServiceCommandBase.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/Platforms/Generic/WebServiceCommandBase.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -38,6 +38,8 @@
     headers_(headers),
     payload_(payload)
   {
+    DeclareEmittableMessage(MessageType_HttpRequestError);
+    DeclareEmittableMessage(MessageType_HttpRequestSuccess);
     RegisterObserver(callback);
   }
 
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Aug 21 18:14:22 2018 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Aug 22 14:59:20 2018 +0200
@@ -93,7 +93,9 @@
   -DORTHANC_ENABLE_LOGGING_PLUGIN=0
   )
 
-
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  add_definitions(-DCHECK_OBSERVERS_MESSAGES)
+endif()
 
 #####################################################################
 ## Embed the colormaps into the binaries
--- a/UnitTestsSources/TestMessageBroker.cpp	Tue Aug 21 18:14:22 2018 +0200
+++ b/UnitTestsSources/TestMessageBroker.cpp	Wed Aug 22 14:59:20 2018 +0200
@@ -28,21 +28,74 @@
 #include "../Framework/StoneEnumerations.h"
 
 
-static int globalCounter = 0;
-class MyObserver : 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);
+  }
+
+
+  void HandleMessage(const 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
 {
 
 public:
-  MyObserver(OrthancStone::MessageBroker& broker)
+  MyPartialObserver(OrthancStone::MessageBroker& broker)
     : OrthancStone::IObserver(broker)
-  {}
+  {
+    DeclareHandledMessage(OrthancStone::MessageType_Test1);
+    // don't declare Test2 on purpose
+  }
 
 
   void HandleMessage(const OrthancStone::IObservable& from, const OrthancStone::IMessage& message) {
-    if (message.GetType() == OrthancStone::MessageType_Generic) {
-      globalCounter++;
+    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
+{
+
+public:
+  MyObservable(OrthancStone::MessageBroker& broker)
+    : OrthancStone::IObservable(broker)
+  {
+    DeclareEmittableMessage(OrthancStone::MessageType_Test1);
+    DeclareEmittableMessage(OrthancStone::MessageType_Test2);
   }
 
 };
@@ -51,59 +104,55 @@
 TEST(MessageBroker, NormalUsage)
 {
   OrthancStone::MessageBroker broker;
-  OrthancStone::IObservable observable(broker);
+  MyObservable observable(broker);
 
-  globalCounter = 0;
-
-  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+  test1Counter = 0;
 
   // no observers have been registered -> nothing shall happen
-  observable.EmitMessage(genericMessage);
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(0, globalCounter);
+  ASSERT_EQ(0, test1Counter);
 
   // register an observer, check it is called
-  MyObserver observer(broker);
-  observable.RegisterObserver(observer);
+  MyFullObserver fullObserver(broker);
+  ASSERT_NO_THROW(observable.RegisterObserver(fullObserver));
 
-  observable.EmitMessage(genericMessage);
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(1, globalCounter);
+  ASSERT_EQ(1, test1Counter);
 
-  // check the observer is not called when another message is issued
-  OrthancStone::IMessage wrongMessage(OrthancStone::MessageType_GeometryReady);
-  // no observers have been registered
-  observable.EmitMessage(wrongMessage);
+  // register an invalid observer, check it raises an exception
+  MyPartialObserver partialObserver(broker);
+  ASSERT_THROW(observable.RegisterObserver(partialObserver), OrthancStone::MessageNotDeclaredException);
 
-  ASSERT_EQ(1, globalCounter);
+  // 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(observer);
-  observable.EmitMessage(genericMessage);
-  ASSERT_EQ(1, globalCounter);
+  observable.UnregisterObserver(fullObserver);
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
+  ASSERT_EQ(1, test1Counter);
 }
 
 TEST(MessageBroker, DeleteObserverWhileRegistered)
 {
   OrthancStone::MessageBroker broker;
-  OrthancStone::IObservable observable(broker);
+  MyObservable observable(broker);
 
-  globalCounter = 0;
-
-  OrthancStone::IMessage genericMessage(OrthancStone::MessageType_Generic);
+  test1Counter = 0;
 
   {
     // register an observer, check it is called
-    MyObserver observer(broker);
+    MyFullObserver observer(broker);
     observable.RegisterObserver(observer);
 
-    observable.EmitMessage(genericMessage);
+    observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-    ASSERT_EQ(1, globalCounter);
+    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(genericMessage);
+  observable.EmitMessage(OrthancStone::IMessage(OrthancStone::MessageType_Test1));
 
-  ASSERT_EQ(1, globalCounter);
+  ASSERT_EQ(1, test1Counter);
 }