changeset 1800:30e97a1f4093 worklists

callback for handling worklists with plugins
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 20 Nov 2015 13:53:20 +0100
parents 4f01c9d73f02
children 2c60c357ee3e
files OrthancServer/DicomProtocol/DicomFindAnswers.h OrthancServer/DicomProtocol/IFindRequestHandler.h OrthancServer/DicomProtocol/IWorklistRequestHandler.h OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/FindScp.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancFindRequestHandler.h OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h
diffstat 13 files changed, 229 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Fri Nov 20 13:53:20 2015 +0100
@@ -42,10 +42,15 @@
     class Answer;
 
     std::vector<Answer*> answers_;
+    bool                 complete_;
 
     Answer& GetAnswerInternal(size_t index) const;
 
   public:
+    DicomFindAnswers() : complete_(true)
+    {
+    }
+
     ~DicomFindAnswers()
     {
       Clear();
@@ -77,5 +82,15 @@
     void ToJson(Json::Value& target,
                 size_t index,
                 bool simplify) const;
+
+    bool IsComplete() const
+    {
+      return complete_;
+    }
+
+    void SetComplete(bool isComplete)
+    {
+      complete_ = isComplete;
+    }
   };
 }
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Fri Nov 20 13:53:20 2015 +0100
@@ -43,13 +43,7 @@
     {
     }
 
-    /**
-     * Can throw exceptions. Returns "false" iff too many results have
-     * to be returned. In such a case, a "Matching terminated due to
-     * Cancel request" DIMSE code would be returned.
-     * https://www.dabsoft.ch/dicom/4/V.4.1/
-     **/
-    virtual bool Handle(DicomFindAnswers& answers,
+    virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
--- a/OrthancServer/DicomProtocol/IWorklistRequestHandler.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/DicomProtocol/IWorklistRequestHandler.h	Fri Nov 20 13:53:20 2015 +0100
@@ -43,13 +43,7 @@
     {
     }
 
-    /**
-     * Can throw exceptions. Returns "false" iff too many results have
-     * to be returned. In such a case, a "Matching terminated due to
-     * Cancel request" DIMSE code would be returned.
-     * https://www.dabsoft.ch/dicom/4/V.4.1/
-     **/
-    virtual bool Handle(DicomFindAnswers& answers,
+    virtual void Handle(DicomFindAnswers& answers,
                         ParsedDicomFile& query,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -803,7 +803,11 @@
               {
                 std::auto_ptr<IStoreRequestHandler> handler
                   (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
-                cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+                }
               }
               break;
 
@@ -812,7 +816,11 @@
               {
                 std::auto_ptr<IMoveRequestHandler> handler
                   (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
-                cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
+
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
+                }
               }
               break;
 
--- a/OrthancServer/Internals/FindScp.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -102,7 +102,6 @@
       const std::string* remoteIp_;
       const std::string* remoteAet_;
       const std::string* calledAet_;
-      bool noCroppingOfResults_;
     };
 
 
@@ -135,9 +134,9 @@
             if (data.worklistHandler_ != NULL)
             {
               ParsedDicomFile query(*requestIdentifiers);
-              data.noCroppingOfResults_ = data.worklistHandler_->Handle(data.answers_, query,
-                                                                        *data.remoteIp_, *data.remoteAet_,
-                                                                        *data.calledAet_);
+              data.worklistHandler_->Handle(data.answers_, query,
+                                            *data.remoteIp_, *data.remoteAet_,
+                                            *data.calledAet_);
               ok = true;
             }
             else
@@ -151,9 +150,9 @@
             {
               DicomMap input;
               FromDcmtkBridge::Convert(input, *requestIdentifiers);
-              data.noCroppingOfResults_ = data.findHandler_->Handle(data.answers_, input,
-                                                                    *data.remoteIp_, *data.remoteAet_,
-                                                                    *data.calledAet_);
+              data.findHandler_->Handle(data.answers_, input,
+                                        *data.remoteIp_, *data.remoteAet_,
+                                        *data.calledAet_);
               ok = true;
             }
             else
@@ -191,7 +190,7 @@
         response->DimseStatus = STATUS_Pending;
         *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
       }
-      else if (data.noCroppingOfResults_)
+      else if (data.answers_.IsComplete())
       {
         // Success: All the results have been sent
         response->DimseStatus = STATUS_Success;
@@ -224,7 +223,6 @@
     data.remoteIp_ = &remoteIp;
     data.remoteAet_ = &remoteAet;
     data.calledAet_ = &calledAet;
-    data.noCroppingOfResults_ = true;
 
     OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
                                           FindScpCallback, &data,
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -88,7 +88,7 @@
   }
 
 
-  bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
+  void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::string& remoteIp,
                                          const std::string& remoteAet,
@@ -195,7 +195,7 @@
     context_.GetIndex().FindCandidates(resources, instances, finder);
 
     assert(resources.size() == instances.size());
-    bool finished = true;
+    bool complete = true;
 
     for (size_t i = 0; i < instances.size(); i++)
     {
@@ -207,7 +207,7 @@
         if (maxResults != 0 &&
             answers.GetSize() >= maxResults)
         {
-          finished = false;
+          complete = false;
           break;
         }
         else
@@ -219,6 +219,6 @@
 
     LOG(INFO) << "Number of matching resources: " << answers.GetSize();
 
-    return finished;
+    answers.SetComplete(complete);
   }
 }
