changeset 1794:bdfae6e17d23 worklists

integration mainline->worklists
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 19 Nov 2015 16:02:35 +0100
parents b769623c806c (diff) 9a3a77d1d2a2 (current diff)
children af6840eb23ee
files OrthancServer/DicomInstanceToStore.cpp OrthancServer/ServerEnumerations.h
diffstat 37 files changed, 672 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Nov 19 11:57:32 2015 +0100
+++ b/CMakeLists.txt	Thu Nov 19 16:02:35 2015 +0100
@@ -179,6 +179,7 @@
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
   OrthancServer/ParsedDicomFile.cpp
   OrthancServer/QueryRetrieveHandler.cpp
+  OrthancServer/Search/IFindConstraint.cpp
   OrthancServer/Search/LookupIdentifierQuery.cpp
   OrthancServer/Search/LookupResource.cpp
   OrthancServer/Search/SetOfResources.cpp
--- a/Core/Enumerations.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/Core/Enumerations.cpp	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/Core/Enumerations.h	Thu Nov 19 16:02:35 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/NEWS	Thu Nov 19 11:57:32 2015 +0100
+++ b/NEWS	Thu Nov 19 16:02:35 2015 +0100
@@ -18,6 +18,7 @@
 Plugins
 -------
 
+* New functions "OrthancPluginDicomInstanceToJson()" and "OrthancPluginDicomBufferToJson()"
 * New function "OrthancPluginRegisterErrorCode()" to declare custom error codes
 * New function "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags
 * New function "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API
--- a/OrthancServer/DicomDirWriter.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomDirWriter.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -527,7 +527,7 @@
       path = directory + '\\' + filename;
     }
 
-    DcmFileFormat& fileFormat = *reinterpret_cast<DcmFileFormat*>(dicom.GetDcmtkObject());
+    DcmFileFormat& fileFormat = dicom.GetDcmtkObject();
 
     DcmDirectoryRecord* instance;
     bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, fileFormat, filename.c_str(), path.c_str());
--- a/OrthancServer/DicomInstanceToStore.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomInstanceToStore.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -41,12 +41,6 @@
 
 namespace Orthanc
 {
-  static DcmDataset& GetDataset(ParsedDicomFile& file)
-  {
-    return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject())->getDataset();
-  }
-
-
   void DicomInstanceToStore::AddMetadata(ResourceType level,
                                          MetadataType metadata,
                                          const std::string& value)
