changeset 1786:164d78911382 worklists

primitives to handle dicom worklists
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 18 Nov 2015 12:00:14 +0100
parents 2dbf25006f88
children 1b1d5470233f
files CMakeLists.txt Core/Enumerations.cpp Core/Enumerations.h OrthancServer/DicomProtocol/DicomFindAnswers.h OrthancServer/DicomProtocol/DicomServer.cpp OrthancServer/DicomProtocol/DicomServer.h OrthancServer/DicomProtocol/DicomWorklistAnswers.cpp OrthancServer/DicomProtocol/DicomWorklistAnswers.h OrthancServer/DicomProtocol/IApplicationEntityFilter.h OrthancServer/DicomProtocol/IFindRequestHandler.h OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h OrthancServer/DicomProtocol/IMoveRequestHandler.h OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h OrthancServer/DicomProtocol/IStoreRequestHandler.h OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h OrthancServer/DicomProtocol/IWorklistRequestHandler.h OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h OrthancServer/Internals/CommandDispatcher.cpp OrthancServer/Internals/FindScp.cpp OrthancServer/Internals/FindScp.h OrthancServer/Internals/MoveScp.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/main.cpp Plugins/Include/orthanc/OrthancCPlugin.h Resources/ErrorCodes.json
diffstat 26 files changed, 412 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Nov 18 09:56:34 2015 +0100
+++ b/CMakeLists.txt	Wed Nov 18 12:00:14 2015 +0100
@@ -155,6 +155,7 @@
   OrthancServer/DicomProtocol/DicomFindAnswers.cpp
   OrthancServer/DicomProtocol/DicomServer.cpp
   OrthancServer/DicomProtocol/DicomUserConnection.cpp
+  OrthancServer/DicomProtocol/DicomWorklistAnswers.cpp
   OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
   OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp
   OrthancServer/ExportedResource.cpp
--- a/Core/Enumerations.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/Core/Enumerations.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -325,6 +325,9 @@
       case ErrorCode_CannotOrderSlices:
         return "Unable to order the slices of the series";
 
+      case ErrorCode_NoWorklistHandler:
+        return "No request handler factory for DICOM C-Find Modality SCP";
+
       default:
         if (error >= ErrorCode_START_PLUGINS)
         {
--- a/Core/Enumerations.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/Core/Enumerations.h	Wed Nov 18 12:00:14 2015 +0100
@@ -138,6 +138,7 @@
     ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
     ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
     ErrorCode_START_PLUGINS = 1000000
   };
 
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Wed Nov 18 12:00:14 2015 +0100
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class DicomFindAnswers
+  class DicomFindAnswers : public boost::noncopyable
   {
   private:
     std::vector<DicomMap*> items_;
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -94,6 +94,7 @@
     findRequestHandlerFactory_ = NULL;
     moveRequestHandlerFactory_ = NULL;
     storeRequestHandlerFactory_ = NULL;
+    worklistRequestHandlerFactory_ = NULL;
     applicationEntityFilter_ = NULL;
     checkCalledAet_ = true;
     clientTimeout_ = 30;
@@ -245,6 +246,29 @@
     }
   }
 
+  void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory)
+  {
+    Stop();
+    worklistRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasWorklistRequestHandlerFactory() const
+  {
+    return (worklistRequestHandlerFactory_ != NULL);
+  }
+
+  IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const
+  {
+    if (HasWorklistRequestHandlerFactory())
+    {
+      return *worklistRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoWorklistHandler);
+    }
+  }
+
   void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
   {
     Stop();
--- a/OrthancServer/DicomProtocol/DicomServer.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Nov 18 12:00:14 2015 +0100
@@ -35,6 +35,7 @@
 #include "IFindRequestHandlerFactory.h"
 #include "IMoveRequestHandlerFactory.h"
 #include "IStoreRequestHandlerFactory.h"
+#include "IWorklistRequestHandlerFactory.h"
 #include "IApplicationEntityFilter.h"
 
 #include <boost/shared_ptr.hpp>
@@ -58,6 +59,7 @@
     IFindRequestHandlerFactory* findRequestHandlerFactory_;
     IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
     IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
+    IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
     IApplicationEntityFilter* applicationEntityFilter_;
 
     static void ServerThread(DicomServer* server);
@@ -91,6 +93,10 @@
     bool HasStoreRequestHandlerFactory() const;
     IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const;
 
+    void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler);
+    bool HasWorklistRequestHandlerFactory() const;
+    IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const;
+
     void SetApplicationEntityFilter(IApplicationEntityFilter& handler);
     bool HasApplicationEntityFilter() const;
     IApplicationEntityFilter& GetApplicationEntityFilter() const;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/DicomWorklistAnswers.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "DicomWorklistAnswers.h"
