changeset 619:9cd19b28f011

test: refactoring oracle
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 07 May 2019 11:13:24 +0200
parents 0925b27e8750
children 8adc8cfb50c7
files Framework/Messages/IMessage.h Samples/Sdl/CMakeLists.txt Samples/Sdl/Loader.cpp
diffstat 3 files changed, 661 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Messages/IMessage.h	Thu May 02 18:58:46 2019 +0200
+++ b/Framework/Messages/IMessage.h	Tue May 07 11:13:24 2019 +0200
@@ -31,10 +31,10 @@
   class IMessage : public boost::noncopyable
   {
   private:
-    int messageType_;
+    MessageType messageType_;
     
   protected:
-    IMessage(const int& messageType) :
+    IMessage(MessageType messageType) :
       messageType_(messageType)
     {
     }
@@ -44,7 +44,7 @@
     {
     }
 
-    virtual int GetType() const
+    virtual MessageType GetType() const
     {
       return messageType_;
     }
@@ -53,7 +53,7 @@
 
   // base class to derive from to implement your own messages
   // it handles the message type for you
-  template <int type>
+  template <MessageType type>
   class BaseMessage : public IMessage
   {
   public:
@@ -63,7 +63,7 @@
     };
 
     BaseMessage() :
-      IMessage(static_cast<int>(Type))
+      IMessage(static_cast<MessageType>(Type))
     {
     }
   };
@@ -72,7 +72,7 @@
   // simple message implementation when no payload is needed
   // sample usage:
   // typedef NoPayloadMessage<MessageType_VolumeSlicer_GeometryReady> GeometryReadyMessage;