@@ -75,7 +69,8 @@
       {
         // Serialize the parsed DICOM file
         buffer_.Allocate();
-        if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), GetDataset(parsed_.GetContent())))
+        if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), 
+                                                 *parsed_.GetContent().GetDcmtkObject().getDataset()))
         {
           LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer";
           throw OrthancException(ErrorCode_InternalError);
@@ -103,14 +98,15 @@
     if (!summary_.HasContent())
     {
       summary_.Allocate();
-      FromDcmtkBridge::Convert(summary_.GetContent(), GetDataset(parsed_.GetContent()));
+      FromDcmtkBridge::Convert(summary_.GetContent(), 
+                               *parsed_.GetContent().GetDcmtkObject().getDataset());
     }
     
     if (!json_.HasContent())
     {
       json_.Allocate();
-      FromDcmtkBridge::ToJson(json_.GetContent(),
-                              GetDataset(parsed_.GetContent()), 
+      FromDcmtkBridge::ToJson(json_.GetContent(), 
+                              *parsed_.GetContent().GetDcmtkObject().getDataset(),
                               DicomToJsonFormat_Full, 
                               DicomToJsonFlags_Default,
                               256 /* max string length */);
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -34,25 +34,154 @@
 #include "DicomFindAnswers.h"
 
 #include "../FromDcmtkBridge.h"
+#include "../ToDcmtkBridge.h"
+#include "../../Core/OrthancException.h"
+
+#include <memory>
+#include <dcmtk/dcmdata/dcfilefo.h>
+
 
 namespace Orthanc
 {
+  class DicomFindAnswers::Answer
+  {
+  private:
+    ParsedDicomFile* dicom_;
+    DicomMap*        map_;
+    
+  public:
+    Answer(ParsedDicomFile& dicom) : 
+      dicom_(dicom.Clone()),
+      map_(NULL)
+    {
+    }
+
+    Answer(const char* dicom,
+           size_t size) : 
+      dicom_(new ParsedDicomFile(dicom, size)),
+      map_(NULL)
+    {
+    }
+
+    Answer(const DicomMap& map) : 
+      dicom_(NULL),
+      map_(map.Clone())
+    {
+    }
+
+    ~Answer()
+    {
+      if (dicom_ != NULL)
+      {
+        delete dicom_;
+      }
+
+      if (map_ != NULL)
+      {
+        delete map_;
+      }
+    }
+
+    ParsedDicomFile& GetDicomFile()
+    {
+      if (dicom_ == NULL)
+      {
+        assert(map_ != NULL);
+        dicom_ = new ParsedDicomFile(*map_);
+      }
+
+      return *dicom_;
+    }
+
+    DcmDataset* ExtractDcmDataset() const
+    {
+      if (dicom_ != NULL)
+      {
+        return new DcmDataset(*dicom_->GetDcmtkObject().getDataset());
+      }
+      else
+      {
+        assert(map_ != NULL);
+        return ToDcmtkBridge::Convert(*map_);
+      }
+    }
+  };
+
+
   void DicomFindAnswers::Clear()
   {
-    for (size_t i = 0; i < items_.size(); i++)
+    for (size_t i = 0; i < answers_.size(); i++)
     {
-      delete items_[i];
+      assert(answers_[i] != NULL);
+      delete answers_[i];
+    }
+
+    answers_.clear();
+  }
+
+
+  void DicomFindAnswers::Reserve(size_t size)
+  {
+    if (size > answers_.size())
+    {
+      answers_.reserve(size);
     }
   }
 
-  void DicomFindAnswers::Reserve(size_t size)
+
+  void DicomFindAnswers::Add(const DicomMap& map)
+  {
+    answers_.push_back(new Answer(map));
+  }
+
+
+  void DicomFindAnswers::Add(ParsedDicomFile& dicom)
   {
-    if (size > items_.size())
+    answers_.push_back(new Answer(dicom));
+  }
+
+
+  void DicomFindAnswers::Add(const char* dicom,
+                             size_t size)
+  {
+    answers_.push_back(new Answer(dicom, size));
+  }
+
+
+  DicomFindAnswers::Answer& DicomFindAnswers::GetAnswerInternal(size_t index) const
+  {
+    if (index < answers_.size())
     {
-      items_.reserve(size);
+      return *answers_.at(index);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 
+
+  ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const
+  {
+    return GetAnswerInternal(index).GetDicomFile();
+  }
+
+
+  DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const
+  {
+    return GetAnswerInternal(index).ExtractDcmDataset();
+  }
+
+
+  void DicomFindAnswers::ToJson(Json::Value& target,
+                                size_t index,
+                                bool simplify) const
+  {
+    DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Simple : DicomToJsonFormat_Full);
+    GetAnswer(index).ToJson(target, format, DicomToJsonFlags_None, 0);
+  }
+
+
   void DicomFindAnswers::ToJson(Json::Value& target,
                                 bool simplify) const
   {
@@ -60,8 +189,8 @@
 
     for (size_t i = 0; i < GetSize(); i++)
     {
-      Json::Value answer(Json::objectValue);
-      FromDcmtkBridge::ToJson(answer, GetAnswer(i), simplify);
+      Json::Value answer;
+      ToJson(answer, i, simplify);
       target.append(answer);
     }
   }
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Thu Nov 19 16:02:35 2015 +0100
@@ -32,17 +32,18 @@
 
 #pragma once
 
-#include "../../Core/DicomFormat/DicomMap.h"
-
-#include <vector>
-#include <json/json.h>
+#include "../ParsedDicomFile.h"
 
 namespace Orthanc
 {
-  class DicomFindAnswers
+  class DicomFindAnswers : public boost::noncopyable
   {
   private:
-    std::vector<DicomMap*> items_;
+    class Answer;
+
+    std::vector<Answer*> answers_;
+
+    Answer& GetAnswerInternal(size_t index) const;
 
   public:
     ~DicomFindAnswers()
@@ -54,22 +55,27 @@
 
     void Reserve(size_t index);
 
-    void Add(const DicomMap& map)
-    {
-      items_.push_back(map.Clone());
-    }
+    void Add(const DicomMap& map);
+
+    void Add(ParsedDicomFile& dicom);
+
+    void Add(const char* dicom,
+             size_t size);
 
     size_t GetSize() const
     {
-      return items_.size();
+      return answers_.size();
     }
 
-    const DicomMap& GetAnswer(size_t index) const
-    {
-      return *items_.at(index);
-    }
+    ParsedDicomFile& GetAnswer(size_t index) const;
+
+    DcmDataset* ExtractDcmDataset(size_t index) const;
 
     void ToJson(Json::Value& target,
                 bool simplify) const;
+
+    void ToJson(Json::Value& target,
+                size_t index,
+                bool simplify) const;
   };
 }
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Thu Nov 19 16:02:35 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;
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h	Thu Nov 19 16:02:35 2015 +0100
@@ -38,7 +38,7 @@
 
 namespace Orthanc
 {
-  class IApplicationEntityFilter
+  class IApplicationEntityFilter : public boost::noncopyable
   {
   public:
     virtual ~IApplicationEntityFilter()
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h	Thu Nov 19 16:02:35 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IFindRequestHandlerFactory
+  class IFindRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IFindRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandler.h	Thu Nov 19 16:02:35 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestIterator
+  class IMoveRequestIterator : public boost::noncopyable
   {
   public:
     enum Status
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h	Thu Nov 19 16:02:35 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class IMoveRequestHandlerFactory
+  class IMoveRequestHandlerFactory : public boost::noncopyable
   {
   public:
     virtual ~IMoveRequestHandlerFactory()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h	Thu Nov 19 16:02:35 2015 +0100
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IStoreRequestHandler
+  class IStoreRequestHandler : public boost::noncopyable
   {
   public:
     virtual ~IStoreRequestHandler()
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h	Thu Nov 19 16:02:35 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	Thu Nov 19 16:02:35 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 "DicomFindAnswers.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(DicomFindAnswers& 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	Thu Nov 19 16:02:35 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/FromDcmtkBridge.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -1073,6 +1073,9 @@
       case EVR_TM:
         return ValueRepresentation_Time;
 
+      case EVR_SQ:
+        return ValueRepresentation_Sequence;
+
       default:
         return ValueRepresentation_Other;
     }
--- a/OrthancServer/Internals/CommandDispatcher.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Internals/CommandDispatcher.cpp	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Internals/FindScp.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -87,7 +87,7 @@
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 
-
+#include <dcmtk/dcmdata/dcfilefo.h>
 
 namespace Orthanc
 {
@@ -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,53 @@
       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)
+            {
+              ParsedDicomFile query(*requestIdentifiers);
+              data.noCroppingOfResults_ = data.worklistHandler_->Handle(data.answers_, 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;
@@ -153,7 +186,7 @@
       {
         // There are pending results that are still to be sent
         response->DimseStatus = STATUS_Pending;
-        *responseIdentifiers = ToDcmtkBridge::Convert(data.answers_.GetAnswer(responseCount - 1));
+        *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1);
       }
       else if (data.noCroppingOfResults_)
       {
@@ -175,13 +208,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	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Internals/FindScp.h	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Internals/MoveScp.cpp	Thu Nov 19 16:02:35 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/OrthancRestApi/OrthancRestModalities.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -280,6 +280,18 @@
   }
 
 
+  static void CopyTagIfExists(DicomMap& target,
+                              ParsedDicomFile& source,
+                              const DicomTag& tag)
+  {
+    std::string tmp;
+    if (source.GetTagValue(tmp, tag))
+    {
+      target.SetValue(tag, tmp);
+    }
+  }
+
+
   static void DicomFind(RestApiPostCall& call)
   {
     LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri();
@@ -303,15 +315,16 @@
     Json::Value result = Json::arrayValue;
     for (size_t i = 0; i < patients.GetSize(); i++)
     {
-      Json::Value patient(Json::objectValue);
-      FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i), true);
+      Json::Value patient;
+      patients.ToJson(patient, i, true);
 
       DicomMap::SetupFindStudyTemplate(m);
       if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize()))
       {
         return;
       }
-      m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
+
+      CopyTagIfExists(m, patients.GetAnswer(i), DICOM_TAG_PATIENT_ID);
 
       DicomFindAnswers studies;
       FindStudy(studies, locker.GetConnection(), m);
@@ -321,16 +334,17 @@
       // Loop over the found studies
       for (size_t j = 0; j < studies.GetSize(); j++)
       {
-        Json::Value study(Json::objectValue);
-        FromDcmtkBridge::ToJson(study, studies.GetAnswer(j), true);
+        Json::Value study;
+        studies.ToJson(study, j, true);
 
         DicomMap::SetupFindSeriesTemplate(m);
         if (!MergeQueryAndTemplate(m, call.GetBodyData(), call.GetBodySize()))
         {
           return;
         }
-        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
-        m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
+
+        CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_PATIENT_ID);
+        CopyTagIfExists(m, studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID);
 
         DicomFindAnswers series;
         FindSeries(series, locker.GetConnection(), m);
@@ -339,8 +353,8 @@
         study["Series"] = Json::arrayValue;
         for (size_t k = 0; k < series.GetSize(); k++)
         {
-          Json::Value series2(Json::objectValue);
-          FromDcmtkBridge::ToJson(series2, series.GetAnswer(k), true);
+          Json::Value series2;
+          series.ToJson(series2, k, true);
           study["Series"].append(series2);
         }
 
@@ -465,8 +479,13 @@
   static void GetQueryOneAnswer(RestApiGetCall& call)
   {
     size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", ""));
+
     QueryAccessor query(call);
-    AnswerDicomMap(call, query->GetAnswer(index), call.HasArgument("simplify"));
+
+    DicomMap map;
+    query->GetAnswer(map, index);
+
+    AnswerDicomMap(call, map, call.HasArgument("simplify"));
   }
 
 
@@ -547,7 +566,9 @@
 
     // Ensure that the answer of interest does exist
     size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", ""));
-    query->GetAnswer(index);
+
+    DicomMap map;
+    query->GetAnswer(map, index);
 
     RestApi::AutoListChildren(call);
   }
--- a/OrthancServer/ParsedDicomFile.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -823,6 +823,18 @@
   }
 
 
+  ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : pimpl_(new PImpl)
+  {
+    std::auto_ptr<DcmDataset> dataset(ToDcmtkBridge::Convert(map));
+
+    // NOTE: This implies an unnecessary memory copy of the dataset, but no way to get around
+    // http://support.dcmtk.org/redmine/issues/544
+    std::auto_ptr<DcmFileFormat> fileFormat(new DcmFileFormat(dataset.get()));
+
+    pimpl_->file_.reset(fileFormat.release());
+  }
+
+
   ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl)
   {
     Setup(content, size);
@@ -851,15 +863,27 @@
   }
 
 
+  ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(&dicom));
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
+  {
+    pimpl_->file_.reset(new DcmFileFormat(dicom));
+  }
+
+
   ParsedDicomFile::~ParsedDicomFile()
   {
     delete pimpl_;
   }
 
 
-  void* ParsedDicomFile::GetDcmtkObject()
+  DcmFileFormat& ParsedDicomFile::GetDcmtkObject()
   {
-    return pimpl_->file_.get();
+    return *pimpl_->file_.get();
   }
 
 
--- a/OrthancServer/ParsedDicomFile.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Thu Nov 19 16:02:35 2015 +0100
@@ -39,6 +39,9 @@
 #include "../Core/Images/ImageBuffer.h"
 #include "../Core/IDynamicObject.h"
 
+class DcmDataset;
+class DcmFileFormat;
+
 namespace Orthanc
 {
   class ParsedDicomFile : public IDynamicObject
@@ -61,14 +64,20 @@
   public:
     ParsedDicomFile();  // Create a minimal DICOM instance
 
+    ParsedDicomFile(const DicomMap& map);
+
     ParsedDicomFile(const char* content,
                     size_t size);
 
     ParsedDicomFile(const std::string& content);
 
+    ParsedDicomFile(DcmDataset& dicom);
+
+    ParsedDicomFile(DcmFileFormat& dicom);
+
     ~ParsedDicomFile();
 
-    void* GetDcmtkObject();
+    DcmFileFormat& GetDcmtkObject();
 
     ParsedDicomFile* Clone();
 
--- a/OrthancServer/QueryRetrieveHandler.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -95,31 +95,24 @@
   }
 
 
-  const DicomMap& QueryRetrieveHandler::GetAnswer(size_t i)
+  void QueryRetrieveHandler::GetAnswer(DicomMap& target,
+                                       size_t i)
   {
     Run();
-
-    if (i >= answers_.GetSize())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    return answers_.GetAnswer(i);
+    answers_.GetAnswer(i).Convert(target);
   }
 
 
   void QueryRetrieveHandler::Retrieve(const std::string& target,
                                       size_t i)
   {
-    Run();
+    DicomMap map;
+    GetAnswer(map, i);
 
-    if (i >= answers_.GetSize())
     {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_);
+      locker.GetConnection().Move(target, map);
     }
-
-    ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), localAet_, modality_);
-    locker.GetConnection().Move(target, answers_.GetAnswer(i));
   }
 
 
--- a/OrthancServer/QueryRetrieveHandler.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/QueryRetrieveHandler.h	Thu Nov 19 16:02:35 2015 +0100
@@ -85,7 +85,8 @@
 
     size_t GetAnswerCount();
 
-    const DicomMap& GetAnswer(size_t i);
+    void GetAnswer(DicomMap& target,
+                   size_t i);
 
     void Retrieve(const std::string& target,
                   size_t i);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/IFindConstraint.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -0,0 +1,130 @@
+/**
+ * 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 "IFindConstraint.h"
+
+#include "ListConstraint.h"
+#include "RangeConstraint.h"
+#include "ValueConstraint.h"
+#include "WildcardConstraint.h"
+
+#include "../FromDcmtkBridge.h"
+#include "../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  IFindConstraint* IFindConstraint::ParseDicomConstraint(const DicomTag& tag,
+                                                         const std::string& dicomQuery,
+                                                         bool caseSensitive)
+  {
+    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+
+    if (vr == ValueRepresentation_Sequence)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if ((vr == ValueRepresentation_Date ||
+         vr == ValueRepresentation_DateTime ||
+         vr == ValueRepresentation_Time) &&
+        dicomQuery.find('-') != std::string::npos)
+    {
+      /**
+       * Range matching is only defined for TM, DA and DT value
+       * representations. This code fixes issues 35 and 37.
+       *
+       * Reference: "Range matching is not defined for types of
+       * Attributes other than dates and times", DICOM PS 3.4,
+       * C.2.2.2.5 ("Range Matching").
+       **/
+      size_t separator = dicomQuery.find('-');
+      std::string lower = dicomQuery.substr(0, separator);
+      std::string upper = dicomQuery.substr(separator + 1);
+      return new RangeConstraint(lower, upper, caseSensitive);
+    }
+    else if (dicomQuery.find('\\') != std::string::npos)
+    {
+      std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive));
+
+      std::vector<std::string> items;
+      Toolbox::TokenizeString(items, dicomQuery, '\\');
+
+      for (size_t i = 0; i < items.size(); i++)
+      {
+        constraint->AddAllowedValue(items[i]);
+      }
+
+      return constraint.release();
+    }
+    else if (dicomQuery.find('*') != std::string::npos ||
+             dicomQuery.find('?') != std::string::npos)
+    {
+      return new WildcardConstraint(dicomQuery, caseSensitive);
+    }
+    else
+    {
+      /**
+       * Case-insensitive match for PN value representation (Patient
+       * Name). Case-senstive match for all the other value
+       * representations.
+       *
+       * Reference: DICOM PS 3.4
+       *   - C.2.2.2.1 ("Single Value Matching") 
+       *   - C.2.2.2.4 ("Wild Card Matching")
+       * http://medical.nema.org/Dicom/2011/11_04pu.pdf
+       *
+       * "Except for Attributes with a PN Value Representation, only
+       * entities with values which match exactly the value specified in the
+       * request shall match. This matching is case-sensitive, i.e.,
+       * sensitive to the exact encoding of the key attribute value in
+       * character sets where a letter may have multiple encodings (e.g.,
+       * based on its case, its position in a word, or whether it is
+       * accented)
+       * 
+       * For Attributes with a PN Value Representation (e.g., Patient Name
+       * (0010,0010)), an application may perform literal matching that is
+       * either case-sensitive, or that is insensitive to some or all
+       * aspects of case, position, accent, or other character encoding
+       * variants."
+       *
+       * (0008,0018) UI SOPInstanceUID     => Case-sensitive
+       * (0008,0050) SH AccessionNumber    => Case-sensitive
+       * (0010,0020) LO PatientID          => Case-sensitive
+       * (0020,000D) UI StudyInstanceUID   => Case-sensitive
+       * (0020,000E) UI SeriesInstanceUID  => Case-sensitive
+       **/
+
+      return new ValueConstraint(dicomQuery, caseSensitive);
+    }
+  }
+}
--- a/OrthancServer/Search/IFindConstraint.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Search/IFindConstraint.h	Thu Nov 19 16:02:35 2015 +0100
@@ -49,5 +49,9 @@
                        const DicomTag& tag) const = 0;
 
     virtual bool Match(const std::string& value) const = 0;
