# HG changeset patch # User Alain Mazy # Date 1538468136 -7200 # Node ID a91ad36b684c09aad796d301fd4c0d5a68e12a3b # Parent f58bfb7bbcc9413728a8123f7452491bcc8ad525# Parent 3b29c9c77d9baf6ed61b6e3b9e7ceaaf9500294d Merged am-callable-and-promise into am-2 diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/BaseCommandBuilder.cpp --- /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 . + **/ + +#include "BaseCommandBuilder.h" +#include "Core/OrthancException.h" +#include +#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; + } + +} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/BaseCommandBuilder.h --- /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 . + **/ + +#pragma once + +#include +#include + +#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); + }; +} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/BaseCommandFactory.cpp --- 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 . - **/ - -#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; - } - -} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/BaseCommandFactory.h --- 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 . - **/ - -#pragma once - -#include -#include - -#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 CommandCreationFunctions; - CommandCreationFunctions commands_; - - public: - virtual ICommand* CreateFromJson(const Json::Value& commandJson); - - template void RegisterCommandClass() - { - // create the command only to get its name - std::auto_ptr command(TCommand::Create()); - - commands_[command->GetName()] = &TCommand::Create; - } - }; -} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/ICommand.h --- 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 - 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 + { + public: + SimpleCommand(const std::string& name) + : BaseCommand(name) + {} + virtual void Execute() {} // TODO currently not used but this is not nice at all ! + }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/ICommandBuilder.h --- /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 . + **/ + + +#pragma once + +#include +#include + +#include "ICommand.h" + +namespace OrthancStone +{ + + class ICommandBuilder : public boost::noncopyable + { + public: + virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0; + }; +} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Commands/ICommandFactory.h --- 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 . - **/ - - -#pragma once - -#include -#include - -#include "ICommand.h" - -namespace OrthancStone -{ - - class ICommandFactory : public boost::noncopyable - { - public: - virtual ICommand* CreateFromJson(const Json::Value& commandJson) = 0; - template void RegisterCommandClass(); - }; -} diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/IStoneApplication.h --- 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 #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) + { + } }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/CMakeLists.txt --- 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 ) diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/SampleApplicationBase.h --- 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_;} + }; } } diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/SimpleViewerApplication.h --- 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 @@ -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(*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(*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(*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(*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(from).GetName(); - dynamic_cast(from).SetDefaultView(); - break; - case MessageType_OrthancApi_GetStudyIds_Ready: - OnStudyListReceived(dynamic_cast(message).response_); - break; - case MessageType_OrthancApi_GetSeries_Ready: - OnSeriesReceived(dynamic_cast(message).response_); - break; - case MessageType_OrthancApi_GetStudy_Ready: - OnStudyReceived(dynamic_cast(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_); diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/Web/simple-viewer.ts --- 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 diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/build-wasm.sh --- 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) diff -r f58bfb7bbcc9 -r a91ad36b684c Applications/Samples/build-web.sh --- 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) diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Layers/ILayerSource.h --- 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 GeometryReadyMessage; + typedef OriginMessage GeometryErrorMessage; + typedef OriginMessage ContentChangedMessage; + + struct SliceChangedMessage : public OriginMessage { 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 { - std::auto_ptr& layer_; + std::auto_ptr& renderer_; const CoordinateSystem3D& slice_; bool isError_; - LayerReadyMessage(std::auto_ptr& layer, + LayerReadyMessage(ILayerSource& origin, + std::auto_ptr& 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& 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& points, const CoordinateSystem3D& viewportSlice) = 0; diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Layers/LayerSourceBase.cpp --- 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 renderer(layer); - EmitMessage(ILayerSource::LayerReadyMessage(renderer, slice, isError)); + EmitMessage(ILayerSource::LayerReadyMessage(*this, renderer, slice, isError)); } } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Layers/LayerSourceBase.h --- 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); } }; diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Layers/OrthancFrameLayerSource.cpp --- 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(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(from); - LayerSourceBase::NotifyGeometryError(); - }; break; - case MessageType_SliceLoader_ImageReady: + LayerSourceBase::NotifyGeometryReady(); + } + else { - const OrthancSlicesLoader::SliceImageReadyMessage& msg = dynamic_cast(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(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(*this, &OrthancFrameLayerSource::OnSliceGeometryReady)); + loader_.RegisterObserverCallback(new Callable(*this, &OrthancFrameLayerSource::OnSliceGeometryError)); + loader_.RegisterObserverCallback(new Callable(*this, &OrthancFrameLayerSource::OnSliceImageReady)); + loader_.RegisterObserverCallback(new Callable(*this, &OrthancFrameLayerSource::OnSliceImageError)); } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Layers/OrthancFrameLayerSource.h --- 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); }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/ICallable.h --- /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 . + **/ + + +#pragma once + +#include "IMessage.h" + +#include + +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 + class MessageHandler: public ICallable + { + }; + + + template + class Callable : public MessageHandler + { + 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(message)); + } + + virtual MessageType GetMessageType() const + { + return static_cast(TMessage::Type); + } + + virtual IObserver* GetObserver() const + { + return &observer_; + } + }; +} diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/IMessage.h --- 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 + struct BaseMessage : public IMessage + { + enum + { + Type = type + }; + + BaseMessage() + : IMessage(static_cast(Type)) + {} + }; + + // simple message implementation when no payload is needed + // sample usage: + // typedef NoPayloadMessage GeometryReadyMessage; + template + struct NoPayloadMessage : public BaseMessage + { + NoPayloadMessage() + : BaseMessage() + {} + + }; + + // simple message implementation when no payload is needed but the origin is required + // sample usage: + // typedef OriginMessage SliceGeometryErrorMessage; + template + struct OriginMessage : public BaseMessage + { + TOrigin& origin_; + OriginMessage(TOrigin& origin) + : BaseMessage(), + origin_(origin) + {} + }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/IObservable.h --- 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 #include #include +#include + #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 observers_; - std::set emittableMessages_; + typedef std::map > Callables; + Callables callables_; + + typedef std::set 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::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(**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& GetEmittableMessages() const - { - return emittableMessages_; - } - - protected: - - void DeclareEmittableMessage(MessageType messageType) - { - emittableMessages_.insert(messageType); - } - - void CheckObserverDeclaredAllObservableMessages(IObserver& observer) - { - for (std::set::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::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); + } + }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/IObserver.h --- 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 handledMessages_; - std::set 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& GetHandledMessages() const - { - return handledMessages_; - } - - const std::set& 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); - } - }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/MessageBroker.cpp --- 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 . - **/ - - -#include "MessageBroker.h" - -#include -#include -#include - -#include "IObserver.h" -#include "MessageType.h" - -namespace OrthancStone { - - void MessageBroker::EmitMessage(IObservable& from, std::set observers, const IMessage& message) - { - std::vector activeObservers; - std::set_intersection(observers.begin(), - observers.end(), - activeObservers_.begin(), - activeObservers_.end(), - std::back_inserter(activeObservers) - ); - - for (std::vector::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) - } - } - } - -} diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/MessageBroker.h --- 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 -#include #include 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 observers, const IMessage& message); + bool IsActive(IObserver* observer) + { + return activeObservers_.find(observer) != activeObservers_.end(); + } }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/MessageForwarder.cpp --- /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 . + **/ + + +#include "MessageForwarder.h" + +#include "IObservable.h" + +namespace OrthancStone +{ + + void IMessageForwarder::ForwardMessageInternal(const IMessage& message) + { + emitter_.EmitMessage(message); + } + + void IMessageForwarder::RegisterForwarderInEmitter() + { + emitter_.RegisterForwarder(this); + } +} diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/MessageForwarder.h --- /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 . + **/ + + +#pragma once + +#include "ICallable.h" +#include "IObserver.h" + +#include + +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(broker, *this) // where this is B + * + * in C: + * B.RegisterObserverCallback(new Callable(*this, &B::MyCallback)) // where this is C + */ + template + class MessageForwarder : public IMessageForwarder, public IObserver, public Callable, TMessage> + { + public: + MessageForwarder(MessageBroker& broker, + IObservable& emitter // the object that will emit the messages to forward + ) + : IMessageForwarder(emitter), + IObserver(broker), + Callable, TMessage>(*this, &MessageForwarder::ForwardMessage) + { + RegisterForwarderInEmitter(); + } + +protected: + void ForwardMessage(const TMessage& message) + { + ForwardMessageInternal(message); + } + + }; +} diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/MessageType.h --- 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) }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Messages/Promise.h --- /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 . + **/ + + +#pragma once + +#include "MessageBroker.h" +#include "ICallable.h" +#include "IMessage.h" + +#include +#include + +namespace OrthancStone { + + class Promise : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::auto_ptr successCallable_; + std::auto_ptr 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; + } + + }; + + +} diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/SmartLoader.cpp --- 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(&from); - // TODO keep track of objects that have been loaded already - }; break; - case MessageType_LayerSource_LayerReady: - { - //const OrthancFrameLayerSource* layerSource=dynamic_cast(&from); - // TODO keep track of objects that have been loaded already - }; break; -// case MessageType_OrthancApi_GetStudyIds_Ready: -// { - -// const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast(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 layerSource (new OrthancFrameLayerSource(IObserver::broker_, webService_)); + std::auto_ptr layerSource (new OrthancFrameLayerSource(IObserver::broker_, orthancApiClient_)); layerSource->SetImageQuality(imageQuality_); - layerSource->RegisterObserver(*this); + layerSource->RegisterObserverCallback(new MessageForwarder(IObserver::broker_, *this)); + layerSource->RegisterObserverCallback(new MessageForwarder(IObserver::broker_, *this)); layerSource->LoadFrame(instanceId, frame); return layerSource.release(); diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/SmartLoader.h --- 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(); diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/StoneException.h --- 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 }; diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/IWebService.h --- 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 #include "../../Framework/Messages/IObserver.h" +#include "../../Framework/Messages/ICallable.h" #include +#include #include namespace OrthancStone @@ -40,81 +42,37 @@ public: typedef std::map Headers; - class ICallback : public IObserver + struct HttpRequestSuccessMessage: public BaseMessage { - 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(message); - OnHttpRequestError(msg.Uri, - msg.Payload); - }; break; + struct HttpRequestErrorMessage: public BaseMessage + { + 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(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* successCallback, + MessageHandler* 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* successCallback, + MessageHandler* failureCallback = NULL, + unsigned int timeoutInSeconds = 60) = 0; + }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/MessagingToolbox.cpp --- 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 #include +#include 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) diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/MessagingToolbox.h --- 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 . **/ @@ -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); diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/OrthancApiClient.cpp --- 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 +#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> orthancApiSuccessCallback_; + std::auto_ptr> orthancApiFailureCallback_; + public: + HttpResponseToJsonConverter(MessageBroker& broker, + MessageHandler* orthancApiSuccessCallback, + MessageHandler* 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> orthancApiSuccessCallback_; + std::auto_ptr> 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(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* orthancApiSuccessCallback, + MessageHandler* 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(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(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* successCallback, + MessageHandler* 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(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson), + new Callable(*converter, &HttpResponseToJsonConverter::ConvertError)); + + } + + void OrthancApiClient::GetBinaryAsync(const std::string& uri, + const IWebService::Headers& headers, + MessageHandler* successCallback, + MessageHandler* 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(*converter, &HttpResponseToBinaryConverter::ConvertResponseToBinary), + new Callable(*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* successCallback, + MessageHandler* 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(*converter, &HttpResponseToJsonConverter::ConvertResponseToJson), + new Callable(*converter, &HttpResponseToJsonConverter::ConvertError)); + } - void OrthancApiClient::ReleaseRequest(BaseRequest* request) + void OrthancApiClient::PostJsonAsyncExpectJson(const std::string& uri, + const Json::Value& data, + MessageHandler* successCallback, + MessageHandler* failureCallback, + Orthanc::IDynamicObject* payload) { - requestsInProgress_.erase(request); - delete request; + std::string body; + MessagingToolbox::JsonToString(body, data); + return PostBinaryAsyncExpectJson(uri, body, successCallback, failureCallback, payload); } + } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/OrthancApiClient.h --- 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 { - 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 + { + std::string Uri; + Orthanc::IDynamicObject* Payload; + + HttpErrorMessage(const std::string& uri, + Orthanc::IDynamicObject* payload = NULL) + : BaseMessage(), + Uri(uri), + Payload(payload) + { + } + }; + + struct BinaryResponseReadyMessage : public BaseMessage + { + 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_; // This is a PImpl pattern - std::set 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* successCallback, + MessageHandler* 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* successCallback, + MessageHandler* 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* successCallback, + MessageHandler* 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* successCallback, + MessageHandler* 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* successCallback, + MessageHandler* failureCallback = NULL, + Orthanc::IDynamicObject* payload = NULL); }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/OrthancSlicesLoader.cpp --- 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(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(dynamic_cast(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(dynamic_cast(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& 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(*(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(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(message.Payload)->GetInstanceId(); + unsigned int frame = dynamic_cast(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(*message.Payload); std::auto_ptr image; try { image.reset(new Orthanc::PngReader); - dynamic_cast(*image).ReadFromMemory(answer, size); + dynamic_cast(*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(*message.Payload); std::auto_ptr image; try { image.reset(new Orthanc::PamReader); - dynamic_cast(*image).ReadFromMemory(std::string(reinterpret_cast(answer), size)); + dynamic_cast(*image).ReadFromMemory(std::string(reinterpret_cast(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(*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(*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(*this, &OrthancSlicesLoader::ParseSeriesGeometry), + new Callable(*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(*this, &OrthancSlicesLoader::ParseInstanceGeometry), + new Callable(*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(*this, &OrthancSlicesLoader::ParseFrameGeometry), + new Callable(*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(*this, &OrthancSlicesLoader::ParseSliceImagePng), + new Callable(*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(*this, &OrthancSlicesLoader::ParseSliceImagePam), + new Callable(*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage(index, slice, SliceImageQuality_FullPam)); } @@ -974,8 +850,10 @@ "-" + slice.GetOrthancInstanceId() + "_" + boost::lexical_cast(slice.GetFrame())); - orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), - Operation::DownloadSliceImage(index, slice, quality)); + orthanc_.GetJsonAsync(uri, + new Callable(*this, &OrthancSlicesLoader::ParseSliceImageJpeg), + new Callable(*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceImage(index, slice, quality)); } @@ -1008,8 +886,10 @@ { std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" + boost::lexical_cast(slice.GetFrame()) + "/raw.gz"); - orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), - Operation::DownloadSliceRawImage(index, slice)); + orthanc_.GetBinaryAsync(uri, IWebService::Headers(), + new Callable(*this, &OrthancSlicesLoader::ParseSliceRawImage), + new Callable(*this, &OrthancSlicesLoader::OnSliceImageError), + Operation::DownloadSliceRawImage(index, slice)); } } } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Toolbox/OrthancSlicesLoader.h --- 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 +#include "OrthancApiClient.h" namespace OrthancStone { - class OrthancSlicesLoader : public IObservable + class OrthancSlicesLoader : public IObservable, public IObserver { public: - struct SliceImageReadyMessage : public IMessage + + typedef OriginMessage SliceGeometryReadyMessage; + typedef OriginMessage SliceGeometryErrorMessage; + + struct SliceImageReadyMessage : public BaseMessage { unsigned int sliceIndex_; const Slice& slice_; @@ -43,7 +48,7 @@ const Slice& slice, std::auto_ptr& 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 { 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_; // This is a PImpl pattern - - IWebService& orthanc_; + OrthancApiClient& orthanc_; State state_; SlicesSorter slices_; @@ -101,34 +103,23 @@ std::auto_ptr& 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); + }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Volumes/StructureSetLoader.cpp --- 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 op(dynamic_cast(payload)); - - switch (op->GetType()) - { - case Operation::Type_LoadStructureSet: - { - OrthancPlugins::FullOrthancDataset dataset(answer, answerSize); - structureSet_.reset(new DicomStructureSet(dataset)); - - std::set instances; - structureSet_->GetReferencedInstances(instances); - - for (std::set::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 instances; + structureSet_->GetReferencedInstances(instances); + + for (std::set::const_iterator it = instances.begin(); + it != instances.end(); ++it) + { + orthanc_.PostBinaryAsyncExpectJson("/tools/lookup", *it, + new Callable(*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(*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(*this, &StructureSetLoader::OnStructureSetLoaded)); } } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Volumes/StructureSetLoader.h --- 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 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); }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Widgets/LayerWidget.cpp --- 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(*this, &LayerWidget::OnGeometryReady)); + // currently ignore errors layer->RegisterObserverCallback(new Callable(*this, &LayerWidget::...)); + layer.RegisterObserverCallback(new Callable(*this, &LayerWidget::OnSliceChanged)); + layer.RegisterObserverCallback(new Callable(*this, &LayerWidget::OnContentChanged)); + layer.RegisterObserverCallback(new Callable(*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(from)); - break; - case MessageType_LayerSource_GeometryError: - LOG(ERROR) << "Cannot get geometry"; - break; - case MessageType_LayerSource_ContentChanged: - OnContentChanged(dynamic_cast(from)); - break; - case MessageType_LayerSource_SliceChanged: - OnSliceChanged(dynamic_cast(from), dynamic_cast(message).slice_); - break; - case MessageType_LayerSource_LayerReady: - { - const ILayerSource::LayerReadyMessage& layerReadyMessage = dynamic_cast(message); - OnLayerReady(layerReadyMessage.layer_, - dynamic_cast(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& 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)); } diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/Widgets/LayerWidget.h --- 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 GeometryChangedMessage; + typedef OriginMessage 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& 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: diff -r f58bfb7bbcc9 -r a91ad36b684c Framework/dev.h --- 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) diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/Oracle.cpp --- 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(*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))); diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/OracleWebService.h --- 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 . **/ @@ -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* successCallback, // takes ownership + MessageHandler* 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* successCallback, // takes ownership + MessageHandler* 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(); } }; } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServiceCommandBase.cpp --- 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* successCallback, + MessageHandler* 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())); } + } + } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServiceCommandBase.h --- 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 @@ -36,7 +37,8 @@ class WebServiceCommandBase : public IOracleCommand, IObservable { protected: - IWebService::ICallback& callback_; + std::auto_ptr> successCallback_; + std::auto_ptr> failureCallback_; Orthanc::WebServiceParameters parameters_; std::string uri_; std::map headers_; @@ -44,18 +46,23 @@ bool success_; std::string answer_; NativeStoneApplicationContext& context_; + unsigned int timeoutInSeconds_; public: WebServiceCommandBase(MessageBroker& broker, - IWebService::ICallback& callback, + MessageHandler* successCallback, // takes ownership + MessageHandler* failureCallback, // takes ownership const Orthanc::WebServiceParameters& parameters, const std::string& uri, const std::map& headers, + unsigned int timeoutInSeconds, Orthanc::IDynamicObject* payload /* takes ownership */, - NativeStoneApplicationContext& context); + NativeStoneApplicationContext& context + ); virtual void Execute() = 0; virtual void Commit(); }; + } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServiceGetCommand.cpp --- 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 . **/ @@ -25,14 +25,17 @@ namespace OrthancStone { + WebServiceGetCommand::WebServiceGetCommand(MessageBroker& broker, - IWebService::ICallback& callback, + MessageHandler* successCallback, // takes ownership + MessageHandler* 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++ ) diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServiceGetCommand.h --- 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 . **/ @@ -29,13 +29,16 @@ { public: WebServiceGetCommand(MessageBroker& broker, - IWebService::ICallback& callback, + MessageHandler* successCallback, // takes ownership + MessageHandler* 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(); }; + } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServicePostCommand.cpp --- 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* successCallback, // takes ownership + MessageHandler* 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_); diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Generic/WebServicePostCommand.h --- 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* successCallback, // takes ownership + MessageHandler* 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); diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/Defaults.cpp --- 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 #include #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 application; -static OrthancStone::IStoneApplicationToWebApplicationAdapter* applicationWebAdapter = NULL; +static std::unique_ptr applicationWasmAdapter = NULL; static std::unique_ptr context; static OrthancStone::StartupParametersBuilder startupParametersBuilder; static OrthancStone::MessageBroker broker; @@ -69,7 +69,7 @@ printf("CreateWasmApplication\n"); application.reset(CreateUserApplication(broker)); - applicationWebAdapter = dynamic_cast(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; } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/Defaults.h --- 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 #include #include +#include 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 { diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/IStoneApplicationToWebApplicationAdapter.h --- 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 - -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 diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/WasmPlatformApplicationAdapter.cpp --- /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 +#include +#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 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 diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/WasmPlatformApplicationAdapter.h --- /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 +#include +#include + +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 diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/WasmWebService.cpp --- 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(callback)-> - OnHttpRequestError(uri, reinterpret_cast(payload)); + reinterpret_cast*>(failureCallable)-> + Apply(OrthancStone::IWebService::HttpRequestErrorMessage(uri, reinterpret_cast(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(callback)-> - OnHttpRequestSuccess(uri, body, bodySize, reinterpret_cast(payload)); + reinterpret_cast*>(successCallable)-> + Apply(OrthancStone::IWebService::HttpRequestSuccessMessage(uri, body, bodySize, reinterpret_cast(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* successCallable, + MessageHandler* 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* successCallable, + MessageHandler* 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); } + } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/WasmWebService.h --- 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* successCallable, + MessageHandler* 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* successCallable, + MessageHandler* failureCallable = NULL, + unsigned int timeoutInSeconds = 60); virtual void Start() { diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/WasmWebService.js --- 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); } } } diff -r f58bfb7bbcc9 -r a91ad36b684c Platforms/Wasm/wasm-application-runner.ts diff -r f58bfb7bbcc9 -r a91ad36b684c Resources/CMake/OrthancStoneConfiguration.cmake --- 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 diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/TestCommands.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 . - **/ +///** +// * 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 . +// **/ -#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 -{ -public: - static int counter; - int increment_; -public: - CommandIncrement() - : OrthancStone::BaseCommand("increment"), - increment_(0) - {} +//class CommandIncrement: public OrthancStone::BaseCommand +//{ +//public: +// static int counter; +// int increment_; +//public: +// CommandIncrement() +// : OrthancStone::BaseCommand("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(); +// factory.RegisterCommandClass(); - Json::Value cmdJson; - cmdJson["command"] = "noop"; +// Json::Value cmdJson; +// cmdJson["command"] = "noop"; - std::auto_ptr command(factory.CreateFromJson(cmdJson)); +// std::auto_ptr 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(); - factory.RegisterCommandClass(); +// factory.RegisterCommandClass(); +// factory.RegisterCommandClass(); - Json::Value cmdJson; - cmdJson["command"] = "increment"; - cmdJson["args"]["increment"] = 2; +// Json::Value cmdJson; +// cmdJson["command"] = "increment"; +// cmdJson["args"]["increment"] = 2; - std::auto_ptr command(factory.CreateFromJson(cmdJson)); +// std::auto_ptr 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(); +//TEST(Commands, TryCreateUnknowCommand) +//{ +// OrthancStone::BaseCommandFactory factory; +// factory.RegisterCommandClass(); - Json::Value cmdJson; - cmdJson["command"] = "unknown"; +// Json::Value cmdJson; +// cmdJson["command"] = "unknown"; - ASSERT_THROW(std::auto_ptr command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException); -} +// ASSERT_THROW(std::auto_ptr command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException); +//} -TEST(Commands, TryCreateCommandFromInvalidJson) -{ - OrthancStone::BaseCommandFactory factory; - factory.RegisterCommandClass(); +//TEST(Commands, TryCreateCommandFromInvalidJson) +//{ +// OrthancStone::BaseCommandFactory factory; +// factory.RegisterCommandClass(); - Json::Value cmdJson; - cmdJson["command-name"] = "noop"; +// Json::Value cmdJson; +// cmdJson["command-name"] = "noop"; - ASSERT_THROW(std::auto_ptr command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException); -} +// ASSERT_THROW(std::auto_ptr command(factory.CreateFromJson(cmdJson)), Orthanc::OrthancException); +//} diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/TestMessageBroker.cpp --- 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 . - **/ +///** +// * 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 . +// **/ -#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); +//} diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/TestMessageBroker2.cpp --- /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 . + **/ + + +#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 +// 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(message)); +// } + +// virtual MessageType GetMessageType() const +// { +// return static_cast(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 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 > 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::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::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 + { + 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(broker, *this)); + } + }; + + + class MyPromiseSource : public IObservable + { + Promise* currentPromise_; + public: + struct MyPromiseMessage: public BaseMessage + { + 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(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(*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(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(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(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(target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable(target, &MyPromiseTarget::DecrementCounter)); + + testCounter = 0; + source.CompleteSomethingAsyncWithSuccess(10); + ASSERT_EQ(10, testCounter); + + // test a failing promise + source.StartSomethingAsync() + .Then(new Callable(target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable(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(*target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable(*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(*target, &MyPromiseTarget::IncrementCounter)) + .Else(new Callable(*target, &MyPromiseTarget::DecrementCounter)); + + testCounter = 0; + source.CompleteSomethingAsyncWithFailure(15); + ASSERT_EQ(0, testCounter); +} + diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/TestMessageBroker2_connect_ok.cpp --- /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 . + **/ + + +#include "gtest/gtest.h" + +#include +#include +#include + +#include +#include +#include + +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 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 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 +// struct CallableObserver : public ICallableObserver +// { +// void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message); +// }; + + struct CallableObserver + { + IObserver* observer; + boost::function f; + }; + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set observers_; + + std::map > 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 void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message)) + void Connect(MessageType messageType, IObserver& observer, boost::function f) + { + callables_[messageType] = std::set(); + 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); +} + + diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/TestMessageBroker2_promise_and_connect_ok.cpp --- /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 . + **/ + + +#include "gtest/gtest.h" + +#include +#include +#include + +#include +#include +#include + +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 activeObservers_; // the list of observers that are currently alive (that have not been deleted) + std::set activePromiseTargets_; + std::set 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 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 successCallable_; + + IPromiseTarget* failureTarget_; + boost::function 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 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 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 f; + }; + + class IObservable : public boost::noncopyable + { + protected: + MessageBroker& broker_; + + std::set observers_; + + std::map > 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::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 void Connect(MessageType messageType, IObserver& observer, void (TObserver::*ptrToMemberHandler)(IObservable& from, const IMessage& message)) + void Connect(int messageType, IObserver& observer, boost::function f) + { + callables_[messageType] = std::set(); + 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(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); +//} diff -r f58bfb7bbcc9 -r a91ad36b684c UnitTestsSources/UnitTestsMain.cpp --- 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) {