-  template <int type>
+  template <MessageType type>
   class NoPayloadMessage : public BaseMessage<type>
   {
   public:
@@ -85,7 +85,7 @@
   // simple message implementation when no payload is needed but the origin is required
   // sample usage:
   // typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage;
-  template <int type, typename TOrigin>
+  template <MessageType type, typename TOrigin>
   class OriginMessage : public BaseMessage<type>
   {
   private:
--- a/Samples/Sdl/CMakeLists.txt	Thu May 02 18:58:46 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Tue May 07 11:13:24 2019 +0200
@@ -60,3 +60,9 @@
   )
 
 target_link_libraries(BasicScene OrthancStone)
+
+add_executable(Loader
+  Loader.cpp
+  )
+
+target_link_libraries(Loader OrthancStone)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/Loader.cpp	Tue May 07 11:13:24 2019 +0200
@@ -0,0 +1,648 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// From Stone
+#include "../../Framework/StoneInitialization.h"
+#include "../../Framework/Messages/IMessage.h"
+#include "../../Framework/Messages/MessageBroker.h"
+#include "../../Framework/Messages/ICallable.h"
+#include "../../Framework/Messages/IObservable.h"
+
+// From Orthanc framework
+#include <Core/IDynamicObject.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/PngWriter.h>
+#include <Core/Logging.h>
+#include <Core/HttpClient.h>
+#include <Core/MultiThreading/SharedMessageQueue.h>
+#include <Core/OrthancException.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+#include <json/writer.h>
+
+#include <list>
+#include <stdio.h>
+
+
+
+namespace Refactoring
+{
+  class IOracleCommand : public boost::noncopyable
+  {
+  public:
+    enum Type
+    {
+      Type_OrthancApi
+    };
+
+    virtual ~IOracleCommand()
+    {
+    }
+
+    virtual Type GetType() const = 0;
+  };
+
+
+  class IOracle : public boost::noncopyable
+  {
+  public:
+    virtual ~IOracle()
+    {
+    }
+
+    virtual void Schedule(IOracleCommand* command) = 0;  // Takes ownership
+  };
+
+
+
+
+  class OracleCommandWithPayload : public IOracleCommand
+  {
+  private:
+    std::auto_ptr<Orthanc::IDynamicObject>  payload_;
+
+  public:
+    void SetPayload(Orthanc::IDynamicObject* payload)
+    {
+      if (payload == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+      else
+      {
+        payload_.reset(payload);
+      }    
+    }
+
+    bool HasPayload() const
+    {
+      return (payload_.get() != NULL);
+    }
+
+    const Orthanc::IDynamicObject& GetPayload() const
+    {
+      if (HasPayload())
+      {
+        return *payload_;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+  };
+
+
+
+  typedef std::map<std::string, std::string>  HttpHeaders;
+
+  class OrthancApiOracleCommand : public OracleCommandWithPayload
+  {
+  public:
+    class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess,   // TODO
+                                                              OrthancApiOracleCommand>
+    {
+    private:
+      HttpHeaders   headers_;
+      std::string   answer_;
+
+    public:
+      SuccessMessage(const OrthancApiOracleCommand& command,
+                     const HttpHeaders& answerHeaders,
+                     std::string& answer  /* will be swapped to avoid a memcpy() */) :
+        OriginMessage(command),
+        headers_(answerHeaders),
+        answer_(answer)
+      {
+      }
+
+      const std::string& GetAnswer() const
+      {
+        return answer_;
+      }
+
+      void GetJsonBody(Json::Value& target) const
+      {
+        Json::Reader reader;
+        if (!reader.parse(answer_, target))
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+        }
+      }
+
+      const HttpHeaders&  GetAnswerHeaders() const
+      {
+        return headers_;
+      }
+    };
+
+
+    class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError,   // TODO
+                                                              OrthancApiOracleCommand>
+    {
+    private:
+      Orthanc::HttpStatus  status_;
+
+    public:
+      FailureMessage(const OrthancApiOracleCommand& command,
+                     Orthanc::HttpStatus status) :
+        OriginMessage(command),
+        status_(status)
+      {
+      }
+
+      Orthanc::HttpStatus GetHttpStatus() const
+      {
+        return status_;
+      }
+    };
+
+
+  private:
+    Orthanc::HttpMethod  method_;
+    std::string          uri_;
+    std::string          body_;
+    HttpHeaders          headers_;
+    unsigned int         timeout_;
+
+    std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> >  successCallback_;
+    std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> >  failureCallback_;
+
+  public:
+    OrthancApiOracleCommand() :
+      method_(Orthanc::HttpMethod_Get),
+      uri_("/"),
+      timeout_(10)
+    {
+    }
+
+    virtual Type GetType() const
+    {
+      return Type_OrthancApi;
+    }
+
+    void SetMethod(Orthanc::HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    void SetUri(const std::string& uri)
+    {
+      uri_ = uri;
+    }
+
+    void SetBody(const std::string& body)
+    {
+      body_ = body;
+    }
+
+    void SetBody(const Json::Value& json)
+    {
+      Json::FastWriter writer;
+      body_ = writer.write(json);
+    }
+
+    void SetHttpHeader(const std::string& key,
+                       const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    Orthanc::HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    const std::string& GetUri() const
+    {
+      return uri_;
+    }
+
+    const std::string& GetBody() const
+    {
+      if (method_ == Orthanc::HttpMethod_Post ||
+          method_ == Orthanc::HttpMethod_Put)
+      {
+        return body_;
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    const HttpHeaders& GetHttpHeaders() const
+    {
+      return headers_;
+    }
+
+    void SetTimeout(unsigned int seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    unsigned int GetTimeout() const
+    {
+      return timeout_;
+    }
+  };
+
+
+
+  class NativeApplicationContext : public boost::noncopyable
+  {
+  private:
+    boost::shared_mutex            mutex_;
+    Orthanc::WebServiceParameters  orthanc_;
+    OrthancStone::MessageBroker    broker_;
+    OrthancStone::IObservable      oracleObservable_;
+
+  public:
+    NativeApplicationContext() :
+      oracleObservable_(broker_)
+    {
+      orthanc_.SetUrl("http://localhost:8042/");
+    }
+
+
+    class ReaderLock : public boost::noncopyable
+    {
+    private:
+      NativeApplicationContext&                that_;
+      boost::shared_lock<boost::shared_mutex>  lock_;
+
+    public:
+      ReaderLock(NativeApplicationContext& that) : 
+      that_(that),
+      lock_(that.mutex_)
+      {
+      }
+
+      const Orthanc::WebServiceParameters& GetOrthancParameters() const
+      {
+        return that_.orthanc_;
+      }
+    };
+
+
+    class WriterLock : public boost::noncopyable
+    {
+    private:
+      NativeApplicationContext&                that_;
+      boost::unique_lock<boost::shared_mutex>  lock_;
+
+    public:
+      WriterLock(NativeApplicationContext& that) : 
+      that_(that),
+      lock_(that.mutex_)
+      {
+      }
+
+      OrthancStone::MessageBroker& GetBroker() 
+      {
+        return that_.broker_;
+      }
+
+      void SetOrthancParameters(Orthanc::WebServiceParameters& orthanc)
+      {
+        that_.orthanc_ = orthanc;
+      }
+
+      OrthancStone::IObservable&  GetOracleObservable()
+      {
+        return that_.oracleObservable_;
+      }
+    };
+  };
+
+
+
+  class NativeOracle : public IOracle
+  {
+  private:
+    class Item : public Orthanc::IDynamicObject
+    {
+    private:
+      std::auto_ptr<IOracleCommand>  command_;
+
+    public:
+      Item(IOracleCommand* command) :
+      command_(command)
+      {
+        if (command == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+      }
+
+      const IOracleCommand& GetCommand()
+      {
+        assert(command_.get() != NULL);
+        return *command_;
+      }
+    };
+
+
+    enum State
+    {
+      State_Setup,
+      State_Running,
+      State_Stopped
+    };
+
+
+    NativeApplicationContext&     context_;
+    Orthanc::SharedMessageQueue   queue_;
+    State                         state_;
+    boost::mutex                  mutex_;
+    std::vector<boost::thread*>   workers_;
+
+
+    void Execute(const OrthancApiOracleCommand& command)
+    {
+      std::auto_ptr<Orthanc::HttpClient> client;
+
+      {
+        NativeApplicationContext::ReaderLock lock(context_);
+        client.reset(new Orthanc::HttpClient(lock.GetOrthancParameters(), command.GetUri()));
+      }
+
+      client->SetMethod(command.GetMethod());
+      client->SetBody(command.GetBody());
+      client->SetTimeout(command.GetTimeout());
+      
+      {
+        const HttpHeaders& headers = command.GetHttpHeaders();
+        for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
+        {
+          client->AddHeader(it->first, it->second);
+        }
+      }
+
+      std::string answer;
+      HttpHeaders answerHeaders;
+
+      bool success;
+      try
+      {
+        success = client->Apply(answer, answerHeaders);
+      }
+      catch (Orthanc::OrthancException& e)
+      {
+        success = false;
+      }
+
+      {
+        NativeApplicationContext::WriterLock lock(context_);
+
+        if (success)
+        {
+          OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer);
+          lock.GetOracleObservable().EmitMessage(message);
+        }
+        else
+        {
+          OrthancApiOracleCommand::FailureMessage message(command, client->GetLastStatus());
+          lock.GetOracleObservable().EmitMessage(message);
+        }
+      }
+    }
+
+
+
+    void Step()
+    {
+      std::auto_ptr<Orthanc::IDynamicObject>  item(queue_.Dequeue(100));
+
+      if (item.get() != NULL)
+      {
+        const IOracleCommand& command = dynamic_cast<Item*>(item.get())->GetCommand();
+
+        switch (command.GetType())
+        {
+          case IOracleCommand::Type_OrthancApi:
+            Execute(dynamic_cast<const OrthancApiOracleCommand&>(command));
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+        }
+      }
+    }
+
+
+    static void Worker(NativeOracle* that)
+    {
+      assert(that != NULL);
+      
+      for (;;)
+      {
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+          if (that->state_ != State_Running)
+          {
+            return;
+          }
+        }
+
+        that->Step();
+      }
+    }
+
+
+    void StopInternal()
+    {
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+
+        if (state_ == State_Setup ||
+            state_ == State_Stopped)
+        {
+          return;
+        }
+        else
+        {
+          state_ = State_Stopped;
+        }
+      }
+
+      for (size_t i = 0; i < workers_.size(); i++)
+      {
+        if (workers_[i] != NULL)
+        {
+          if (workers_[i]->joinable())
+          {
+            workers_[i]->join();
+          }
+
+          delete workers_[i];
+        }
+      } 
+    }
+
+
+  public:
+    NativeOracle(NativeApplicationContext& context) :
+    context_(context),
+      state_(State_Setup),
+      workers_(4)
+    {
+    }
+
+    virtual ~NativeOracle()
+    {
+      StopInternal();
+    }
+
+    void SetWorkersCount(unsigned int count)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (count <= 0)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      else if (state_ != State_Setup)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        workers_.resize(count);
+      }
+    }
+
+    void Start()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (state_ != State_Setup)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        state_ = State_Running;
+
+        for (unsigned int i = 0; i < workers_.size(); i++)
+        {
+          workers_[i] = new boost::thread(Worker, this);
+        }
+      }      
+    }
+
+    void Stop()
+    {
+      StopInternal();
+    }
+
+    virtual void Schedule(IOracleCommand* command)
+    {
+      queue_.Enqueue(new Item(command));
+    }
+  };
+}
+
+
+
+class Toto : public OrthancStone::IObserver
+{
+private:
+  void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
+  {
+    Json::Value v;
+    message.GetJsonBody(v);
+
+    printf("ICI [%s]\n", v.toStyledString().c_str());
+  }
+
+  void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
+  {
+    printf("ERROR %d\n", message.GetHttpStatus());
+  }
+
+public:
+  Toto(OrthancStone::IObservable& oracle) :
+    IObserver(oracle.GetBroker())
+  {
+    oracle.RegisterObserverCallback(new OrthancStone::Callable<Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle));
+    oracle.RegisterObserverCallback(new OrthancStone::Callable<Toto, Refactoring::OrthancApiOracleCommand::FailureMessage>(*this, &Toto::Handle));
+  }
+};
+
+
+void Run(Refactoring::NativeApplicationContext& context)
+{
+  std::auto_ptr<Toto> toto;
+
+  {
+    Refactoring::NativeApplicationContext::WriterLock lock(context);
+    toto.reset(new Toto(lock.GetOracleObservable()));
+  }
+
+  Refactoring::NativeOracle oracle(context);
+  oracle.Start();
+
+  {
+    Json::Value v = Json::objectValue;
+    v["Level"] = "Series";
+    v["Query"] = Json::objectValue;
+
+    std::auto_ptr<Refactoring::OrthancApiOracleCommand>  command(new Refactoring::OrthancApiOracleCommand);
+    command->SetMethod(Orthanc::HttpMethod_Post);
+    command->SetUri("/tools/find");
+    command->SetBody(v);
+
+    oracle.Schedule(command.release());
+  }
+
+  boost::this_thread::sleep(boost::posix_time::seconds(1));
+
+  oracle.Stop();
+}
+
+
+
+/**
+ * IMPORTANT: The full arguments to "main()" are needed for SDL on
+ * Windows. Otherwise, one gets the linking error "undefined reference
+ * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
+ **/
+int main(int argc, char* argv[])
+{
+  OrthancStone::StoneInitialize();
+  Orthanc::Logging::EnableInfoLevel(true);
+
+  try
+  {
+    Refactoring::NativeApplicationContext context;
+    Run(context);
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << "EXCEPTION: " << e.What();
+  }
+
+  OrthancStone::StoneFinalize();
+
+  return 0;
+}