+
+    static IFindConstraint* ParseDicomConstraint(const DicomTag& tag,
+                                                 const std::string& dicomQuery,
+                                                 bool caseSensitive);
   };
 }
--- a/OrthancServer/Search/LookupResource.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/Search/LookupResource.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -33,11 +33,6 @@
 #include "../PrecompiledHeadersServer.h"
 #include "LookupResource.h"
 
-#include "ListConstraint.h"
-#include "RangeConstraint.h"
-#include "ValueConstraint.h"
-#include "WildcardConstraint.h"
-
 #include "../../Core/OrthancException.h"
 #include "../../Core/FileStorage/StorageAccessor.h"
 #include "../ServerToolbox.h"
@@ -426,85 +421,16 @@
                                           const std::string& dicomQuery,
                                           bool caseSensitive)
   {
-    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
-
     // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
     // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
     if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
     {
       SetModalitiesInStudy(dicomQuery);
     }
-    else if ((vr == ValueRepresentation_Date ||
-              vr == ValueRepresentation_DateTime ||
-              vr == ValueRepresentation_Time) &&
-             dicomQuery.find('-') != std::string::npos)
-    {
-      /**
-       * Range matching is only defined for TM, DA and DT value
-       * representations. This code fixes issues 35 and 37.
-       *
-       * Reference: "Range matching is not defined for types of
-       * Attributes other than dates and times", DICOM PS 3.4,
-       * C.2.2.2.5 ("Range Matching").
-       **/
-      size_t separator = dicomQuery.find('-');
-      std::string lower = dicomQuery.substr(0, separator);
-      std::string upper = dicomQuery.substr(separator + 1);
-      Add(tag, new RangeConstraint(lower, upper, caseSensitive));
-    }
-    else if (dicomQuery.find('\\') != std::string::npos)
-    {
-      std::auto_ptr<ListConstraint> constraint(new ListConstraint(caseSensitive));
-
-      std::vector<std::string> items;
-      Toolbox::TokenizeString(items, dicomQuery, '\\');
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        constraint->AddAllowedValue(items[i]);
-      }
-
-      Add(tag, constraint.release());
-    }
-    else if (dicomQuery.find('*') != std::string::npos ||
-             dicomQuery.find('?') != std::string::npos)
+    else 
     {
-      Add(tag, new WildcardConstraint(dicomQuery, caseSensitive));
-    }
-    else
-    {
-      /**
-       * Case-insensitive match for PN value representation (Patient
-       * Name). Case-senstive match for all the other value
-       * representations.
-       *
-       * Reference: DICOM PS 3.4
-       *   - C.2.2.2.1 ("Single Value Matching") 
-       *   - C.2.2.2.4 ("Wild Card Matching")
-       * http://medical.nema.org/Dicom/2011/11_04pu.pdf
-       *
-       * "Except for Attributes with a PN Value Representation, only
-       * entities with values which match exactly the value specified in the
-       * request shall match. This matching is case-sensitive, i.e.,
-       * sensitive to the exact encoding of the key attribute value in
-       * character sets where a letter may have multiple encodings (e.g.,
-       * based on its case, its position in a word, or whether it is
-       * accented)
-       * 
-       * For Attributes with a PN Value Representation (e.g., Patient Name
-       * (0010,0010)), an application may perform literal matching that is
-       * either case-sensitive, or that is insensitive to some or all
-       * aspects of case, position, accent, or other character encoding
-       * variants."
-       *
-       * (0008,0018) UI SOPInstanceUID     => Case-sensitive
-       * (0008,0050) SH AccessionNumber    => Case-sensitive
-       * (0010,0020) LO PatientID          => Case-sensitive
-       * (0020,000D) UI StudyInstanceUID   => Case-sensitive
-       * (0020,000E) UI SeriesInstanceUID  => Case-sensitive
-      **/
-
-      Add(tag, new ValueConstraint(dicomQuery, caseSensitive));
+      Add(tag, IFindConstraint::ParseDicomConstraint(tag, dicomQuery, caseSensitive));
     }
   }