--- a/OrthancServer/OrthancFindRequestHandler.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Fri Nov 20 13:53:20 2015 +0100
@@ -55,7 +55,7 @@
     {
     }
 
-    virtual bool Handle(DicomFindAnswers& answers,
+    virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
--- a/OrthancServer/ParsedDicomFile.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -153,7 +153,8 @@
 
 
   // This method can only be called from the constructors!
-  void ParsedDicomFile::Setup(const char* buffer, size_t size)
+  void ParsedDicomFile::Setup(const void* buffer, 
+                              size_t size)
   {
     DcmInputBufferStream is;
     if (size > 0)
@@ -835,7 +836,8 @@
   }
 
 
-  ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl)
+  ParsedDicomFile::ParsedDicomFile(const void* content, 
+                                   size_t size) : pimpl_(new PImpl)
   {
     Setup(content, size);
   }
--- a/OrthancServer/ParsedDicomFile.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Fri Nov 20 13:53:20 2015 +0100
@@ -52,7 +52,7 @@
 
     ParsedDicomFile(ParsedDicomFile& other);
 
-    void Setup(const char* content,
+    void Setup(const void* content,
                size_t size);
 
     void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
@@ -66,7 +66,7 @@
 
     ParsedDicomFile(const DicomMap& map);
 
-    ParsedDicomFile(const char* content,
+    ParsedDicomFile(const void* content,
                     size_t size);
 
     ParsedDicomFile(const std::string& content);
--- a/OrthancServer/main.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/OrthancServer/main.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -107,7 +107,7 @@
   {
   }
 
-  virtual bool Handle(DicomFindAnswers& answers,
+  virtual void Handle(DicomFindAnswers& answers,
                       ParsedDicomFile& query,
                       const std::string& remoteIp,
                       const std::string& remoteAet,
@@ -150,15 +150,14 @@
       LOG(ERROR) << "Inexistent folder while scanning for worklists: " << source;
     }
 
-    return true;  // All the worklists have been returned
+    answers.SetComplete(true);  // All the worklists have been returned
   }
 };
 
 
 class MyDicomServerFactory : 
   public IStoreRequestHandlerFactory,
-  public IFindRequestHandlerFactory,
-  public IWorklistRequestHandlerFactory, 
+  public IFindRequestHandlerFactory, 
   public IMoveRequestHandlerFactory
 {
 private:
@@ -209,11 +208,6 @@
     return new OrthancMoveRequestHandler(context_);
   }
 
-  virtual IWorklistRequestHandler* ConstructWorklistRequestHandler()
-  {
-    return new OrthancWorklistRequestHandler(context_);
-  }
-
   void Done()
   {
   }
@@ -780,8 +774,12 @@
   dicomServer.SetMoveRequestHandlerFactory(serverFactory);
   dicomServer.SetFindRequestHandlerFactory(serverFactory);
 
-  // TODO - Disable the following line if no worklist plugin is available
-  dicomServer.SetWorklistRequestHandlerFactory(serverFactory);
+#if ORTHANC_PLUGINS_ENABLED == 1
+  if (plugins)
+  {
+    dicomServer.SetWorklistRequestHandlerFactory(*plugins);
+  }
+#endif
 
   dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242));
   dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