+
+#include "../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  void DicomWorklistAnswers::Clear()
+  {
+    for (size_t i = 0; i < items_.size(); i++)
+    {
+      delete items_[i];
+    }
+
+    items_.clear();
+  }
+
+  void DicomWorklistAnswers::Add(ParsedDicomFile& dicom)
+  {
+    items_.push_back(dicom.Clone());
+  }
+
+  void DicomWorklistAnswers::Add(const std::string& dicom)
+  {
+    items_.push_back(new ParsedDicomFile(dicom));
+  }
+
+  const ParsedDicomFile& DicomWorklistAnswers::GetAnswer(size_t index) const
+  {
+    if (index < items_.size())
+    {
+      return *items_[index];
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/DicomWorklistAnswers.h	Wed Nov 18 12:00:14 2015 +0100
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../ParsedDicomFile.h"
+
+namespace Orthanc
+{
+  class DicomWorklistAnswers : public boost::noncopyable
+  {
+  private:
+    std::vector<ParsedDicomFile*> items_;
+
+  public:
+    ~DicomWorklistAnswers()
+    {
+      Clear();
+    }
+
+    void Clear();
+
+    void Add(ParsedDicomFile& dicom);
+
+    void Add(const std::string& dicom);
+
+    size_t GetSize() const
+    {
+      return items_.size();
+    }
+
+    const ParsedDicomFile& GetAnswer(size_t index) const;
+  };
+}
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Wed Nov 18 12:00:14 2015 +0100
@@ -38,7 +38,7 @@
 
 namespace Orthanc
 {
-  class IApplicationEntityFilter
+  class IApplicationEntityFilter : public boost::noncopyable
   {
   public:
     virtual ~IApplicationEntityFilter()
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Wed Nov 18 12:00:14 2015 +0100
@@ -34,13 +34,9 @@
 
 #include "DicomFindAnswers.h"
 
-#include <vector>
-#include <string>
-
-
 namespace Orthanc
 {
-  class IFindRequestHandler
+  class IFindRequestHandler : public boost::noncopyable
   {
   public:
     virtual ~IFindRequestHandler()
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Wed Nov 18 12:00:14 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IFindRequestHandlerFactory
+  class IFindRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IFindRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Wed Nov 18 12:00:14 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestIterator
+  class IMoveRequestIterator : public boost::noncopyable
   {
   public:
     enum Status
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Wed Nov 18 12:00:14 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestHandlerFactory
+  class IMoveRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IMoveRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Wed Nov 18 12:00:14 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IStoreRequestHandler
+  class IStoreRequestHandler : public boost::noncopyable
   {
   public:
     virtual ~IStoreRequestHandler()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Wed Nov 18 12:00:14 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IStoreRequestHandlerFactory
+  class IStoreRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IStoreRequestHandlerFactory()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/IWorklistRequestHandler.h	Wed Nov 18 12:00:14 2015 +0100
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "DicomWorklistAnswers.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandler()
+    {
+    }
+
+    /**
+     * 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(DicomWorklistAnswers& answers,
+                        const ParsedDicomFile& query,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h	Wed Nov 18 12:00:14 2015 +0100
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWorklistRequestHandler.h"
+
+namespace Orthanc
+{
+  class IWorklistRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IWorklistRequestHandlerFactory()
+    {
+    }
+
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0;
+  };
+}
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -430,6 +430,11 @@
         knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
       }
 
+      if (server.HasWorklistRequestHandlerFactory())
+      {
+        knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
+      }
+
       // For C-MOVE
       if (server.HasMoveRequestHandlerFactory())
       {
@@ -812,11 +817,23 @@
               break;
 
             case DicomRequestType_Find:
-              if (server_.HasFindRequestHandlerFactory()) // Should always be true
+              if (server_.HasFindRequestHandlerFactory() || // Should always be true
+                  server_.HasWorklistRequestHandlerFactory())
               {
-                std::auto_ptr<IFindRequestHandler> handler
-                  (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
-                cond = Internals::findScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_);
+                std::auto_ptr<IFindRequestHandler> findHandler;
+                if (server_.HasFindRequestHandlerFactory())
+                {
+                  findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
+                }
+
+                std::auto_ptr<IWorklistRequestHandler> worklistHandler;
+                if (server_.HasWorklistRequestHandlerFactory())
+                {
+                  worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
+                }
+
+                cond = Internals::findScp(assoc_, &msg, presID, findHandler.get(), 
+                                          worklistHandler.get(), remoteIp_, remoteAet_);
               }
               break;
 
--- a/OrthancServer/Internals/FindScp.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -95,8 +95,8 @@
   {  
     struct FindScpData
     {
-      IFindRequestHandler* handler_;
-      DicomMap input_;
+      IFindRequestHandler* findHandler_;
+      IWorklistRequestHandler* worklistHandler_;
       DicomFindAnswers answers_;
       DcmDataset* lastRequest_;
       const std::string* remoteIp_;
@@ -120,20 +120,54 @@
       bzero(response, sizeof(T_DIMSE_C_FindRSP));
       *statusDetail = NULL;
 
+      std::string sopClassUid(request->AffectedSOPClassUID);
+
       FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData);
       if (data.lastRequest_ == NULL)
       {
-        FromDcmtkBridge::Convert(data.input_, *requestIdentifiers);
+        bool ok = false;
 
         try
         {
-          data.noCroppingOfResults_ = data.handler_->Handle(data.answers_, data.input_, 
-                                                            *data.remoteIp_, *data.remoteAet_);
+          if (sopClassUid == UID_FINDModalityWorklistInformationModel)
+          {
+            if (data.worklistHandler_ != NULL)
+            {
+              // TODO
+              std::auto_ptr<ParsedDicomFile> query(ParsedDicomFile::CreateFromDcmtkDataset(requestIdentifiers));
+              DicomWorklistAnswers a;
+              data.worklistHandler_->Handle(a, *query, *data.remoteIp_, *data.remoteAet_);
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request";
+            }
+          }
+          else
+          {
+            if (data.findHandler_ != NULL)
+            {
+              DicomMap input;
+              FromDcmtkBridge::Convert(input, *requestIdentifiers);
+              data.noCroppingOfResults_ = data.findHandler_->Handle(data.answers_, input,
+                                                                    *data.remoteIp_, *data.remoteAet_);
+              ok = true;
+            }
+            else
+            {
+              LOG(ERROR) << "No C-Find handler is installed, cannot handle this request";
+            }
+          }
         }
         catch (OrthancException& e)
         {
           // Internal error!
           LOG(ERROR) <<  "C-FIND request handler has failed: " << e.What();
+        }
+
+        if (!ok)
+        {
           response->DimseStatus = STATUS_FIND_Failed_UnableToProcess;
           *responseIdentifiers = NULL;   
           return;
@@ -175,13 +209,15 @@
   OFCondition Internals::findScp(T_ASC_Association * assoc, 
                                  T_DIMSE_Message * msg, 
                                  T_ASC_PresentationContextID presID,
-                                 IFindRequestHandler& handler,
+                                 IFindRequestHandler* findHandler,
+                                 IWorklistRequestHandler* worklistHandler,
                                  const std::string& remoteIp,
                                  const std::string& remoteAet)
   {
     FindScpData data;
     data.lastRequest_ = NULL;
-    data.handler_ = &handler;
+    data.findHandler_ = findHandler;
+    data.worklistHandler_ = worklistHandler;
     data.remoteIp_ = &remoteIp;
     data.remoteAet_ = &remoteAet;
     data.noCroppingOfResults_ = true;
--- a/OrthancServer/Internals/FindScp.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/Internals/FindScp.h	Wed Nov 18 12:00:14 2015 +0100
@@ -33,6 +33,7 @@
 #pragma once
 
 #include "../DicomProtocol/IFindRequestHandler.h"
+#include "../DicomProtocol/IWorklistRequestHandler.h"
 
 #include <dcmtk/dcmnet/dimse.h>
 
@@ -43,7 +44,8 @@
     OFCondition findScp(T_ASC_Association * assoc, 
                         T_DIMSE_Message * msg, 
                         T_ASC_PresentationContextID presID,
-                        IFindRequestHandler& handler,
+                        IFindRequestHandler* findHandler,   // can be NULL
+                        IWorklistRequestHandler* worklistHandler,   // can be NULL
                         const std::string& remoteIp,
                         const std::string& remoteAet);
   }
--- a/OrthancServer/Internals/MoveScp.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/Internals/MoveScp.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -98,7 +98,6 @@
     {
       std::string target_;
       IMoveRequestHandler* handler_;
-      DicomMap input_;
       DcmDataset* lastRequest_;
       unsigned int subOperationCount_;
       unsigned int failureCount_;
@@ -128,11 +127,12 @@
       MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData);
       if (data.lastRequest_ == NULL)
       {
-        FromDcmtkBridge::Convert(data.input_, *requestIdentifiers);
+        DicomMap input;
+        FromDcmtkBridge::Convert(input, *requestIdentifiers);
 
         try
         {
-          data.iterator_.reset(data.handler_->Handle(data.target_, data.input_, 
+          data.iterator_.reset(data.handler_->Handle(data.target_, input,
                                                      *data.remoteIp_, *data.remoteAet_));
 
           if (data.iterator_.get() == NULL)
--- a/OrthancServer/ParsedDicomFile.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -823,6 +823,12 @@
   }
 
 
+  ParsedDicomFile::ParsedDicomFile(void* fileFormat) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(static_cast<DcmFileFormat*>(fileFormat));
+  }
+
+
   ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl)
   {
     Setup(content, size);
@@ -1222,4 +1228,15 @@
   {
     FromDcmtkBridge::Convert(tags, *pimpl_->file_->getDataset());
   }
+
+
+  ParsedDicomFile* ParsedDicomFile::CreateFromDcmtkDataset(void* dataset)
+  {
+    assert(dataset != NULL);
+
+    DcmDataset *d = static_cast<DcmDataset*>(dataset);
+    std::auto_ptr<DcmFileFormat> fileFormat(new DcmFileFormat(d));
+    
+    return new ParsedDicomFile(fileFormat.release());
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Wed Nov 18 12:00:14 2015 +0100
@@ -58,6 +58,8 @@
                           const std::string& value,
                           bool decodeBinaryTags);
 
+    ParsedDicomFile(void* fileFormat);   // Create by embedding a DcmFileFormat (takes ownership)
+
   public:
     ParsedDicomFile();  // Create a minimal DICOM instance
 
@@ -150,6 +152,8 @@
     bool ExtractPdf(std::string& pdf);
 
     void Convert(DicomMap& tags);
+
+    static ParsedDicomFile* CreateFromDcmtkDataset(void* dataset);
   };
 
 }
--- a/OrthancServer/main.cpp	Wed Nov 18 09:56:34 2015 +0100
+++ b/OrthancServer/main.cpp	Wed Nov 18 12:00:14 2015 +0100
@@ -89,10 +89,32 @@
 };
 
 
+class OrthancWorklistRequestHandler : public IWorklistRequestHandler
+{
+private:
+  ServerContext& server_;
+
+public:
+  OrthancWorklistRequestHandler(ServerContext& context) :
+    server_(context)
+  {
+  }
+
+  virtual bool Handle(DicomWorklistAnswers& answers,
+                      const ParsedDicomFile& query,
+                      const std::string& remoteIp,
+                      const std::string& remoteAet)
+  {
+    printf("Worklist\n");
+    return true;
+  }
+};
+
 
 class MyDicomServerFactory : 
   public IStoreRequestHandlerFactory,
-  public IFindRequestHandlerFactory, 
+  public IFindRequestHandlerFactory,
+  public IWorklistRequestHandlerFactory, 
   public IMoveRequestHandlerFactory
 {
 private:
@@ -143,6 +165,11 @@
     return new OrthancMoveRequestHandler(context_);
   }
 
+  virtual IWorklistRequestHandler* ConstructWorklistRequestHandler()
+  {
+    return new OrthancWorklistRequestHandler(context_);
+  }
+
   void Done()
   {
   }
@@ -550,6 +577,7 @@
     PrintErrorCode(ErrorCode_DatabaseNotInitialized, "Plugin trying to call the database during its initialization");
     PrintErrorCode(ErrorCode_SslDisabled, "Orthanc has been built without SSL support");
     PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series");
+    PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP");
   }
 
   std::cout << std::endl;
@@ -704,6 +732,10 @@
   dicomServer.SetStoreRequestHandlerFactory(serverFactory);
   dicomServer.SetMoveRequestHandlerFactory(serverFactory);
   dicomServer.SetFindRequestHandlerFactory(serverFactory);
+
+  // TODO - Disable the following line if no worklist plugin is available
+  dicomServer.SetWorklistRequestHandlerFactory(serverFactory);
+
   dicomServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("DicomPort", 4242));
   dicomServer.SetApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
   dicomServer.SetApplicationEntityFilter(dicomFilter);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Nov 18 09:56:34 2015 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Nov 18 12:00:14 2015 +0100
@@ -271,6 +271,7 @@
     OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
     OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
     OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
   } OrthancPluginErrorCode;
--- a/Resources/ErrorCodes.json	Wed Nov 18 09:56:34 2015 +0100
+++ b/Resources/ErrorCodes.json	Wed Nov 18 12:00:14 2015 +0100
@@ -513,5 +513,10 @@
     "Code": 2040,
     "Name": "CannotOrderSlices",
     "Description": "Unable to order the slices of the series"
+  },
+  {
+    "Code": 2041, 
+    "Name": "NoWorklistHandler", 
+    "Description": "No request handler factory for DICOM C-Find Modality SCP"
   }
 ]