+
 }
--- a/OrthancServer/ServerEnumerations.h	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/ServerEnumerations.h	Thu Nov 19 16:02:35 2015 +0100
@@ -98,7 +98,8 @@
     ValueRepresentation_PatientName,
     ValueRepresentation_Date,
     ValueRepresentation_DateTime,
-    ValueRepresentation_Time
+    ValueRepresentation_Time,
+    ValueRepresentation_Sequence
   };
 
   enum DicomToJsonFormat
--- a/OrthancServer/main.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/OrthancServer/main.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -89,10 +89,32 @@
 };
 
 
+class OrthancWorklistRequestHandler : public IWorklistRequestHandler
+{
+private:
+  ServerContext& server_;
+
+public:
+  OrthancWorklistRequestHandler(ServerContext& context) :
+    server_(context)
+  {
+  }
+
+  virtual bool Handle(DicomFindAnswers& 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Nov 19 16:02:35 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	Thu Nov 19 11:57:32 2015 +0100
+++ b/Resources/ErrorCodes.json	Thu Nov 19 16:02:35 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"
   }
 ]
--- a/UnitTestsSources/FromDcmtkTests.cpp	Thu Nov 19 11:57:32 2015 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu Nov 19 16:02:35 2015 +0100
@@ -43,6 +43,7 @@
 #include "../Core/Images/PngWriter.h"
 #include "../Core/Uuid.h"
 #include "../Resources/EncodingTests.h"
+#include "../OrthancServer/DicomProtocol/DicomFindAnswers.h"
 
 #include <dcmtk/dcmdata/dcelem.h>
 
@@ -606,3 +607,33 @@
   ASSERT_EQ("application/octet-stream", mime);
   ASSERT_EQ("Pixels", content);
 }
+
+
+TEST(DicomFindAnswers, Basic)
+{
+  DicomFindAnswers a;
+
+  {
+    DicomMap m;
+    m.SetValue(DICOM_TAG_PATIENT_ID, "hello");
+    a.Add(m);
+  }
+
+  {
+    ParsedDicomFile d;
+    d.Replace(DICOM_TAG_PATIENT_ID, "my");
+    a.Add(d);
+  }
+
+  {
+    DicomMap m;
+    m.SetValue(DICOM_TAG_PATIENT_ID, "world");
+    a.Add(m);
+  }
+
+  Json::Value j;
+  a.ToJson(j, true);
+  ASSERT_EQ(3u, j.size());
+
+  //std::cout << j;
+}