--- a/Plugins/Engine/OrthancPlugins.cpp	Fri Nov 20 12:57:14 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Fri Nov 20 13:53:20 2015 +0100
@@ -237,6 +237,7 @@
     typedef std::list<RestCallback*>  RestCallbacks;
     typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
     typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
+    typedef std::list<OrthancPluginWorklistCallback>  WorklistCallbacks;
     typedef std::map<Property, std::string>  Properties;
 
     PluginsManager manager_;
@@ -244,10 +245,12 @@
     RestCallbacks restCallbacks_;
     OnStoredCallbacks  onStoredCallbacks_;
     OnChangeCallbacks  onChangeCallbacks_;
+    WorklistCallbacks  worklistCallbacks_;
     std::auto_ptr<StorageAreaFactory>  storageArea_;
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
     boost::recursive_mutex changeCallbackMutex_;
+    boost::recursive_mutex worklistCallbackMutex_;
     boost::recursive_mutex invokeServiceMutex_;
     Properties properties_;
     int argc_;
@@ -622,6 +625,17 @@
   }
 
 
+  void OrthancPlugins::RegisterWorklistCallback(const void* parameters)
+  {
+    const _OrthancPluginWorklistCallback& p = 
+      *reinterpret_cast<const _OrthancPluginWorklistCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered an modality worklist callback";
+    pimpl_->worklistCallbacks_.push_back(p.callback);
+  }
+
+
+
 
   void OrthancPlugins::AnswerBuffer(const void* parameters)
   {
@@ -1401,6 +1415,10 @@
         RegisterOnChangeCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterWorklistCallback:
+        RegisterWorklistCallback(parameters);
+        return true;
+
       case _OrthancPluginService_AnswerBuffer:
         AnswerBuffer(parameters);
         return true;
@@ -1831,6 +1849,16 @@
         ApplyDicomToJson(service, parameters);
         return true;
 
+      case _OrthancPluginService_AddWorklistAnswer:
+      {
+        const _OrthancPluginAddWorklistAnswer& p =
+          *reinterpret_cast<const _OrthancPluginAddWorklistAnswer*>(parameters);
+        
+        ParsedDicomFile answer(p.answerDicom, p.answerSize);
+        reinterpret_cast<DicomFindAnswers*>(p.target)->Add(answer);
+        return true;
+      }
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
@@ -1948,4 +1976,72 @@
   {
     return pimpl_->dictionary_;
   }
+
+
+  void OrthancPlugins::HandleWorklist(DicomFindAnswers& answers,
+                                      ParsedDicomFile& query,
+                                      const std::string& remoteIp,
+                                      const std::string& remoteAet,
+                                      const std::string& calledAet)
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+
+    for (PImpl::WorklistCallbacks::const_iterator
+           callback = pimpl_->worklistCallbacks_.begin(); 
+         callback != pimpl_->worklistCallbacks_.end(); ++callback)
+    {
+      OrthancPluginErrorCode error = (*callback) 
+        (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers),
+         reinterpret_cast<OrthancPluginWorklistQuery*>(&query),
+         remoteAet.c_str(),
+         calledAet.c_str());
+
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        GetErrorDictionary().LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+  }
+
+
+  class OrthancPlugins::WorklistHandler : public IWorklistRequestHandler
+  {
+  private:
+    OrthancPlugins&  plugins_;
+
+  public:
+    WorklistHandler(OrthancPlugins& plugins) : plugins_(plugins)
+    {
+    }
+
+    virtual void Handle(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet)
+    {
+      plugins_.HandleWorklist(answers, query, remoteIp, remoteAet, calledAet);
+    }
+  };
+
+  
+  IWorklistRequestHandler* OrthancPlugins::ConstructWorklistRequestHandler()
+  {
+    bool hasHandler;
+
+    {
+      boost::recursive_mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_);
+      hasHandler = !pimpl_->worklistCallbacks_.empty();
+    }
+
+    if (hasHandler)
+    {
+      return new WorklistHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Fri Nov 20 13:53:20 2015 +0100
@@ -50,6 +50,7 @@
 #include "../../Core/FileStorage/IStorageArea.h"
 #include "../../Core/HttpServer/IHttpHandler.h"
 #include "../../OrthancServer/IServerListener.h"
+#include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
@@ -63,12 +64,15 @@
   class OrthancPlugins : 
     public IHttpHandler, 
     public IPluginServiceProvider, 
-    public IServerListener
+    public IServerListener,
+    public IWorklistRequestHandlerFactory
   {
   private:
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
+    class WorklistHandler;
+
     void CheckContextAvailable();
 
     void RegisterRestCallback(const void* parameters,
@@ -78,6 +82,8 @@
 
     void RegisterOnChangeCallback(const void* parameters);
 
+    void RegisterWorklistCallback(const void* parameters);
+
     void AnswerBuffer(const void* parameters);
 
     void Redirect(const void* parameters);
@@ -138,6 +144,12 @@
                               OrthancPluginResourceType resourceType,
                               const char* resource);
 
+    void HandleWorklist(DicomFindAnswers& answers,
+                        ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
+
   public:
     OrthancPlugins();
 
@@ -204,6 +216,9 @@
     {
       SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL);
     }
+
+
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler();
   };
 }
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Nov 20 12:57:14 2015 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Nov 20 13:53:20 2015 +0100
@@ -402,6 +402,7 @@
     _OrthancPluginService_RegisterStorageArea = 1002,
     _OrthancPluginService_RegisterOnChangeCallback = 1003,
     _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -466,6 +467,9 @@
     _OrthancPluginService_GetFontInfo = 6010,
     _OrthancPluginService_DrawText = 6011,
 
+    /* Primitives for handling worklists */
+    _OrthancPluginService_AddWorklistAnswer = 7000,
+
     _OrthancPluginService_INTERNAL = 0x7fffffff
   } _OrthancPluginService;
 
@@ -873,14 +877,18 @@
    *
    * Signature of a callback function that is triggered when Orthanc receives a C-Find SCP request.
    *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
    * @return 0 if success, other value if error.
    * @ingroup Worklists
    **/
-  typedef OrthancPluginErrorCode (*OrthancPluginFindWorklist) (
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
     OrthancPluginWorklistAnswers*     answers,
     const OrthancPluginWorklistQuery* query,
-    const char*                       calledAet,
-    const char*                       remoteAet);
+    const char*                       remoteAet,
+    const char*                       calledAet);
 
 
 
@@ -4061,6 +4069,58 @@
     return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
   }
 
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @ingroup Worklists
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*  target;
+    const void*                    answerDicom;
+    uint32_t                       answerSize;
+  } _OrthancPluginAddWorklistAnswer;
+
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginAddWorklistAnswer(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  target,
+    const void*                    answerDicom,
+    uint32_t                       answerSize)
+  {
+    _OrthancPluginAddWorklistAnswer params;
+    params.target = target;
+    params.answerDicom = answerDicom;
+    params.answerSize = answerSize;
+
+    return context->InvokeService(context, _OrthancPluginService_AddWorklistAnswer, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif