changeset 1785:c131566b8252 dcmtk-3.6.1

integration mainline->dcmtk-3.6.1
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 18 Nov 2015 10:16:21 +0100
parents 1b82bb0446d2 (current diff) 2dbf25006f88 (diff)
children 4f2386d0f326
files Core/DicomFormat/DicomNullValue.h Core/DicomFormat/DicomString.h Core/MultiThreading/BagOfRunnablesBySteps.cpp Core/MultiThreading/BagOfRunnablesBySteps.h OrthancServer/DicomFindQuery.cpp OrthancServer/DicomFindQuery.h OrthancServer/ResourceFinder.cpp OrthancServer/ResourceFinder.h Resources/CMake/Compiler.cmake Resources/CMake/DcmtkConfiguration.cmake
diffstat 149 files changed, 11546 insertions(+), 4913 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Sep 23 10:29:06 2015 +0200
+++ b/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
@@ -10,8 +10,9 @@
 #   * Orthanc 0.3.1                  = version 2
 #   * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3
 #   * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4
-#   * Orthanc 0.8.5 -> mainline      = version 5
-set(ORTHANC_DATABASE_VERSION 5)
+#   * Orthanc 0.8.5 -> Orthanc 0.9.4 = version 5
+#   * Orthanc 0.9.5 -> mainline      = version 6
+set(ORTHANC_DATABASE_VERSION 6)
 
 
 #####################################################################
@@ -95,6 +96,7 @@
   Core/DicomFormat/DicomImageInformation.cpp
   Core/DicomFormat/DicomIntegerPixelAccessor.cpp
   Core/DicomFormat/DicomInstanceHasher.cpp
+  Core/DicomFormat/DicomValue.cpp
   Core/Enumerations.cpp
   Core/FileStorage/FilesystemStorage.cpp
   Core/FileStorage/StorageAccessor.cpp
@@ -108,6 +110,7 @@
   Core/HttpServer/MongooseServer.cpp
   Core/HttpServer/HttpFileSender.cpp
   Core/HttpServer/FilesystemHttpSender.cpp
+  Core/HttpServer/HttpContentNegociation.cpp
   Core/HttpServer/HttpStreamTranscoder.cpp
   Core/Logging.cpp
   Core/RestApi/RestApiCall.cpp
@@ -116,9 +119,9 @@
   Core/RestApi/RestApiPath.cpp
   Core/RestApi/RestApiOutput.cpp
   Core/RestApi/RestApi.cpp
-  Core/MultiThreading/BagOfRunnablesBySteps.cpp
   Core/MultiThreading/Mutex.cpp
   Core/MultiThreading/ReaderWriterLock.cpp
+  Core/MultiThreading/RunnableWorkersPool.cpp
   Core/MultiThreading/Semaphore.cpp
   Core/MultiThreading/SharedMessageQueue.cpp
   Core/Images/Font.cpp
@@ -145,21 +148,27 @@
 
 
 set(ORTHANC_SERVER_SOURCES
+  OrthancServer/DatabaseWrapper.cpp
+  OrthancServer/DatabaseWrapperBase.cpp
+  OrthancServer/DicomDirWriter.cpp
+  OrthancServer/DicomModification.cpp
   OrthancServer/DicomProtocol/DicomFindAnswers.cpp
   OrthancServer/DicomProtocol/DicomServer.cpp
   OrthancServer/DicomProtocol/DicomUserConnection.cpp
   OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
   OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp
-  OrthancServer/DicomModification.cpp
+  OrthancServer/ExportedResource.cpp
   OrthancServer/FromDcmtkBridge.cpp
-  OrthancServer/ParsedDicomFile.cpp
-  OrthancServer/DicomDirWriter.cpp
   OrthancServer/Internals/CommandDispatcher.cpp
+  OrthancServer/Internals/DicomImageDecoder.cpp
   OrthancServer/Internals/FindScp.cpp
   OrthancServer/Internals/MoveScp.cpp
   OrthancServer/Internals/StoreScp.cpp
-  OrthancServer/Internals/DicomImageDecoder.cpp
+  OrthancServer/LuaScripting.cpp
+  OrthancServer/OrthancFindRequestHandler.cpp
+  OrthancServer/OrthancHttpHandler.cpp
   OrthancServer/OrthancInitialization.cpp
+  OrthancServer/OrthancMoveRequestHandler.cpp
   OrthancServer/OrthancPeerParameters.cpp
   OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp
   OrthancServer/OrthancRestApi/OrthancRestApi.cpp
@@ -168,20 +177,21 @@
   OrthancServer/OrthancRestApi/OrthancRestModalities.cpp
   OrthancServer/OrthancRestApi/OrthancRestResources.cpp
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
-  OrthancServer/ServerIndex.cpp
-  OrthancServer/ToDcmtkBridge.cpp
-  OrthancServer/DatabaseWrapper.cpp
+  OrthancServer/ParsedDicomFile.cpp
+  OrthancServer/QueryRetrieveHandler.cpp
+  OrthancServer/Search/LookupIdentifierQuery.cpp
+  OrthancServer/Search/LookupResource.cpp
+  OrthancServer/Search/SetOfResources.cpp
+  OrthancServer/Search/ListConstraint.cpp
+  OrthancServer/Search/RangeConstraint.cpp
+  OrthancServer/Search/ValueConstraint.cpp
+  OrthancServer/Search/WildcardConstraint.cpp
   OrthancServer/ServerContext.cpp
   OrthancServer/ServerEnumerations.cpp
+  OrthancServer/ServerIndex.cpp
   OrthancServer/ServerToolbox.cpp
-  OrthancServer/OrthancFindRequestHandler.cpp
-  OrthancServer/OrthancMoveRequestHandler.cpp
-  OrthancServer/ExportedResource.cpp
-  OrthancServer/ResourceFinder.cpp
-  OrthancServer/DicomFindQuery.cpp
-  OrthancServer/QueryRetrieveHandler.cpp
-  OrthancServer/LuaScripting.cpp
-  OrthancServer/OrthancHttpHandler.cpp
+  OrthancServer/SliceOrdering.cpp
+  OrthancServer/ToDcmtkBridge.cpp
 
   # From "lua-scripting" branch
   OrthancServer/DicomInstanceToStore.cpp
@@ -222,6 +232,7 @@
     Plugins/Engine/OrthancPluginDatabase.cpp
     Plugins/Engine/OrthancPlugins.cpp
     Plugins/Engine/PluginsEnumerations.cpp
+    Plugins/Engine/PluginsErrorDictionary.cpp
     Plugins/Engine/PluginsManager.cpp
     Plugins/Engine/SharedLibrary.cpp
     )
--- a/Core/DicomFormat/DicomArray.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomArray.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -63,7 +63,8 @@
     for (size_t  i = 0; i < elements_.size(); i++)
     {
       DicomTag t = elements_[i]->GetTag();
-      std::string s = elements_[i]->GetValue().AsString();
+      const DicomValue& v = elements_[i]->GetValue();
+      std::string s = v.IsNull() ? "(null)" : v.GetContent();
       printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str());
     }
   }
--- a/Core/DicomFormat/DicomImageInformation.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomImageInformation.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -54,7 +54,7 @@
 
     try
     {
-      std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).AsString();
+      std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).GetContent();
       Toolbox::ToUpperCase(p);
 
       if (p == "RGB")
@@ -114,13 +114,13 @@
         photometric_ = PhotometricInterpretation_Unknown;
       }
 
-      width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).AsString());
-      height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).AsString());
-      bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString());
+      width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).GetContent());
+      height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).GetContent());
+      bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).GetContent());
 
       try
       {
-        samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString());
+        samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).GetContent());
       }
       catch (OrthancException&)
       {
@@ -129,7 +129,7 @@
 
       try
       {
-        bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).AsString());
+        bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).GetContent());
       }
       catch (OrthancException&)
       {
@@ -138,7 +138,7 @@
 
       try
       {
-        highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).AsString());
+        highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).GetContent());
       }
       catch (OrthancException&)
       {
@@ -147,7 +147,7 @@
 
       try
       {
-        pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString());
+        pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).GetContent());
       }
       catch (OrthancException&)
       {
@@ -160,7 +160,7 @@
         // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/
         try
         {
-          planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString());
+          planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).GetContent());
         }
         catch (OrthancException&)
         {
@@ -179,9 +179,9 @@
 
     try
     {
-      numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).AsString());
+      numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent());
     }
-    catch (OrthancException)
+    catch (OrthancException&)
     {
       // If the tag "NumberOfFrames" is absent, assume there is a single frame
       numberOfFrames_ = 1;
--- a/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -60,10 +60,10 @@
   {
     const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID);
 
-    Setup(patientId == NULL ? "" : patientId->AsString(),
-          instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString(),
-          instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString(),
-          instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString());
+    Setup(patientId == NULL ? "" : patientId->GetContent(),
+          instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(),
+          instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(),
+          instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent());
   }
 
   const std::string& DicomInstanceHasher::HashPatient()
--- a/Core/DicomFormat/DicomMap.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -35,7 +35,6 @@
 
 #include <stdio.h>
 #include <memory>
-#include "DicomString.h"
 #include "DicomArray.h"
 #include "../OrthancException.h"
 
@@ -57,10 +56,10 @@
   {
     //DicomTag(0x0010, 0x1020), // PatientSize
     //DicomTag(0x0010, 0x1030)  // PatientWeight
-    DicomTag(0x0008, 0x0020),   // StudyDate
+    DICOM_TAG_STUDY_DATE,
     DicomTag(0x0008, 0x0030),   // StudyTime
-    DicomTag(0x0008, 0x1030),   // StudyDescription
     DicomTag(0x0020, 0x0010),   // StudyID
+    DICOM_TAG_STUDY_DESCRIPTION,
     DICOM_TAG_ACCESSION_NUMBER,
     DICOM_TAG_STUDY_INSTANCE_UID
   };
@@ -70,10 +69,10 @@
     //DicomTag(0x0010, 0x1080), // MilitaryRank
     DicomTag(0x0008, 0x0021),   // SeriesDate
     DicomTag(0x0008, 0x0031),   // SeriesTime
-    DicomTag(0x0008, 0x0060),   // Modality
+    DICOM_TAG_MODALITY,
     DicomTag(0x0008, 0x0070),   // Manufacturer
     DicomTag(0x0008, 0x1010),   // StationName
-    DicomTag(0x0008, 0x103e),   // SeriesDescription
+    DICOM_TAG_SERIES_DESCRIPTION,
     DicomTag(0x0018, 0x0015),   // BodyPartExamined
     DicomTag(0x0018, 0x0024),   // SequenceName
     DicomTag(0x0018, 0x1030),   // ProtocolName
@@ -83,7 +82,8 @@
     DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS,
     DICOM_TAG_NUMBER_OF_SLICES,
     DICOM_TAG_NUMBER_OF_TIME_SLICES,
-    DICOM_TAG_SERIES_INSTANCE_UID
+    DICOM_TAG_SERIES_INSTANCE_UID,
+    DICOM_TAG_IMAGE_ORIENTATION_PATIENT    // New in db v6
   };
 
   static DicomTag instanceTags[] =
@@ -95,10 +95,41 @@
     DICOM_TAG_INSTANCE_NUMBER,
     DICOM_TAG_NUMBER_OF_FRAMES,
     DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
-    DICOM_TAG_SOP_INSTANCE_UID
+    DICOM_TAG_SOP_INSTANCE_UID,
+    DICOM_TAG_IMAGE_POSITION_PATIENT    // New in db v6
   };
 
 
+  void DicomMap::LoadMainDicomTags(const DicomTag*& tags,
+                                   size_t& size,
+                                   ResourceType level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 
 
   void DicomMap::SetValue(uint16_t group, 
@@ -283,6 +314,7 @@
     result.Remove(DICOM_TAG_NUMBER_OF_SLICES);
     result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
     result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
+    result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
   }
 
   void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
--- a/Core/DicomFormat/DicomMap.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomMap.h	Wed Nov 18 10:16:21 2015 +0100
@@ -34,7 +34,6 @@
 
 #include "DicomTag.h"
 #include "DicomValue.h"
-#include "DicomString.h"
 #include "../Enumerations.h"
 
 #include <set>
@@ -105,14 +104,14 @@
     void SetValue(const DicomTag& tag,
                   const std::string& str)
     {
-      SetValue(tag, new DicomString(str));
+      SetValue(tag, new DicomValue(str, false));
     }
 
     void SetValue(uint16_t group, 
                   uint16_t element, 
                   const std::string& str)
     {
-      SetValue(group, element, new DicomString(str));
+      SetValue(group, element, new DicomValue(str, false));
     }
 
     bool HasTag(uint16_t group, uint16_t element) const
@@ -173,5 +172,9 @@
     void Print(FILE* fp) const;
 
     void GetTags(std::set<DicomTag>& tags) const;
+
+    static void LoadMainDicomTags(const DicomTag*& tags,
+                                  size_t& size,
+                                  ResourceType level);
   };
 }
--- a/Core/DicomFormat/DicomNullValue.h	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * 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 "DicomValue.h"
-
-namespace Orthanc
-{
-  class DicomNullValue : public DicomValue
-  {
-  public:
-    DicomNullValue()
-    {
-    }
-
-    virtual DicomValue* Clone() const 
-    {
-      return new DicomNullValue();
-    }
-
-    virtual std::string AsString() const
-    {
-      return "(null)";
-    }
-
-    virtual bool IsNull() const
-    {
-      return true;
-    }
-  };
-}
--- a/Core/DicomFormat/DicomString.h	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * 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 "DicomValue.h"
-
-namespace Orthanc
-{
-  class DicomString : public DicomValue
-  {
-  private:
-    std::string value_;
-
-  public:
-    DicomString(const std::string& v) : value_(v)
-    {
-    }
-
-    DicomString(const char* v)
-    {
-      if (v)
-        value_ = v;
-      else
-        value_ = "";
-    }
-
-    virtual DicomValue* Clone() const 
-    {
-      return new DicomString(value_);
-    }
-
-    virtual std::string AsString() const
-    {
-      return value_;
-    }
-  };
-}
--- a/Core/DicomFormat/DicomTag.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -114,6 +114,12 @@
     if (*this == DICOM_TAG_PATIENT_NAME)
       return "PatientName";
 
+    if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT)
+      return "ImagePositionPatient";
+
+    if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
+      return "ImageOrientationPatient";
+
     return "";
   }
 
@@ -243,14 +249,4 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
-
-
-  bool DicomTag::IsIdentifier() const
-  {
-    return (*this == DICOM_TAG_PATIENT_ID ||
-            *this == DICOM_TAG_STUDY_INSTANCE_UID ||
-            *this == DICOM_TAG_ACCESSION_NUMBER ||
-            *this == DICOM_TAG_SERIES_INSTANCE_UID ||
-            *this == DICOM_TAG_SOP_INSTANCE_UID);
-  }
 }
--- a/Core/DicomFormat/DicomTag.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomTag.h	Wed Nov 18 10:16:21 2015 +0100
@@ -86,8 +86,6 @@
 
     static void AddTagsForModule(std::set<DicomTag>& target,
                                  DicomModule module);
-
-    bool IsIdentifier() const;
   };
 
   // Aliases for the most useful tags
@@ -109,6 +107,10 @@
   static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
   static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011);
 
+  static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030);
+  static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e);
+  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
+
   // The following is used for "modify/anonymize" operations
   static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
   static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002);
@@ -134,6 +136,8 @@
   static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
   static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
   static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+  static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
+  static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
 
   // Tags related to date and time
   static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomFormat/DicomValue.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,93 @@
+/**
+ * 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 "../PrecompiledHeaders.h"
+#include "DicomValue.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  DicomValue::DicomValue(const DicomValue& other) : 
+    type_(other.type_),
+    content_(other.content_)
+  {
+  }
+
+
+  DicomValue::DicomValue(const std::string& content,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String),
+    content_(content)
+  {
+  }
+  
+  
+  DicomValue::DicomValue(const char* data,
+                         size_t size,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String)
+  {
+    content_.assign(data, size);
+  }
+    
+  
+  const std::string& DicomValue::GetContent() const
+  {
+    if (type_ == Type_Null)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return content_;
+    }
+  }
+
+
+  DicomValue* DicomValue::Clone() const
+  {
+    return new DicomValue(*this);
+  }
+
+  
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+  void DicomValue::FormatDataUriScheme(std::string& target,
+                                       const std::string& mime) const
+  {
+    Toolbox::EncodeBase64(target, GetContent());
+    target.insert(0, "data:" + mime + ";base64,");
+  }
+#endif
+
+}
--- a/Core/DicomFormat/DicomValue.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/DicomFormat/DicomValue.h	Wed Nov 18 10:16:21 2015 +0100
@@ -32,22 +32,60 @@
 
 #pragma once
 
-#include "../IDynamicObject.h"
-
 #include <string>
+#include <boost/noncopyable.hpp>
 
 namespace Orthanc
 {
-  class DicomValue : public IDynamicObject
+  class DicomValue : public boost::noncopyable
   {
-  public:
-    virtual DicomValue* Clone() const = 0;
+  private:
+    enum Type
+    {
+      Type_Null,
+      Type_String,
+      Type_Binary
+    };
+
+    Type         type_;
+    std::string  content_;
+
+    DicomValue(const DicomValue& other);
 
-    virtual std::string AsString() const = 0;
+  public:
+    DicomValue() : type_(Type_Null)
+    {
+    }
+    
+    DicomValue(const std::string& content,
+               bool isBinary);
+    
+    DicomValue(const char* data,
+               size_t size,
+               bool isBinary);
+    
+    const std::string& GetContent() const;
 
-    virtual bool IsNull() const
+    bool IsNull() const
+    {
+      return type_ == Type_Null;
+    }
+
+    bool IsBinary() const
     {
-      return false;
+      return type_ == Type_Binary;
     }
+    
+    DicomValue* Clone() const;
+
+#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+    void FormatDataUriScheme(std::string& target,
+                             const std::string& mime) const;
+
+    void FormatDataUriScheme(std::string& target) const
+    {
+      FormatDataUriScheme(target, "application/octet-stream");
+    }
+#endif
   };
 }
--- a/Core/EnumerationDictionary.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/EnumerationDictionary.h	Wed Nov 18 10:16:21 2015 +0100
@@ -60,6 +60,11 @@
         stringToEnumeration_.clear();
       }
 
+      bool Contains(Enumeration value) const
+      {
+        return enumerationToString_.find(value) != enumerationToString_.end();
+      }
+
       void Add(Enumeration value, const std::string& str)
       {
         // Check if these values are free
--- a/Core/Enumerations.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Enumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -142,6 +142,18 @@
       case ErrorCode_BadFont:
         return "Badly formatted font file";
 
+      case ErrorCode_DatabasePlugin:
+        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
+
+      case ErrorCode_StorageAreaPlugin:
+        return "Error in the plugin implementing a custom storage area";
+
+      case ErrorCode_EmptyRequest:
+        return "The request is empty";
+
+      case ErrorCode_NotAcceptable:
+        return "Cannot send a response which is acceptable according to the Accept HTTP header";
+
       case ErrorCode_SQLiteNotOpened:
         return "SQLite: The database is not opened";
 
@@ -304,11 +316,24 @@
       case ErrorCode_DatabaseBackendAlreadyRegistered:
         return "Another plugin has already registered a custom database back-end";
 
-      case ErrorCode_DatabasePlugin:
-        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
+      case ErrorCode_DatabaseNotInitialized:
+        return "Plugin trying to call the database during its initialization";
+
+      case ErrorCode_SslDisabled:
+        return "Orthanc has been built without SSL support";
+
+      case ErrorCode_CannotOrderSlices:
+        return "Unable to order the slices of the series";
 
       default:
-        return "Unknown error code";
+        if (error >= ErrorCode_START_PLUGINS)
+        {
+          return "Error encountered within some plugin";
+        }
+        else
+        {
+          return "Unknown error code";
+        }
     }
   }
 
@@ -958,39 +983,6 @@
   }
 
 
-  const char* GetMimeType(FileContentType type)
-  {
-    switch (type)
-    {
-      case FileContentType_Dicom:
-        return "application/dicom";
-
-      case FileContentType_DicomAsJson:
-        return "application/json";
-
-      default:
-        return "application/octet-stream";
-    }
-  }
-
-
-  const char* GetFileExtension(FileContentType type)
-  {
-    switch (type)
-    {
-      case FileContentType_Dicom:
-        return ".dcm";
-
-      case FileContentType_DicomAsJson:
-        return ".json";
-
-      default:
-        // Unknown file type
-        return "";
-    }
-  }
-
-
   ResourceType GetChildResourceType(ResourceType type)
   {
     switch (type)
@@ -1142,8 +1134,18 @@
       case ErrorCode_Unauthorized:
         return HttpStatus_401_Unauthorized;
 
+      case ErrorCode_NotAcceptable:
+        return HttpStatus_406_NotAcceptable;
+
       default:
         return HttpStatus_500_InternalServerError;
     }
   }
+
+
+  bool IsUserContentType(FileContentType type)
+  {
+    return (type >= FileContentType_StartUser &&
+            type <= FileContentType_EndUser);
+  }
 }
--- a/Core/Enumerations.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Enumerations.h	Wed Nov 18 10:16:21 2015 +0100
@@ -77,6 +77,10 @@
     ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
     ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
     ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
     ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
     ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
     ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
@@ -131,7 +135,10 @@
     ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
     ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
     ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    ErrorCode_DatabasePlugin = 2038    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */
+    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_START_PLUGINS = 1000000
   };
 
   enum LogLevel
@@ -448,10 +455,6 @@
   bool GetDicomEncoding(Encoding& encoding,
                         const char* specificCharacterSet);
 
-  const char* GetMimeType(FileContentType type);
-
-  const char* GetFileExtension(FileContentType type);
-
   ResourceType GetChildResourceType(ResourceType type);
 
   ResourceType GetParentResourceType(ResourceType type);
@@ -461,4 +464,6 @@
   const char* GetDicomSpecificCharacterSet(Encoding encoding);
 
   HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
+
+  bool IsUserContentType(FileContentType type);
 }
--- a/Core/FileStorage/StorageAccessor.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/FileStorage/StorageAccessor.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -126,20 +126,53 @@
   }
 
 
+  void StorageAccessor::Read(Json::Value& content,
+                             const FileInfo& info)
+  {
+    std::string s;
+    Read(s, info);
+
+    Json::Reader reader;
+    if (!reader.parse(s, content))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
   void StorageAccessor::SetupSender(BufferHttpSender& sender,
-                                    const FileInfo& info)
+                                    const FileInfo& info,
+                                    const std::string& mime)
   {
     area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType());
-    sender.SetContentType(GetMimeType(info.GetContentType()));
-    sender.SetContentFilename(info.GetUuid() + std::string(GetFileExtension(info.GetContentType())));
+    sender.SetContentType(mime);
+
+    const char* extension;
+    switch (info.GetContentType())
+    {
+      case FileContentType_Dicom:
+        extension = ".dcm";
+        break;
+
+      case FileContentType_DicomAsJson:
+        extension = ".json";
+        break;
+
+      default:
+        // Non-standard content type
+        extension = "";
+    }
+
+    sender.SetContentFilename(info.GetUuid() + std::string(extension));
   }
 
 
   void StorageAccessor::AnswerFile(HttpOutput& output,
-                                   const FileInfo& info)
+                                   const FileInfo& info,
+                                   const std::string& mime)
   {
     BufferHttpSender sender;
-    SetupSender(sender, info);
+    SetupSender(sender, info, mime);
   
     HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
     output.Answer(transcoder);
@@ -147,10 +180,11 @@
 
 
   void StorageAccessor::AnswerFile(RestApiOutput& output,
-                                   const FileInfo& info)
+                                   const FileInfo& info,
+                                   const std::string& mime)
   {
     BufferHttpSender sender;
-    SetupSender(sender, info);
+    SetupSender(sender, info, mime);
   
     HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
     output.AnswerStream(transcoder);
--- a/Core/FileStorage/StorageAccessor.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/FileStorage/StorageAccessor.h	Wed Nov 18 10:16:21 2015 +0100
@@ -41,6 +41,7 @@
 #include <string>
 #include <boost/noncopyable.hpp>
 #include <stdint.h>
+#include <json/value.h>
 
 namespace Orthanc
 {
@@ -50,7 +51,8 @@
     IStorageArea&  area_;
 
     void SetupSender(BufferHttpSender& sender,
-                     const FileInfo& info);
+                     const FileInfo& info,
+                     const std::string& mime);
 
   public:
     StorageAccessor(IStorageArea& area) : area_(area)
@@ -75,15 +77,20 @@
     void Read(std::string& content,
               const FileInfo& info);
 
+    void Read(Json::Value& content,
+              const FileInfo& info);
+
     void Remove(const FileInfo& info)
     {
       area_.Remove(info.GetUuid(), info.GetContentType());
     }
 
     void AnswerFile(HttpOutput& output,
-                    const FileInfo& info);
+                    const FileInfo& info,
+                    const std::string& mime);
 
     void AnswerFile(RestApiOutput& output,
-                    const FileInfo& info);
+                    const FileInfo& info,
+                    const std::string& mime);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpContentNegociation.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,265 @@
+/**
+ * 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 "../PrecompiledHeaders.h"
+#include "HttpContentNegociation.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  HttpContentNegociation::Handler::Handler(const std::string& type,
+                                           const std::string& subtype,
+                                           IHandler& handler) :
+    type_(type),
+    subtype_(subtype),
+    handler_(handler)
+  {
+  }
+
+
+  bool HttpContentNegociation::Handler::IsMatch(const std::string& type,
+                                                const std::string& subtype) const
+  {
+    if (type == "*" && subtype == "*")
+    {
+      return true;
+    }
+        
+    if (subtype == "*" && type == type_)
+    {
+      return true;
+    }
+
+    return type == type_ && subtype == subtype_;
+  }
+
+
+  struct HttpContentNegociation::Reference : public boost::noncopyable
+  {
+    const Handler&  handler_;
+    uint8_t         level_;
+    float           quality_;
+
+    Reference(const Handler& handler,
+              const std::string& type,
+              const std::string& subtype,
+              float quality) :
+      handler_(handler),
+      quality_(quality)
+    {
+      if (type == "*" && subtype == "*")
+      {
+        level_ = 0;
+      }
+      else if (subtype == "*")
+      {
+        level_ = 1;
+      }
+      else
+      {
+        level_ = 2;
+      }
+    }
+      
+    bool operator< (const Reference& other) const
+    {
+      if (level_ < other.level_)
+      {
+        return true;
+      }
+
+      if (level_ > other.level_)
+      {
+        return false;
+      }
+
+      return quality_ < other.quality_;
+    }
+  };
+
+
+  bool HttpContentNegociation::SplitPair(std::string& first /* out */,
+                                         std::string& second /* out */,
+                                         const std::string& source,
+                                         char separator)
+  {
+    size_t pos = source.find(separator);
+
+    if (pos == std::string::npos)
+    {
+      return false;
+    }
+    else
+    {
+      first = Toolbox::StripSpaces(source.substr(0, pos));
+      second = Toolbox::StripSpaces(source.substr(pos + 1));
+      return true;      
+    }
+  }
+
+
+  float HttpContentNegociation::GetQuality(const Tokens& parameters)
+  {
+    for (size_t i = 1; i < parameters.size(); i++)
+    {
+      std::string key, value;
+      if (SplitPair(key, value, parameters[i], '=') &&
+          key == "q")
+      {
+        float quality;
+        bool ok = false;
+
+        try
+        {
+          quality = boost::lexical_cast<float>(value);
+          ok = (quality >= 0.0f && quality <= 1.0f);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+
+        if (ok)
+        {
+          return quality;
+        }
+        else
+        {
+          LOG(ERROR) << "Quality parameter out of range in a HTTP request (must be between 0 and 1): " << value;
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+    }
+
+    return 1.0f;  // Default quality
+  }
+
+
+  void HttpContentNegociation::SelectBestMatch(std::auto_ptr<Reference>& best,
+                                               const Handler& handler,
+                                               const std::string& type,
+                                               const std::string& subtype,
+                                               float quality)
+  {
+    std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality));
+
+    if (best.get() == NULL ||
+        *best < *match)
+    {
+      best = match;
+    }
+  }
+
+
+  void HttpContentNegociation::Register(const std::string& mime,
+                                        IHandler& handler)
+  {
+    std::string type, subtype;
+
+    if (SplitPair(type, subtype, mime, '/') &&
+        type != "*" &&
+        subtype != "*")
+    {
+      handlers_.push_back(Handler(type, subtype, handler));
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+    
+  bool HttpContentNegociation::Apply(const HttpHeaders& headers)
+  {
+    HttpHeaders::const_iterator accept = headers.find("accept");
+    if (accept != headers.end())
+    {
+      return Apply(accept->second);
+    }
+    else
+    {
+      return Apply("*/*");
+    }
+  }
+
+
+  bool HttpContentNegociation::Apply(const std::string& accept)
+  {
+    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+    // https://en.wikipedia.org/wiki/Content_negotiation
+    // http://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers
+
+    Tokens mediaRanges;
+    Toolbox::TokenizeString(mediaRanges, accept, ',');
+
+    std::auto_ptr<Reference> bestMatch;
+
+    for (Tokens::const_iterator it = mediaRanges.begin();
+         it != mediaRanges.end(); ++it)
+    {
+      Tokens parameters;
+      Toolbox::TokenizeString(parameters, *it, ';');
+
+      if (parameters.size() > 0)
+      {
+        float quality = GetQuality(parameters);
+
+        std::string type, subtype;
+        if (SplitPair(type, subtype, parameters[0], '/'))
+        {
+          for (Handlers::const_iterator it2 = handlers_.begin();
+               it2 != handlers_.end(); ++it2)
+          {
+            if (it2->IsMatch(type, subtype))
+            {
+              SelectBestMatch(bestMatch, *it2, type, subtype, quality);
+            }
+          }
+        }
+      }
+    }
+
+    if (bestMatch.get() == NULL)  // No match was found
+    {
+      return false;
+    }
+    else
+    {
+      bestMatch->handler_.Call();
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpContentNegociation.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,111 @@
+/**
+ * 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 <memory>
+#include <boost/noncopyable.hpp>
+#include <map>
+#include <list>
+#include <string>
+#include <vector>
+#include <stdint.h>
+
+
+namespace Orthanc
+{
+  class HttpContentNegociation : public boost::noncopyable
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+    class IHandler : public boost::noncopyable
+    {
+    public:
+      virtual ~IHandler()
+      {
+      }
+
+      virtual void Handle(const std::string& type,
+                          const std::string& subtype) = 0;
+    };
+
+  private:
+    struct Handler
+    {
+      std::string  type_;
+      std::string  subtype_;
+      IHandler&    handler_;
+
+      Handler(const std::string& type,
+              const std::string& subtype,
+              IHandler& handler);
+
+      bool IsMatch(const std::string& type,
+                   const std::string& subtype) const;
+
+      void Call() const
+      {
+        handler_.Handle(type_, subtype_);
+      }
+   };
+
+
+    struct Reference;
+
+    typedef std::vector<std::string>  Tokens;
+    typedef std::list<Handler>   Handlers;
+
+    Handlers  handlers_;
+
+
+    static bool SplitPair(std::string& first /* out */,
+                          std::string& second /* out */,
+                          const std::string& source,
+                          char separator);
+
+    static float GetQuality(const Tokens& parameters);
+
+    static void SelectBestMatch(std::auto_ptr<Reference>& best,
+                                const Handler& handler,
+                                const std::string& type,
+                                const std::string& subtype,
+                                float quality);
+
+  public:
+    void Register(const std::string& mime,
+                  IHandler& handler);
+    
+    bool Apply(const HttpHeaders& headers);
+
+    bool Apply(const std::string& accept);
+  };
+}
--- a/Core/HttpServer/HttpOutput.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -302,15 +302,7 @@
     
     stateMachine_.ClearHeaders();
     stateMachine_.SetHttpStatus(status);
-
-    if (describeErrors_)
-    {
-      stateMachine_.SendBody(message, messageSize);
-    }
-    else
-    {
-      stateMachine_.SendBody(NULL, 0);
-    }
+    stateMachine_.SendBody(message, messageSize);
   }
 
 
--- a/Core/HttpServer/HttpOutput.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/HttpOutput.h	Wed Nov 18 10:16:21 2015 +0100
@@ -114,7 +114,6 @@
     StateMachine stateMachine_;
     bool         isDeflateAllowed_;
     bool         isGzipAllowed_;
-    bool         describeErrors_;
 
     HttpCompression GetPreferredCompression(size_t bodySize) const;
 
@@ -123,8 +122,7 @@
                bool isKeepAlive) : 
       stateMachine_(stream, isKeepAlive),
       isDeflateAllowed_(false),
-      isGzipAllowed_(false),
-      describeErrors_(true)
+      isGzipAllowed_(false)
     {
     }
 
@@ -148,16 +146,6 @@
       return isGzipAllowed_;
     }
 
-    void SetDescribeErrorsEnabled(bool enabled)
-    {
-      describeErrors_ = enabled;
-    }
-
-    bool IsDescribeErrorsEnabled() const
-    {
-      return describeErrors_;      
-    }
-
     void SendStatus(HttpStatus status,
 		    const char* message,
 		    size_t messageSize);
--- a/Core/HttpServer/HttpToolbox.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/HttpToolbox.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -201,10 +201,9 @@
   bool HttpToolbox::SimpleGet(std::string& result,
                               IHttpHandler& handler,
                               RequestOrigin origin,
-                              const std::string& uri)
+                              const std::string& uri,
+                              const IHttpHandler::Arguments& httpHeaders)
   {
-    IHttpHandler::Arguments headers;  // No HTTP header
-
     UriComponents curi;
     IHttpHandler::GetArguments getArguments;
     ParseGetQuery(curi, getArguments, uri.c_str());
@@ -213,7 +212,7 @@
     HttpOutput http(stream, false /* no keep alive */);
 
     if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Get, curi, 
-                       headers, getArguments, NULL /* no body for GET */, 0))
+                       httpHeaders, getArguments, NULL /* no body for GET */, 0))
     {
       stream.GetOutput(result);
       return true;
@@ -225,6 +224,16 @@
   }
 
 
+  bool HttpToolbox::SimpleGet(std::string& result,
+                              IHttpHandler& handler,
+                              RequestOrigin origin,
+                              const std::string& uri)
+  {
+    IHttpHandler::Arguments headers;  // No HTTP header
+    return SimpleGet(result, handler, origin, uri, headers);
+  }
+
+
   static bool SimplePostOrPut(std::string& result,
                               IHttpHandler& handler,
                               RequestOrigin origin,
--- a/Core/HttpServer/HttpToolbox.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/HttpToolbox.h	Wed Nov 18 10:16:21 2015 +0100
@@ -65,6 +65,12 @@
                           RequestOrigin origin,
                           const std::string& uri);
 
+    static bool SimpleGet(std::string& result,
+                          IHttpHandler& handler,
+                          RequestOrigin origin,
+                          const std::string& uri,
+                          const IHttpHandler::Arguments& httpHeaders);
+
     static bool SimplePost(std::string& result,
                            IHttpHandler& handler,
                            RequestOrigin origin,
--- a/Core/HttpServer/MongooseServer.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -36,7 +36,6 @@
 #include "MongooseServer.h"
 
 #include "../Logging.h"
-#include "../OrthancException.h"
 #include "../ChunkedBuffer.h"
 #include "HttpToolbox.h"
 #include "mongoose.h"
@@ -583,7 +582,6 @@
 
     MongooseOutputStream stream(connection);
     HttpOutput output(stream, that->IsKeepAliveEnabled());
-    output.SetDescribeErrorsEnabled(that->IsDescribeErrorsEnabled());
 
     // Check remote calls
     if (!that->IsRemoteAccessAllowed() &&
@@ -735,12 +733,12 @@
       }
       catch (boost::bad_lexical_cast&)
       {
-        throw OrthancException(ErrorCode_BadParameterType, HttpStatus_400_BadRequest);
+        throw OrthancException(ErrorCode_BadParameterType);
       }
       catch (std::runtime_error&)
       {
         // Presumably an error while parsing the JSON body
-        throw OrthancException(ErrorCode_BadRequest, HttpStatus_400_BadRequest);
+        throw OrthancException(ErrorCode_BadRequest);
       }
 
       if (!found)
@@ -751,22 +749,17 @@
     catch (OrthancException& e)
     {
       // Using this candidate handler results in an exception
-      LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
-
-      Json::Value message = Json::objectValue;
-      message["HttpError"] = EnumerationToString(e.GetHttpStatus());
-      message["HttpStatus"] = e.GetHttpStatus();
-      message["Message"] = e.What();
-      message["Method"] = EnumerationToString(method);
-      message["OrthancError"] = EnumerationToString(e.GetErrorCode());
-      message["OrthancStatus"] = e.GetErrorCode();
-      message["Uri"] = request->uri;
-
-      std::string info = message.toStyledString();
-
       try
       {
-	output.SendStatus(e.GetHttpStatus(), info);
+        if (that->GetExceptionFormatter() == NULL)
+        {
+          LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
+          output.SendStatus(e.GetHttpStatus());
+        }
+        else
+        {
+          that->GetExceptionFormatter()->Format(output, e, method, request->uri);
+        }
       }
       catch (OrthancException&)
       {
@@ -832,7 +825,7 @@
     filter_ = NULL;
     keepAlive_ = false;
     httpCompression_ = true;
-    describeErrors_ = true;
+    exceptionFormatter_ = NULL;
 
 #if ORTHANC_SSL_ENABLED == 1
     // Check for the Heartbleed exploit
@@ -938,7 +931,7 @@
 #if ORTHANC_SSL_ENABLED == 0
     if (enabled)
     {
-      throw OrthancException("Orthanc has been built without SSL support");
+      throw OrthancException(ErrorCode_SslDisabled);
     }
     else
     {
@@ -983,18 +976,20 @@
     LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled");
   }
   
-  void MongooseServer::SetDescribeErrorsEnabled(bool enabled)
-  {
-    describeErrors_ = enabled;
-    LOG(INFO) << "Description of the errors in the HTTP answers is " << (enabled ? "enabled" : "disabled");
-  }
-
   void MongooseServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter)
   {
     Stop();
     filter_ = &filter;
   }
 
+
+  void MongooseServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter)
+  {
+    Stop();
+    exceptionFormatter_ = &formatter;
+  }
+
+
   bool MongooseServer::IsValidBasicHttpAuthentication(const std::string& basic) const
   {
     return registeredUsers_.find(basic) != registeredUsers_.end();
--- a/Core/HttpServer/MongooseServer.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/HttpServer/MongooseServer.h	Wed Nov 18 10:16:21 2015 +0100
@@ -34,6 +34,8 @@
 
 #include "IHttpHandler.h"
 
+#include "../OrthancException.h"
+
 #include <list>
 #include <map>
 #include <set>
@@ -57,6 +59,21 @@
                            const char* username) const = 0;
   };
 
+
+  class IHttpExceptionFormatter
+  {
+  public:
+    virtual ~IHttpExceptionFormatter()
+    {
+    }
+
+    virtual void Format(HttpOutput& output,
+                        const OrthancException& exception,
+                        HttpMethod method,
+                        const char* uri) = 0;
+  };
+
+
   class MongooseServer
   {
   private:
@@ -77,7 +94,7 @@
     IIncomingHttpRequestFilter* filter_;
     bool keepAlive_;
     bool httpCompression_;
-    bool describeErrors_;
+    IHttpExceptionFormatter* exceptionFormatter_;
   
     bool IsRunning() const;
 
@@ -144,13 +161,6 @@
 
     void SetHttpCompressionEnabled(bool enabled);
 
-    bool IsDescribeErrorsEnabled() const
-    {
-      return describeErrors_;
-    }
-
-    void SetDescribeErrorsEnabled(bool enabled);
-
     const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const
     {
       return filter_;
@@ -170,5 +180,12 @@
     }
 
     IHttpHandler& GetHandler() const;
+
+    void SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter);
+
+    IHttpExceptionFormatter* GetExceptionFormatter()
+    {
+      return exceptionFormatter_;
+    }
   };
 }
--- a/Core/Images/Font.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Images/Font.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -118,7 +118,7 @@
           throw OrthancException(ErrorCode_BadFont);
         }
 
-        c->bitmap_[j] = value;
+        c->bitmap_[j] = static_cast<uint8_t>(value);
       }
 
       int index = boost::lexical_cast<int>(characters[i]);
@@ -166,7 +166,7 @@
     unsigned int width = MyMin(character.width_, target.GetWidth() - x);
     unsigned int height = MyMin(character.height_, target.GetHeight() - y);
 
-    uint8_t bpp = target.GetBytesPerPixel();
+    unsigned int bpp = target.GetBytesPerPixel();
 
     // Blit the font bitmap OVER the target image
     // https://en.wikipedia.org/wiki/Alpha_compositing
@@ -184,7 +184,8 @@
           for (unsigned int cx = left; cx < width; cx++, pos++, p++)
           {
             uint16_t alpha = character.bitmap_[pos];
-            *p = (alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(*p)) >> 8;
+            uint16_t value = alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(*p);
+            *p = static_cast<uint8_t>(value >> 8);
           }
 
           break;
@@ -196,9 +197,11 @@
           for (unsigned int cx = left; cx < width; cx++, pos++, p += 3)
           {
             uint16_t alpha = character.bitmap_[pos];
-            p[0] = (alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(p[0])) >> 8;
-            p[1] = (alpha * static_cast<uint16_t>(color[1]) + (255 - alpha) * static_cast<uint16_t>(p[1])) >> 8;
-            p[2] = (alpha * static_cast<uint16_t>(color[2]) + (255 - alpha) * static_cast<uint16_t>(p[2])) >> 8;
+            for (uint8_t i = 0; i < 3; i++)
+            {
+              uint16_t value = alpha * static_cast<uint16_t>(color[i]) + (255 - alpha) * static_cast<uint16_t>(p[i]);
+              p[i] = static_cast<uint8_t>(value >> 8);
+            }
           }
 
           break;
--- a/Core/Images/JpegWriter.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Images/JpegWriter.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -69,7 +69,7 @@
                        unsigned int width,
                        unsigned int height,
                        PixelFormat format,
-                       int quality)
+                       uint8_t quality)
   {
     cinfo.image_width = width;
     cinfo.image_height = height;
--- a/Core/Images/JpegWriter.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Images/JpegWriter.h	Wed Nov 18 10:16:21 2015 +0100
@@ -42,7 +42,7 @@
   class JpegWriter
   {
   private:
-    int  quality_;
+    uint8_t  quality_;
 
   public:
     JpegWriter() : quality_(90)
--- a/Core/Logging.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Logging.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -144,9 +144,7 @@
     std::ostream* warning_;
     std::ostream* info_;
 
-    std::auto_ptr<std::ofstream> errorFile_;
-    std::auto_ptr<std::ofstream> warningFile_;
-    std::auto_ptr<std::ofstream> infoFile_;
+    std::auto_ptr<std::ofstream> file_;
 
     LoggingState() : 
       infoEnabled_(false),
@@ -172,14 +170,14 @@
   {
     static void GetLogPath(boost::filesystem::path& log,
                            boost::filesystem::path& link,
-                           const char* level,
+                           const std::string& suffix,
                            const std::string& directory)
     {
       /**
          From Google Log documentation:
 
          Unless otherwise specified, logs will be written to the filename
-         "<program name>.<hostname>.<user name>.log.<severity level>.",
+         "<program name>.<hostname>.<user name>.log<suffix>.",
          followed by the date, time, and pid (you can't prevent the date,
          time, and pid from being in the filename).
 
@@ -208,21 +206,17 @@
 
       std::string programName = exe.filename().replace_extension("").string();
 
-      log = (root / (programName + ".log." +
-                     std::string(level) + "." +
-                     std::string(date)));
-
-      link = (root / (programName + "." + std::string(level)));
+      log = (root / (programName + ".log" + suffix + "." + std::string(date)));
+      link = (root / (programName + ".log" + suffix));
     }
 
 
-    static void PrepareLogFile(std::ostream*& stream,
-                               std::auto_ptr<std::ofstream>& file,
-                               const char* level,
+    static void PrepareLogFile(std::auto_ptr<std::ofstream>& file,
+                               const std::string& suffix,
                                const std::string& directory)
     {
       boost::filesystem::path log, link;
-      GetLogPath(log, link, level, directory);
+      GetLogPath(log, link, suffix, directory);
 
 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
       boost::filesystem::remove(link);
@@ -230,7 +224,6 @@
 #endif
 
       file.reset(new std::ofstream(log.string().c_str()));
-      stream = file.get();
     }
 
 
@@ -273,9 +266,11 @@
       boost::mutex::scoped_lock lock(loggingMutex_);
       assert(loggingState_.get() != NULL);
 
-      PrepareLogFile(loggingState_->error_,   loggingState_->errorFile_,   "ERROR", path);
-      PrepareLogFile(loggingState_->warning_, loggingState_->warningFile_, "WARNING", path);
-      PrepareLogFile(loggingState_->info_,    loggingState_->infoFile_,    "INFO", path);
+      PrepareLogFile(loggingState_->file_, "" /* no suffix */, path);
+
+      loggingState_->warning_ = loggingState_->file_.get();
+      loggingState_->error_ = loggingState_->file_.get();
+      loggingState_->info_ = loggingState_->file_.get();
     }
 
     InternalLogger::InternalLogger(const char* level,
--- a/Core/Lua/LuaContext.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Lua/LuaContext.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -48,6 +48,20 @@
 
 namespace Orthanc
 {
+  static bool OnlyContainsDigits(const std::string& s)
+  {
+    for (size_t i = 0; i < s.size(); i++)
+    {
+      if (!isdigit(s[i]))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+  
+
   LuaContext& LuaContext::GetLuaContext(lua_State *state)
   {
     const void* value = GetGlobalVariable(state, "_LuaContext");
@@ -128,14 +142,21 @@
     LuaContext& that = GetLuaContext(state);
 
     int nArgs = lua_gettop(state);
-    if (nArgs != 1)
+    if ((nArgs != 1 && nArgs != 2) ||
+        (nArgs == 2 && !lua_isboolean(state, 2)))
     {
       lua_pushnil(state);
       return 1;
     }
 
+    bool keepStrings = false;
+    if (nArgs == 2)
+    {
+      keepStrings = lua_toboolean(state, 2) ? true : false;
+    }
+
     Json::Value json;
-    that.GetJson(json, 1);
+    that.GetJson(json, 1, keepStrings);
 
     Json::FastWriter writer;
     std::string s = writer.write(json);
@@ -376,7 +397,8 @@
 
 
   void LuaContext::GetJson(Json::Value& result,
-                           int top)
+                           int top,
+                           bool keepStrings)
   {
     if (lua_istable(lua_, top))
     {
@@ -401,14 +423,15 @@
         // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
         std::string key(lua_tostring(lua_, -1));
         Json::Value v;
-        GetJson(v, -2);
+        GetJson(v, -2, keepStrings);
 
         tmp[key] = v;
 
         size += 1;
         try
         {
-          if (boost::lexical_cast<size_t>(key) != size)
+          if (!OnlyContainsDigits(key) ||
+              boost::lexical_cast<size_t>(key) != size)
           {
             isArray = false;
           }
@@ -446,11 +469,13 @@
     {
       result = Json::nullValue;
     }
-    else if (lua_isboolean(lua_, top))
+    else if (!keepStrings &&
+             lua_isboolean(lua_, top))
     {
       result = lua_toboolean(lua_, top) ? true : false;
     }
-    else if (lua_isnumber(lua_, top))
+    else if (!keepStrings &&
+             lua_isnumber(lua_, top))
     {
       // Convert to "int" if truncation does not loose precision
       double value = static_cast<double>(lua_tonumber(lua_, top));
--- a/Core/Lua/LuaContext.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Lua/LuaContext.h	Wed Nov 18 10:16:21 2015 +0100
@@ -72,7 +72,8 @@
                          const std::string& command);
 
     void GetJson(Json::Value& result,
-                 int top);
+                 int top,
+                 bool keepStrings);
     
   public:
     LuaContext();
--- a/Core/Lua/LuaFunctionCall.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Lua/LuaFunctionCall.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -130,10 +130,11 @@
   }
 
 
-  void LuaFunctionCall::ExecuteToJson(Json::Value& result)
+  void LuaFunctionCall::ExecuteToJson(Json::Value& result,
+                                      bool keepStrings)
   {
     ExecuteInternal(1);
-    context_.GetJson(result, lua_gettop(context_.lua_));
+    context_.GetJson(result, lua_gettop(context_.lua_), keepStrings);
   }
 
 
--- a/Core/Lua/LuaFunctionCall.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Lua/LuaFunctionCall.h	Wed Nov 18 10:16:21 2015 +0100
@@ -69,7 +69,8 @@
 
     bool ExecutePredicate();
 
-    void ExecuteToJson(Json::Value& result);                    
+    void ExecuteToJson(Json::Value& result,
+                       bool keepStrings);
 
     void ExecuteToString(std::string& result);
   };
--- a/Core/MultiThreading/BagOfRunnablesBySteps.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/**
- * 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 "../PrecompiledHeaders.h"
-#include "BagOfRunnablesBySteps.h"
-
-#include "../Logging.h"
-
-#include <stack>
-#include <boost/thread.hpp>
-
-namespace Orthanc
-{
-  struct BagOfRunnablesBySteps::PImpl
-  {
-    bool continue_;
-    bool stopFinishListener_;
-
-    boost::mutex mutex_;
-    boost::condition_variable oneThreadIsStopped_;
-    boost::condition_variable oneThreadIsJoined_;
-
-    // The list of threads that are waiting to be joined.
-    typedef std::stack<IRunnableBySteps*>  StoppedThreads;
-    StoppedThreads  stoppedThreads_;
-
-    // The set of active runnables, i.e. the runnables that have not
-    // finished their job yet, plus the runnables that have not been
-    // joined yet.
-    typedef std::map<IRunnableBySteps*, boost::thread*>  ActiveThreads;
-    ActiveThreads  activeThreads_;
-
-    // The thread that joins the runnables after they stop
-    std::auto_ptr<boost::thread> finishListener_;
-  };
-
-
-
-  void BagOfRunnablesBySteps::RunnableThread(BagOfRunnablesBySteps* bag,
-                                             IRunnableBySteps* runnable)
-  {
-    while (bag->pimpl_->continue_)
-    {
-      if (!runnable->Step())
-      {
-        break;
-      }
-    }
-
-    {
-      // Register this runnable as having stopped
-      boost::mutex::scoped_lock lock(bag->pimpl_->mutex_);
-      bag->pimpl_->stoppedThreads_.push(runnable);
-      bag->pimpl_->oneThreadIsStopped_.notify_one();
-    }
-  }
-
-  
-  void BagOfRunnablesBySteps::FinishListener(BagOfRunnablesBySteps* bag)
-  {
-    boost::mutex::scoped_lock lock(bag->pimpl_->mutex_);
-
-    while (!bag->pimpl_->stopFinishListener_)
-    {
-      while (!bag->pimpl_->stoppedThreads_.empty())
-      {
-        std::auto_ptr<IRunnableBySteps> r(bag->pimpl_->stoppedThreads_.top());
-        bag->pimpl_->stoppedThreads_.pop();
-
-        assert(r.get() != NULL);
-        assert(bag->pimpl_->activeThreads_.find(r.get()) != bag->pimpl_->activeThreads_.end());
-
-        std::auto_ptr<boost::thread> t(bag->pimpl_->activeThreads_[r.get()]);
-        bag->pimpl_->activeThreads_.erase(r.get());
-
-        assert(t.get() != NULL);
-        assert(bag->pimpl_->activeThreads_.find(r.get()) == bag->pimpl_->activeThreads_.end());
-
-        if (t->joinable())
-        {
-          t->join();
-        }
-
-        bag->pimpl_->oneThreadIsJoined_.notify_one();
-      }
-
-      bag->pimpl_->oneThreadIsStopped_.wait(lock);
-    }
-  }
-
-
-  BagOfRunnablesBySteps::BagOfRunnablesBySteps() : pimpl_(new PImpl)
-  {
-    pimpl_->continue_ = true;
-    pimpl_->stopFinishListener_ = false;
-
-    // Everyting is set up, the finish listener can be started
-    pimpl_->finishListener_.reset(new boost::thread(FinishListener, this));
-  }
-
-
-  BagOfRunnablesBySteps::~BagOfRunnablesBySteps()
-  {
-    if (!pimpl_->stopFinishListener_)
-    {
-      LOG(ERROR) << "INTERNAL ERROR: BagOfRunnablesBySteps::Finalize() should be invoked manually to avoid mess in the destruction order!";
-      Finalize();
-    }
-  }
-
-
-  void BagOfRunnablesBySteps::Add(IRunnableBySteps* runnable)
-  {
-    // Make sure the runnable is deleted is something goes wrong
-    std::auto_ptr<IRunnableBySteps> runnableRabi(runnable);
-
-    boost::mutex::scoped_lock lock(pimpl_->mutex_);
-    boost::thread* t(new boost::thread(RunnableThread, this, runnable));
-
-    pimpl_->activeThreads_.insert(std::make_pair(runnableRabi.release(), t));
-  }
-
-
-  void BagOfRunnablesBySteps::StopAll()
-  {
-    boost::mutex::scoped_lock lock(pimpl_->mutex_);
-    pimpl_->continue_ = false;
-
-    while (pimpl_->activeThreads_.size() > 0)
-    {
-      pimpl_->oneThreadIsJoined_.wait(lock);
-    }
-
-    pimpl_->continue_ = true;
-  }
-
-
-
-  void BagOfRunnablesBySteps::Finalize()
-  {
-    if (!pimpl_->stopFinishListener_)
-    {
-      StopAll();
-
-      // Stop the finish listener
-      pimpl_->stopFinishListener_ = true;
-      pimpl_->oneThreadIsStopped_.notify_one();  // Awakens the listener
-
-      if (pimpl_->finishListener_->joinable())
-      {
-        pimpl_->finishListener_->join();
-      }
-    }
-  }
-
-}
--- a/Core/MultiThreading/BagOfRunnablesBySteps.h	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * 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 "IRunnableBySteps.h"
-
-#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
-
-namespace Orthanc
-{
-  class BagOfRunnablesBySteps : public boost::noncopyable
-  {
-  private:
-    struct PImpl;
-    boost::shared_ptr<PImpl> pimpl_;
-
-    static void RunnableThread(BagOfRunnablesBySteps* bag,
-                               IRunnableBySteps* runnable);
-
-    static void FinishListener(BagOfRunnablesBySteps* bag);
-
-  public:
-    BagOfRunnablesBySteps();
-
-    ~BagOfRunnablesBySteps();
-
-    void Add(IRunnableBySteps* runnable);
-
-    void StopAll();
-
-    void Finalize();
-  };
-}
--- a/Core/MultiThreading/IRunnableBySteps.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/MultiThreading/IRunnableBySteps.h	Wed Nov 18 10:16:21 2015 +0100
@@ -32,9 +32,11 @@
 
 #pragma once
 
+#include "../IDynamicObject.h"
+
 namespace Orthanc
 {
-  class IRunnableBySteps
+  class IRunnableBySteps : public IDynamicObject
   {
   public:
     virtual ~IRunnableBySteps()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/RunnableWorkersPool.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,157 @@
+/**
+ * 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 "../PrecompiledHeaders.h"
+#include "RunnableWorkersPool.h"
+
+#include "SharedMessageQueue.h"
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+namespace Orthanc
+{
+  struct RunnableWorkersPool::PImpl
+  {
+    class Worker
+    {
+    private:
+      const bool&           continue_;
+      SharedMessageQueue&   queue_;
+      boost::thread         thread_;
+ 
+      static void WorkerThread(Worker* that)
+      {
+        while (that->continue_)
+        {
+          std::auto_ptr<IDynamicObject>  obj(that->queue_.Dequeue(100));
+          if (obj.get() != NULL)
+          {
+            try
+            {
+              IRunnableBySteps& runnable = *dynamic_cast<IRunnableBySteps*>(obj.get());
+            
+              bool wishToContinue = runnable.Step();
+
+              if (wishToContinue)
+              {
+                // The runnable wishes to continue, reinsert it at the beginning of the queue
+                that->queue_.Enqueue(obj.release());
+              }
+            }
+            catch (OrthancException& e)
+            {
+              LOG(ERROR) << "Exception in a pool of working threads: " << e.What();
+            }
+          }
+        }
+      }
+
+    public:
+      Worker(const bool& globalContinue,
+             SharedMessageQueue& queue) : 
+        continue_(globalContinue),
+        queue_(queue)
+      {
+        thread_ = boost::thread(WorkerThread, this);
+      }
+
+      void Join()
+      {
+        if (thread_.joinable())
+        {
+          thread_.join();
+        }
+      }
+    };
+
+
+    bool                  continue_;
+    std::vector<Worker*>  workers_;
+    SharedMessageQueue    queue_;
+  };
+
+
+
+  RunnableWorkersPool::RunnableWorkersPool(size_t countWorkers) : pimpl_(new PImpl)
+  {
+    pimpl_->continue_ = true;
+
+    if (countWorkers <= 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    pimpl_->workers_.resize(countWorkers);
+
+    for (size_t i = 0; i < countWorkers; i++)
+    {
+      pimpl_->workers_[i] = new PImpl::Worker(pimpl_->continue_, pimpl_->queue_);
+    }
+  }
+
+
+  void RunnableWorkersPool::Stop()
+  {
+    if (pimpl_->continue_)
+    {
+      pimpl_->continue_ = false;
+
+      for (size_t i = 0; i < pimpl_->workers_.size(); i++)
+      {
+        PImpl::Worker* worker = pimpl_->workers_[i];
+
+        if (worker != NULL)
+        {
+          worker->Join();
+          delete worker;
+        }
+      }
+    }
+  }
+
+
+  RunnableWorkersPool::~RunnableWorkersPool()
+  {
+    Stop();
+  }
+
+
+  void RunnableWorkersPool::Add(IRunnableBySteps* runnable)
+  {
+    if (!pimpl_->continue_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    pimpl_->queue_.Enqueue(runnable);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/RunnableWorkersPool.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,56 @@
+/**
+ * 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 "IRunnableBySteps.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class RunnableWorkersPool : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Stop();
+
+  public:
+    RunnableWorkersPool(size_t countWorkers);
+
+    ~RunnableWorkersPool();
+
+    void Add(IRunnableBySteps* runnable);  // Takes the ownership
+  };
+}
--- a/Core/OrthancException.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/OrthancException.h	Wed Nov 18 10:16:21 2015 +0100
@@ -32,6 +32,7 @@
 
 #pragma once
 
+#include <stdint.h>
 #include <string>
 #include "Enumerations.h"
 
@@ -40,7 +41,7 @@
   class OrthancException
   {
   protected:
-    ErrorCode errorCode_;
+    ErrorCode  errorCode_;
     HttpStatus httpStatus_;
 
   public:
--- a/Core/SQLite/Connection.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/SQLite/Connection.h	Wed Nov 18 10:16:21 2015 +0100
@@ -46,7 +46,7 @@
 struct sqlite3;
 struct sqlite3_stmt;
 
-#define SQLITE_FROM_HERE SQLite::StatementId(__FILE__, __LINE__)
+#define SQLITE_FROM_HERE ::Orthanc::SQLite::StatementId(__FILE__, __LINE__)
 
 namespace Orthanc
 {
--- a/Core/Toolbox.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Toolbox.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -210,7 +210,7 @@
   {
     if (!boost::filesystem::is_regular_file(path))
     {
-      LOG(ERROR) << "The path does not point to a regular file: " << path;
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
       throw OrthancException(ErrorCode_RegularFileExpected);
     }
 
@@ -468,9 +468,13 @@
     assert(value < 16);
 
     if (value < 10)
+    {
       return value + '0';
+    }
     else
+    {
       return (value - 10) + 'a';
+    }
   }
 
 
@@ -508,8 +512,8 @@
     result.resize(32);
     for (unsigned int i = 0; i < 16; i++)
     {
-      result[2 * i] = GetHexadecimalCharacter(actualHash[i] / 16);
-      result[2 * i + 1] = GetHexadecimalCharacter(actualHash[i] % 16);
+      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
+      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
     }
   }
 #endif
@@ -527,6 +531,29 @@
   {
     result = base64_decode(data);
   }
+
+
+#  if BOOST_HAS_REGEX == 1
+  void Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      DecodeBase64(content, what[2]);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+#  endif
+
 #endif
 
 
@@ -1009,28 +1036,6 @@
   }
 
 
-#if BOOST_HAS_REGEX == 1
-  void Toolbox::DecodeDataUriScheme(std::string& mime,
-                                    std::string& content,
-                                    const std::string& source)
-  {
-    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
-                         boost::regex::icase /* case insensitive search */);
-
-    boost::cmatch what;
-    if (regex_match(source.c_str(), what, pattern))
-    {
-      mime = what[1];
-      content = what[2];
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-  }
-#endif
-
-
   void Toolbox::MakeDirectory(const std::string& path)
   {
     if (boost::filesystem::exists(path))
--- a/Core/Toolbox.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Core/Toolbox.h	Wed Nov 18 10:16:21 2015 +0100
@@ -117,6 +117,12 @@
 
     void EncodeBase64(std::string& result, 
                       const std::string& data);
+
+#  if BOOST_HAS_REGEX == 1
+    void DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
+#  endif
 #endif
 
     std::string GetPathToExecutable();
@@ -153,12 +159,6 @@
                         const std::string& source,
                         char separator);
 
-#if BOOST_HAS_REGEX == 1
-    void DecodeDataUriScheme(std::string& mime,
-                             std::string& content,
-                             const std::string& source);
-#endif
-
     void MakeDirectory(const std::string& path);
 
     bool IsExistingFile(const std::string& path);
--- a/NEWS	Wed Sep 23 10:29:06 2015 +0200
+++ b/NEWS	Wed Nov 18 10:16:21 2015 +0100
@@ -1,13 +1,46 @@
 Pending changes in the mainline
 ===============================
 
+* Full indexation of the patient/study tags to speed up searches and C-Find
 * Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive")
+* "/tools/create-dicom": Support of binary tags encoded using data URI scheme
+* "/tools/create-dicom": Support of hierarchical structures (creation of sequences)
+* "/modify" can insert/modify sequences
+* "/series/.../ordered-slices" to order the slices of a 2D+t or 3D image
+* New URI "/tools/shutdown" to stop Orthanc from the REST API
+* New URIs for attachments: ".../compress", ".../uncompress" and ".../is-compressed"
+* New configuration option: "Dictionary" to declare custom DICOM tags
+* MIME content type can be associated to custom attachments (cf. "UserContentType")
+* New URIs "/tools/create-archive" and "/tools/create-media" to create ZIP/DICOMDIR
+  from a set of resources
+* ".../preview" and ".../image-uint8" can return JPEG images if the HTTP Accept Header asks so
+
+Plugins
+-------
+
+* 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
+* New "OrthancStarted", "OrthancStopped", "UpdatedAttachment" 
+  and "UpdatedMetadata" events in change callbacks
+* "/system" URI gives information about the plugins used for storage area and DB back-end
+* Plugin callbacks should now return explicit "OrthancPluginErrorCode" instead of integers
+
+Lua
+---
+
+* Optional argument "keepStrings" in "DumpJson()"
 
 Maintenance
 -----------
 
-* "/system" URI gives information about the plugins used for storage area and DB back-end
-* Plugin callbacks must now return explicit "OrthancPluginErrorCode" instead of integers
+* Full refactoring of the searching features
+* C-Move SCP for studies using AccessionNumber tag
+* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change)
+* "/tools/create-dicom" can create tags with unknown VR
+* "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos
+* "--errors" flag lists the error codes that could be returned by Orthanc
+* Under Windows, the exit status of Orthanc corresponds to the encountered error code
 
 
 Version 0.9.4 (2015/09/16)
--- a/OrthancExplorer/explorer.js	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancExplorer/explorer.js	Wed Nov 18 10:16:21 2015 +0100
@@ -236,8 +236,8 @@
           ).format
   (patient.MainDicomTags.PatientName,
    FormatMainDicomTags(patient.MainDicomTags, [ 
-     "PatientName", 
-     "OtherPatientIDs" 
+     "PatientName"
+     /*"OtherPatientIDs" */
    ]),
    patient.Studies.length
   );
@@ -288,7 +288,8 @@
      "SeriesTime", 
      "Manufacturer",
      "ImagesInAcquisition",
-     "SeriesDate"
+     "SeriesDate",
+     "ImageOrientationPatient"
    ]),
    c
   );
@@ -305,7 +306,8 @@
      "AcquisitionNumber", 
      "InstanceNumber", 
      "InstanceCreationDate", 
-     "InstanceCreationTime"
+     "InstanceCreationTime",
+     "ImagePositionPatient"
    ])
   );
 
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -37,6 +37,7 @@
 #include "../Core/Logging.h"
 #include "../Core/Uuid.h"
 #include "EmbeddedResources.h"
+#include "ServerToolbox.h"
 
 #include <stdio.h>
 #include <boost/lexical_cast.hpp>
@@ -186,130 +187,6 @@
   }
 
 
-  
-  void DatabaseWrapper::SetGlobalProperty(GlobalProperty property,
-                                          const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
-    s.BindInt(0, property);
-    s.BindString(1, value);
-    s.Run();
-  }
-
-  bool DatabaseWrapper::LookupGlobalProperty(std::string& target,
-                                             GlobalProperty property)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM GlobalProperties WHERE property=?");
-    s.BindInt(0, property);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-  int64_t DatabaseWrapper::CreateResource(const std::string& publicId,
-                                          ResourceType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
-    s.BindInt(0, type);
-    s.BindString(1, publicId);
-    s.Run();
-    return db_.GetLastInsertRowId();
-  }
-
-  bool DatabaseWrapper::LookupResource(int64_t& id,
-                                       ResourceType& type,
-                                       const std::string& publicId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
-    s.BindString(0, publicId);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      id = s.ColumnInt(0);
-      type = static_cast<ResourceType>(s.ColumnInt(1));
-
-      // Check whether there is a single resource with this public id
-      assert(!s.Step());
-
-      return true;
-    }
-  }
-
-  bool DatabaseWrapper::LookupParent(int64_t& parentId,
-                                     int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT parentId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-
-    if (!s.Step())
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    if (s.ColumnIsNull(0))
-    {
-      return false;
-    }
-    else
-    {
-      parentId = s.ColumnInt(0);
-      return true;
-    }
-  }
-
-  std::string DatabaseWrapper::GetPublicId(int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT publicId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (!s.Step())
-    { 
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    return s.ColumnString(0);
-  }
-
-
-  ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT resourceType FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (!s.Step())
-    { 
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    return static_cast<ResourceType>(s.ColumnInt(0));
-  }
-
-
-  void DatabaseWrapper::AttachChild(int64_t parent,
-                                    int64_t child)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
-    s.BindInt64(0, parent);
-    s.BindInt64(1, child);
-    s.Run();
-  }
-
 
   void DatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds,
                                     int64_t id)
@@ -341,183 +218,6 @@
     }
   }
 
-  void DatabaseWrapper::SetMetadata(int64_t id,
-                                    MetadataType type,
-                                    const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.BindString(2, value);
-    s.Run();
-  }
-
-  void DatabaseWrapper::DeleteMetadata(int64_t id,
-                                       MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.Run();
-  }
-
-  bool DatabaseWrapper::LookupMetadata(std::string& target,
-                                       int64_t id,
-                                       MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM Metadata WHERE id=? AND type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-  void DatabaseWrapper::ListAvailableMetadata(std::list<MetadataType>& target,
-                                              int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
-    }
-  }
-
-
-  void DatabaseWrapper::AddAttachment(int64_t id,
-                                      const FileInfo& attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment.GetContentType());
-    s.BindString(2, attachment.GetUuid());
-    s.BindInt64(3, attachment.GetCompressedSize());
-    s.BindInt64(4, attachment.GetUncompressedSize());
-    s.BindInt(5, attachment.GetCompressionType());
-    s.BindString(6, attachment.GetUncompressedMD5());
-    s.BindString(7, attachment.GetCompressedMD5());
-    s.Run();
-  }
-
-
-  void DatabaseWrapper::DeleteAttachment(int64_t id,
-                                         FileContentType attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment);
-    s.Run();
-  }
-
-
-
-  void DatabaseWrapper::ListAvailableAttachments(std::list<FileContentType>& target,
-                                                 int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT fileType FROM AttachedFiles WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
-    }
-  }
-
-  bool DatabaseWrapper::LookupAttachment(FileInfo& attachment,
-                                         int64_t id,
-                                         FileContentType contentType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, contentType);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      attachment = FileInfo(s.ColumnString(0),
-                            contentType,
-                            s.ColumnInt64(1),
-                            s.ColumnString(4),
-                            static_cast<CompressionType>(s.ColumnInt(2)),
-                            s.ColumnInt64(3),
-                            s.ColumnString(5));
-      return true;
-    }
-  }
-
-
-  static void SetMainDicomTagsInternal(SQLite::Statement& s,
-                                       int64_t id,
-                                       const DicomTag& tag,
-                                       const std::string& value)
-  {
-    s.BindInt64(0, id);
-    s.BindInt(1, tag.GetGroup());
-    s.BindInt(2, tag.GetElement());
-    s.BindString(3, value);
-    s.Run();
-  }
-
-
-  void DatabaseWrapper::SetMainDicomTag(int64_t id,
-                                        const DicomTag& tag,
-                                        const std::string& value)
-  {
-    if (tag.IsIdentifier())
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
-      SetMainDicomTagsInternal(s, id, tag, value);
-    }
-    else
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
-      SetMainDicomTagsInternal(s, id, tag, value);
-    }
-  }
-
-  void DatabaseWrapper::GetMainDicomTags(DicomMap& map,
-                                         int64_t id)
-  {
-    map.Clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
-    s.BindInt64(0, id);
-    while (s.Step())
-    {
-      map.SetValue(s.ColumnInt(1),
-                   s.ColumnInt(2),
-                   s.ColumnString(3));
-    }
-
-    SQLite::Statement s2(db_, SQLITE_FROM_HERE, "SELECT * FROM DicomIdentifiers WHERE id=?");
-    s2.BindInt64(0, id);
-    while (s2.Step())
-    {
-      map.SetValue(s2.ColumnInt(1),
-                   s2.ColumnInt(2),
-                   s2.ColumnString(3));
-    }
-  }
-
 
   bool DatabaseWrapper::GetParentPublicId(std::string& target,
                                           int64_t id)
@@ -538,164 +238,6 @@
   }
 
 
-  void DatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target,
-                                            int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
-    s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  void DatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target,
-                                              int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
-    s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
-  }
-
-
-  void DatabaseWrapper::LogChange(int64_t internalId,
-                                  const ServerIndexChange& change)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
-    s.BindInt(0, change.GetChangeType());
-    s.BindInt64(1, internalId);
-    s.BindInt(2, change.GetResourceType());
-    s.BindString(3, change.GetDate());
-    s.Run();
-  }
-
-
-  void DatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target,
-                                           bool& done,
-                                           SQLite::Statement& s,
-                                           uint32_t maxResults)
-  {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
-    {
-      int64_t seq = s.ColumnInt64(0);
-      ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
-      const std::string& date = s.ColumnString(4);
-
-      int64_t internalId = s.ColumnInt64(2);
-      std::string publicId = GetPublicId(internalId);
-
-      target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
-    }
-
-    done = !(target.size() == maxResults && s.Step());
-  }
-
-
-  void DatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target,
-                                   bool& done,
-                                   int64_t since,
-                                   uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    GetChangesInternal(target, done, s, maxResults);
-  }
-
-  void DatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
-    GetChangesInternal(target, done, s, 1);
-  }
-
-
-  void DatabaseWrapper::LogExportedResource(const ExportedResource& resource)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
-
-    s.BindInt(0, resource.GetResourceType());
-    s.BindString(1, resource.GetPublicId());
-    s.BindString(2, resource.GetModality());
-    s.BindString(3, resource.GetPatientId());
-    s.BindString(4, resource.GetStudyInstanceUid());
-    s.BindString(5, resource.GetSeriesInstanceUid());
-    s.BindString(6, resource.GetSopInstanceUid());
-    s.BindString(7, resource.GetDate());
-    s.Run();      
-  }
-
-
-  void DatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                                     bool& done,
-                                                     SQLite::Statement& s,
-                                                     uint32_t maxResults)
-  {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
-    {
-      int64_t seq = s.ColumnInt64(0);
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
-      std::string publicId = s.ColumnString(2);
-
-      ExportedResource resource(seq, 
-                                resourceType,
-                                publicId,
-                                s.ColumnString(3),  // modality
-                                s.ColumnString(8),  // date
-                                s.ColumnString(4),  // patient ID
-                                s.ColumnString(5),  // study instance UID
-                                s.ColumnString(6),  // series instance UID
-                                s.ColumnString(7)); // sop instance UID
-
-      target.push_back(resource);
-    }
-
-    done = !(target.size() == maxResults && s.Step());
-  }
-
-
-  void DatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target,
-                                             bool& done,
-                                             int64_t since,
-                                             uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    GetExportedResourcesInternal(target, done, s, maxResults);
-  }
-
-    
-  void DatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
-    GetExportedResourcesInternal(target, done, s, 1);
-  }
-
-
-    
-
   int64_t DatabaseWrapper::GetTableRecordCount(const std::string& table)
   {
     char buf[128];
@@ -714,72 +256,20 @@
   }
 
     
-  uint64_t DatabaseWrapper::GetTotalCompressedSize()
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-    
-  uint64_t DatabaseWrapper::GetTotalUncompressedSize()
+  DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL), base_(db_)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-  void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
-                                        ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
+    db_.Open(path);
   }
 
-  void DatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
-                                        ResourceType resourceType,
-                                        size_t since,
-                                        size_t limit)
-  {
-    if (limit == 0)
-    {
-      target.clear();
-      return;
-    }
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?");
-    s.BindInt(0, resourceType);
-    s.BindInt64(1, limit);
-    s.BindInt64(2, since);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  DatabaseWrapper::DatabaseWrapper(const std::string& path) : listener_(NULL)
-  {
-    db_.Open(path);
-    Open();
-  }
-
-  DatabaseWrapper::DatabaseWrapper() : listener_(NULL)
+  DatabaseWrapper::DatabaseWrapper() : listener_(NULL), base_(db_)
   {
     db_.OpenInMemory();
-    Open();
   }
 
   void DatabaseWrapper::Open()
   {
+    db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
+
     // Performance tuning of SQLite with PRAGMAs
     // http://www.sqlite.org/pragma.html
     db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
@@ -839,15 +329,17 @@
   void DatabaseWrapper::Upgrade(unsigned int targetVersion,
                                 IStorageArea& storageArea)
   {
-    if (targetVersion != 5)
+    if (targetVersion != 6)
     {
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
 
-    // This version of Orthanc is only compatible with versions 3, 4 and 5 of the DB schema
+    // This version of Orthanc is only compatible with versions 3, 4,
+    // 5 and 6 of the DB schema
     if (version_ != 3 &&
         version_ != 4 &&
-        version_ != 5)
+        version_ != 5 &&
+        version_ != 6)
     {
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
@@ -865,6 +357,23 @@
       ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5);
       version_ = 5;
     }
+
+    if (version_ == 5)
+    {
+      LOG(WARNING) << "Upgrading database version from 5 to 6";
+      // No change in the DB schema, the step from version 5 to 6 only
+      // consists in reconstructing the main DICOM tags information
+      // (as more tags got included).
+      db_.BeginTransaction();
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient);
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study);
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series);
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance);
+      db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" +
+                  boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";");
+      db_.CommitTransaction();
+      version_ = 6;
+    }
   }
 
 
@@ -875,90 +384,6 @@
     db_.Register(new Internals::SignalResourceDeleted(listener));
   }
 
-  uint64_t DatabaseWrapper::GetResourceCount(ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-    
-    if (!s.Step())
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    int64_t c = s.ColumnInt(0);
-    assert(!s.Step());
-
-    return c;
-  }
-
-  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
-   
-    if (!s.Step())
-    {
-      // No patient remaining or all the patients are protected
-      return false;
-    }
-    else
-    {
-      internalId = s.ColumnInt(0);
-      return true;
-    }    
-  }
-
-  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId,
-                                               int64_t patientIdToAvoid)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT patientId FROM PatientRecyclingOrder "
-                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
-    s.BindInt64(0, patientIdToAvoid);
-
-    if (!s.Step())
-    {
-      // No patient remaining or all the patients are protected
-      return false;
-    }
-    else
-    {
-      internalId = s.ColumnInt(0);
-      return true;
-    }   
-  }
-
-  bool DatabaseWrapper::IsProtectedPatient(int64_t internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
-    s.BindInt64(0, internalId);
-    return !s.Step();
-  }
-
-  void DatabaseWrapper::SetProtectedPatient(int64_t internalId, 
-                                            bool isProtected)
-  {
-    if (isProtected)
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else if (IsProtectedPatient(internalId))
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else
-    {
-      // Nothing to do: The patient is already unprotected
-    }
-  }
-
-
 
   void DatabaseWrapper::ClearTable(const std::string& tableName)
   {
@@ -966,53 +391,75 @@
   }
 
 
-  bool DatabaseWrapper::IsExistingResource(int64_t internalId)
+  bool DatabaseWrapper::LookupParent(int64_t& parentId,
+                                     int64_t resourceId)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM Resources WHERE internalId=?");
-    s.BindInt64(0, internalId);
-    return s.Step();
+    bool found;
+    ErrorCode error = base_.LookupParent(found, parentId, resourceId);
+
+    if (error != ErrorCode_Success)
+    {
+      throw OrthancException(error);
+    }
+    else
+    {
+      return found;
+    }
   }
 
 
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
-                                          const DicomTag& tag,
-                                          const std::string& value)
+  ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId)
   {
-    if (!tag.IsIdentifier())
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
+    ResourceType result;
+    ErrorCode code = base_.GetResourceType(result, resourceId);
 
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT id FROM DicomIdentifiers WHERE tagGroup=? AND tagElement=? and value=?");
-
-    s.BindInt(0, tag.GetGroup());
-    s.BindInt(1, tag.GetElement());
-    s.BindString(2, value);
-
-    target.clear();
-
-    while (s.Step())
+    if (code == ErrorCode_Success)
     {
-      target.push_back(s.ColumnInt64(0));
+      return result;
+    }
+    else
+    {
+      throw OrthancException(code);
     }
   }
 
 
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
-                                          const std::string& value)
+  std::string DatabaseWrapper::GetPublicId(int64_t resourceId)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT id FROM DicomIdentifiers WHERE value=?");
+    std::string id;
+
+    if (base_.GetPublicId(id, resourceId))
+    {
+      return id;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
 
-    s.BindString(0, value);
-
-    target.clear();
+  void DatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                                   bool& done /*out*/,
+                                   int64_t since,
+                                   uint32_t maxResults)
+  {
+    ErrorCode code = base_.GetChanges(target, done, since, maxResults);
 
-    while (s.Step())
+    if (code != ErrorCode_Success)
     {
-      target.push_back(s.ColumnInt64(0));
+      throw OrthancException(code);
+    }
+  }
+
+
+  void DatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
+  {
+    ErrorCode code = base_.GetLastChange(target);
+
+    if (code != ErrorCode_Success)
+    {
+      throw OrthancException(code);
     }
   }
 
--- a/OrthancServer/DatabaseWrapper.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Wed Nov 18 10:16:21 2015 +0100
@@ -36,6 +36,7 @@
 
 #include "../Core/SQLite/Connection.h"
 #include "../Core/SQLite/Transaction.h"
+#include "DatabaseWrapperBase.h"
 
 namespace Orthanc
 {
@@ -54,21 +55,10 @@
   private:
     IDatabaseListener* listener_;
     SQLite::Connection db_;
+    DatabaseWrapperBase base_;
     Internals::SignalRemainingAncestor* signalRemainingAncestor_;
     unsigned int version_;
 
-    void Open();
-
-    void GetChangesInternal(std::list<ServerIndexChange>& target,
-                            bool& done,
-                            SQLite::Statement& s,
-                            uint32_t maxResults);
-
-    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                      bool& done,
-                                      SQLite::Statement& s,
-                                      uint32_t maxResults);
-
     void ClearTable(const std::string& tableName);
 
   public:
@@ -76,20 +66,39 @@
 
     DatabaseWrapper();
 
+    virtual void Open();
+
+    virtual void Close()
+    {
+      db_.Close();
+    }
+
     virtual void SetListener(IDatabaseListener& listener);
 
     virtual void SetGlobalProperty(GlobalProperty property,
-                                   const std::string& value);
+                                   const std::string& value)
+    {
+      base_.SetGlobalProperty(property, value);
+    }
 
     virtual bool LookupGlobalProperty(std::string& target,
-                                      GlobalProperty property);
+                                      GlobalProperty property)
+    {
+      return base_.LookupGlobalProperty(target, property);
+    }
 
     virtual int64_t CreateResource(const std::string& publicId,
-                                   ResourceType type);
+                                   ResourceType type)
+    {
+      return base_.CreateResource(publicId, type);
+    }
 
     virtual bool LookupResource(int64_t& id,
                                 ResourceType& type,
-                                const std::string& publicId);
+                                const std::string& publicId)
+    {
+      return base_.LookupResource(id, type, publicId);
+    }
 
     virtual bool LookupParent(int64_t& parentId,
                               int64_t resourceId);
@@ -99,52 +108,106 @@
     virtual ResourceType GetResourceType(int64_t resourceId);
 
     virtual void AttachChild(int64_t parent,
-                             int64_t child);
+                             int64_t child)
+    {
+      base_.AttachChild(parent, child);
+    }
 
     virtual void DeleteResource(int64_t id);
 
     virtual void SetMetadata(int64_t id,
                              MetadataType type,
-                             const std::string& value);
+                             const std::string& value)
+    {
+      base_.SetMetadata(id, type, value);
+    }
 
     virtual void DeleteMetadata(int64_t id,
-                                MetadataType type);
+                                MetadataType type)
+    {
+      base_.DeleteMetadata(id, type);
+    }
 
     virtual bool LookupMetadata(std::string& target,
                                 int64_t id,
-                                MetadataType type);
+                                MetadataType type)
+    {
+      return base_.LookupMetadata(target, id, type);
+    }
 
     virtual void ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id);
+                                       int64_t id)
+    {
+      base_.ListAvailableMetadata(target, id);
+    }
 
     virtual void AddAttachment(int64_t id,
-                               const FileInfo& attachment);
+                               const FileInfo& attachment)
+    {
+      base_.AddAttachment(id, attachment);
+    }
 
     virtual void DeleteAttachment(int64_t id,
-                                  FileContentType attachment);
+                                  FileContentType attachment)
+    {
+      base_.DeleteAttachment(id, attachment);
+    }
 
     virtual void ListAvailableAttachments(std::list<FileContentType>& target,
-                                          int64_t id);
+                                          int64_t id)
+    {
+      return base_.ListAvailableAttachments(target, id);
+    }
 
     virtual bool LookupAttachment(FileInfo& attachment,
                                   int64_t id,
-                                  FileContentType contentType);
+                                  FileContentType contentType)
+    {
+      return base_.LookupAttachment(attachment, id, contentType);
+    }
+
+    virtual void ClearMainDicomTags(int64_t id)
+    {
+      base_.ClearMainDicomTags(id);
+    }
 
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
-                                 const std::string& value);
+                                 const std::string& value)
+    {
+      base_.SetMainDicomTag(id, tag, value);
+    }
+
+    virtual void SetIdentifierTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value)
+    {
+      base_.SetIdentifierTag(id, tag, value);
+    }
 
     virtual void GetMainDicomTags(DicomMap& map,
-                                  int64_t id);
+                                  int64_t id)
+    {
+      base_.GetMainDicomTags(map, id);
+    }
 
     virtual void GetChildrenPublicId(std::list<std::string>& target,
-                                     int64_t id);
+                                     int64_t id)
+    {
+      base_.GetChildrenPublicId(target, id);
+    }
 
     virtual void GetChildrenInternalId(std::list<int64_t>& target,
-                                       int64_t id);
+                                       int64_t id)
+    {
+      base_.GetChildrenInternalId(target, id);
+    }
 
     virtual void LogChange(int64_t internalId,
-                           const ServerIndexChange& change);
+                           const ServerIndexChange& change)
+    {
+      base_.LogChange(internalId, change);
+    }
 
     virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
                             bool& done /*out*/,
@@ -153,38 +216,80 @@
 
     virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/);
 
-    virtual void LogExportedResource(const ExportedResource& resource);
+    virtual void LogExportedResource(const ExportedResource& resource)
+    {
+      base_.LogExportedResource(resource);
+    }
     
     virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
                                       bool& done /*out*/,
                                       int64_t since,
-                                      uint32_t maxResults);
+                                      uint32_t maxResults)
+    {
+      base_.GetExportedResources(target, done, since, maxResults);
+    }
 
-    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/);
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
+    {
+      base_.GetLastExportedResource(target);
+    }
 
-    virtual uint64_t GetTotalCompressedSize();
+    virtual uint64_t GetTotalCompressedSize()
+    {
+      return base_.GetTotalCompressedSize();
+    }
     
-    virtual uint64_t GetTotalUncompressedSize();
+    virtual uint64_t GetTotalUncompressedSize()
+    {
+      return base_.GetTotalUncompressedSize();
+    }
 
-    virtual uint64_t GetResourceCount(ResourceType resourceType);
+    virtual uint64_t GetResourceCount(ResourceType resourceType)
+    {
+      return base_.GetResourceCount(resourceType);
+    }
+
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   ResourceType resourceType)
+    {
+      base_.GetAllInternalIds(target, resourceType);
+    }
 
     virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType);
+                                 ResourceType resourceType)
+    {
+      base_.GetAllPublicIds(target, resourceType);
+    }
 
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  ResourceType resourceType,
                                  size_t since,
-                                 size_t limit);
+                                 size_t limit)
+    {
+      base_.GetAllPublicIds(target, resourceType, since, limit);
+    }
 
-    virtual bool SelectPatientToRecycle(int64_t& internalId);
+    virtual bool SelectPatientToRecycle(int64_t& internalId)
+    {
+      return base_.SelectPatientToRecycle(internalId);
+    }
 
     virtual bool SelectPatientToRecycle(int64_t& internalId,
-                                        int64_t patientIdToAvoid);
+                                        int64_t patientIdToAvoid)
+    {
+      return base_.SelectPatientToRecycle(internalId, patientIdToAvoid);
+    }
 
-    virtual bool IsProtectedPatient(int64_t internalId);
+    virtual bool IsProtectedPatient(int64_t internalId)
+    {
+      return base_.IsProtectedPatient(internalId);
+    }
 
     virtual void SetProtectedPatient(int64_t internalId, 
-                                     bool isProtected);
+                                     bool isProtected)
+    {
+      base_.SetProtectedPatient(internalId, isProtected);
+    }
 
     virtual SQLite::ITransaction* StartTransaction()
     {
@@ -211,14 +316,19 @@
       ClearTable("ExportedResources");
     }
 
-    virtual bool IsExistingResource(int64_t internalId);
+    virtual bool IsExistingResource(int64_t internalId)
+    {
+      return base_.IsExistingResource(internalId);
+    }
 
-    virtual void LookupIdentifier(std::list<int64_t>& target,
+    virtual void LookupIdentifier(std::list<int64_t>& result,
+                                  ResourceType level,
                                   const DicomTag& tag,
-                                  const std::string& value);
-
-    virtual void LookupIdentifier(std::list<int64_t>& target,
-                                  const std::string& value);
+                                  IdentifierConstraintType type,
+                                  const std::string& value)
+    {
+      base_.LookupIdentifier(result, level, tag, type, value);
+    }
 
     virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
                                 int64_t id);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DatabaseWrapperBase.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,730 @@
+/**
+ * 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 "DatabaseWrapperBase.h"
+
+#include <stdio.h>
+#include <memory>
+
+namespace Orthanc
+{
+  void DatabaseWrapperBase::SetGlobalProperty(GlobalProperty property,
+                                              const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
+    s.BindInt(0, property);
+    s.BindString(1, value);
+    s.Run();
+  }
+
+  bool DatabaseWrapperBase::LookupGlobalProperty(std::string& target,
+                                                 GlobalProperty property)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT value FROM GlobalProperties WHERE property=?");
+    s.BindInt(0, property);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+  }
+
+  int64_t DatabaseWrapperBase::CreateResource(const std::string& publicId,
+                                              ResourceType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
+    s.BindInt(0, type);
+    s.BindString(1, publicId);
+    s.Run();
+    return db_.GetLastInsertRowId();
+  }
+
+  bool DatabaseWrapperBase::LookupResource(int64_t& id,
+                                           ResourceType& type,
+                                           const std::string& publicId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
+    s.BindString(0, publicId);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      id = s.ColumnInt(0);
+      type = static_cast<ResourceType>(s.ColumnInt(1));
+
+      // Check whether there is a single resource with this public id
+      assert(!s.Step());
+
+      return true;
+    }
+  }
+
+  ErrorCode DatabaseWrapperBase::LookupParent(bool& found,
+                                              int64_t& parentId,
+                                              int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT parentId FROM Resources WHERE internalId=?");
+    s.BindInt64(0, resourceId);
+
+    if (!s.Step())
+    {
+      return ErrorCode_UnknownResource;
+    }
+
+    if (s.ColumnIsNull(0))
+    {
+      found = false;
+    }
+    else
+    {
+      found = true;
+      parentId = s.ColumnInt(0);
+    }
+
+    return ErrorCode_Success;
+  }
+
+  bool DatabaseWrapperBase::GetPublicId(std::string& result,
+                                        int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT publicId FROM Resources WHERE internalId=?");
+    s.BindInt64(0, resourceId);
+    
+    if (!s.Step())
+    { 
+      return false;
+    }
+    else
+    {
+      result = s.ColumnString(0);
+      return true;
+    }
+  }
+
+
+  ErrorCode DatabaseWrapperBase::GetResourceType(ResourceType& result,
+                                                 int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT resourceType FROM Resources WHERE internalId=?");
+    s.BindInt64(0, resourceId);
+    
+    if (s.Step())
+    {
+      result = static_cast<ResourceType>(s.ColumnInt(0));
+      return ErrorCode_Success;
+    }
+    else
+    { 
+      return ErrorCode_UnknownResource;
+    }
+  }
+
+
+  void DatabaseWrapperBase::AttachChild(int64_t parent,
+                                        int64_t child)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
+    s.BindInt64(0, parent);
+    s.BindInt64(1, child);
+    s.Run();
+  }
+
+
+  void DatabaseWrapperBase::SetMetadata(int64_t id,
+                                        MetadataType type,
+                                        const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
+    s.BindInt64(0, id);
+    s.BindInt(1, type);
+    s.BindString(2, value);
+    s.Run();
+  }
+
+  void DatabaseWrapperBase::DeleteMetadata(int64_t id,
+                                           MetadataType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
+    s.BindInt64(0, id);
+    s.BindInt(1, type);
+    s.Run();
+  }
+
+  bool DatabaseWrapperBase::LookupMetadata(std::string& target,
+                                           int64_t id,
+                                           MetadataType type)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT value FROM Metadata WHERE id=? AND type=?");
+    s.BindInt64(0, id);
+    s.BindInt(1, type);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+  }
+
+  void DatabaseWrapperBase::ListAvailableMetadata(std::list<MetadataType>& target,
+                                                  int64_t id)
+  {
+    target.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
+    s.BindInt64(0, id);
+
+    while (s.Step())
+    {
+      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
+    }
+  }
+
+
+  void DatabaseWrapperBase::AddAttachment(int64_t id,
+                                          const FileInfo& attachment)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
+    s.BindInt64(0, id);
+    s.BindInt(1, attachment.GetContentType());
+    s.BindString(2, attachment.GetUuid());
+    s.BindInt64(3, attachment.GetCompressedSize());
+    s.BindInt64(4, attachment.GetUncompressedSize());
+    s.BindInt(5, attachment.GetCompressionType());
+    s.BindString(6, attachment.GetUncompressedMD5());
+    s.BindString(7, attachment.GetCompressedMD5());
+    s.Run();
+  }
+
+
+  void DatabaseWrapperBase::DeleteAttachment(int64_t id,
+                                             FileContentType attachment)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
+    s.BindInt64(0, id);
+    s.BindInt(1, attachment);
+    s.Run();
+  }
+
+
+
+  void DatabaseWrapperBase::ListAvailableAttachments(std::list<FileContentType>& target,
+                                                     int64_t id)
+  {
+    target.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT fileType FROM AttachedFiles WHERE id=?");
+    s.BindInt64(0, id);
+
+    while (s.Step())
+    {
+      target.push_back(static_cast<FileContentType>(s.ColumnInt(0)));
+    }
+  }
+
+  bool DatabaseWrapperBase::LookupAttachment(FileInfo& attachment,
+                                             int64_t id,
+                                             FileContentType contentType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT uuid, uncompressedSize, compressionType, compressedSize, uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
+    s.BindInt64(0, id);
+    s.BindInt(1, contentType);
+
+    if (!s.Step())
+    {
+      return false;
+    }
+    else
+    {
+      attachment = FileInfo(s.ColumnString(0),
+                            contentType,
+                            s.ColumnInt64(1),
+                            s.ColumnString(4),
+                            static_cast<CompressionType>(s.ColumnInt(2)),
+                            s.ColumnInt64(3),
+                            s.ColumnString(5));
+      return true;
+    }
+  }
+
+
+  void DatabaseWrapperBase::ClearMainDicomTags(int64_t id)
+  {
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
+      s.BindInt64(0, id);
+      s.Run();
+    }
+
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
+      s.BindInt64(0, id);
+      s.Run();
+    }
+  }
+
+
+  void DatabaseWrapperBase::SetMainDicomTag(int64_t id,
+                                            const DicomTag& tag,
+                                            const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+    s.BindInt64(0, id);
+    s.BindInt(1, tag.GetGroup());
+    s.BindInt(2, tag.GetElement());
+    s.BindString(3, value);
+    s.Run();
+  }
+
+
+  void DatabaseWrapperBase::SetIdentifierTag(int64_t id,
+                                             const DicomTag& tag,
+                                             const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
+    s.BindInt64(0, id);
+    s.BindInt(1, tag.GetGroup());
+    s.BindInt(2, tag.GetElement());
+    s.BindString(3, value);
+    s.Run();
+  }
+
+
+  void DatabaseWrapperBase::GetMainDicomTags(DicomMap& map,
+                                             int64_t id)
+  {
+    map.Clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
+    s.BindInt64(0, id);
+    while (s.Step())
+    {
+      map.SetValue(s.ColumnInt(1),
+                   s.ColumnInt(2),
+                   s.ColumnString(3));
+    }
+  }
+
+
+
+  void DatabaseWrapperBase::GetChildrenPublicId(std::list<std::string>& target,
+                                                int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
+                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+    s.BindInt64(0, id);
+
+    target.clear();
+
+    while (s.Step())
+    {
+      target.push_back(s.ColumnString(0));
+    }
+  }
+
+
+  void DatabaseWrapperBase::GetChildrenInternalId(std::list<int64_t>& target,
+                                                  int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
+                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+    s.BindInt64(0, id);
+
+    target.clear();
+
+    while (s.Step())
+    {
+      target.push_back(s.ColumnInt64(0));
+    }
+  }
+
+
+  void DatabaseWrapperBase::LogChange(int64_t internalId,
+                                      const ServerIndexChange& change)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
+    s.BindInt(0, change.GetChangeType());
+    s.BindInt64(1, internalId);
+    s.BindInt(2, change.GetResourceType());
+    s.BindString(3, change.GetDate());
+    s.Run();
+  }
+
+
+  ErrorCode DatabaseWrapperBase::GetChangesInternal(std::list<ServerIndexChange>& target,
+                                                    bool& done,
+                                                    SQLite::Statement& s,
+                                                    uint32_t maxResults)
+  {
+    target.clear();
+
+    while (target.size() < maxResults && s.Step())
+    {
+      int64_t seq = s.ColumnInt64(0);
+      ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
+      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
+      const std::string& date = s.ColumnString(4);
+
+      int64_t internalId = s.ColumnInt64(2);
+      std::string publicId;
+      if (!GetPublicId(publicId, internalId))
+      {
+        return ErrorCode_UnknownResource;
+      }
+
+      target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
+    }
+
+    done = !(target.size() == maxResults && s.Step());
+    return ErrorCode_Success;
+  }
+
+
+  ErrorCode DatabaseWrapperBase::GetChanges(std::list<ServerIndexChange>& target,
+                                            bool& done,
+                                            int64_t since,
+                                            uint32_t maxResults)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
+    s.BindInt64(0, since);
+    s.BindInt(1, maxResults + 1);
+    return GetChangesInternal(target, done, s, maxResults);
+  }
+
+  ErrorCode DatabaseWrapperBase::GetLastChange(std::list<ServerIndexChange>& target)
+  {
+    bool done;  // Ignored
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
+    return GetChangesInternal(target, done, s, 1);
+  }
+
+
+  void DatabaseWrapperBase::LogExportedResource(const ExportedResource& resource)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+    s.BindInt(0, resource.GetResourceType());
+    s.BindString(1, resource.GetPublicId());
+    s.BindString(2, resource.GetModality());
+    s.BindString(3, resource.GetPatientId());
+    s.BindString(4, resource.GetStudyInstanceUid());
+    s.BindString(5, resource.GetSeriesInstanceUid());
+    s.BindString(6, resource.GetSopInstanceUid());
+    s.BindString(7, resource.GetDate());
+    s.Run();      
+  }
+
+
+  void DatabaseWrapperBase::GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                                         bool& done,
+                                                         SQLite::Statement& s,
+                                                         uint32_t maxResults)
+  {
+    target.clear();
+
+    while (target.size() < maxResults && s.Step())
+    {
+      int64_t seq = s.ColumnInt64(0);
+      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
+      std::string publicId = s.ColumnString(2);
+
+      ExportedResource resource(seq, 
+                                resourceType,
+                                publicId,
+                                s.ColumnString(3),  // modality
+                                s.ColumnString(8),  // date
+                                s.ColumnString(4),  // patient ID
+                                s.ColumnString(5),  // study instance UID
+                                s.ColumnString(6),  // series instance UID
+                                s.ColumnString(7)); // sop instance UID
+
+      target.push_back(resource);
+    }
+
+    done = !(target.size() == maxResults && s.Step());
+  }
+
+
+  void DatabaseWrapperBase::GetExportedResources(std::list<ExportedResource>& target,
+                                                 bool& done,
+                                                 int64_t since,
+                                                 uint32_t maxResults)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
+    s.BindInt64(0, since);
+    s.BindInt(1, maxResults + 1);
+    GetExportedResourcesInternal(target, done, s, maxResults);
+  }
+
+    
+  void DatabaseWrapperBase::GetLastExportedResource(std::list<ExportedResource>& target)
+  {
+    bool done;  // Ignored
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
+    GetExportedResourcesInternal(target, done, s, 1);
+  }
+
+
+    
+  uint64_t DatabaseWrapperBase::GetTotalCompressedSize()
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
+    s.Run();
+    return static_cast<uint64_t>(s.ColumnInt64(0));
+  }
+
+    
+  uint64_t DatabaseWrapperBase::GetTotalUncompressedSize()
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
+    s.Run();
+    return static_cast<uint64_t>(s.ColumnInt64(0));
+  }
+
+  void DatabaseWrapperBase::GetAllInternalIds(std::list<int64_t>& target,
+                                              ResourceType resourceType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT internalId FROM Resources WHERE resourceType=?");
+    s.BindInt(0, resourceType);
+
+    target.clear();
+    while (s.Step())
+    {
+      target.push_back(s.ColumnInt64(0));
+    }
+  }
+
+
+  void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target,
+                                            ResourceType resourceType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
+    s.BindInt(0, resourceType);
+
+    target.clear();
+    while (s.Step())
+    {
+      target.push_back(s.ColumnString(0));
+    }
+  }
+
+  void DatabaseWrapperBase::GetAllPublicIds(std::list<std::string>& target,
+                                            ResourceType resourceType,
+                                            size_t since,
+                                            size_t limit)
+  {
+    if (limit == 0)
+    {
+      target.clear();
+      return;
+    }
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=? LIMIT ? OFFSET ?");
+    s.BindInt(0, resourceType);
+    s.BindInt64(1, limit);
+    s.BindInt64(2, since);
+
+    target.clear();
+    while (s.Step())
+    {
+      target.push_back(s.ColumnString(0));
+    }
+  }
+
+
+  uint64_t DatabaseWrapperBase::GetResourceCount(ResourceType resourceType)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
+    s.BindInt(0, resourceType);
+    
+    if (!s.Step())
+    {
+      return 0;
+    }
+    else
+    {
+      int64_t c = s.ColumnInt(0);
+      assert(!s.Step());
+      return c;
+    }
+  }
+
+
+  bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
+   
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }    
+  }
+
+  bool DatabaseWrapperBase::SelectPatientToRecycle(int64_t& internalId,
+                                                   int64_t patientIdToAvoid)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder "
+                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
+    s.BindInt64(0, patientIdToAvoid);
+
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }   
+  }
+
+  bool DatabaseWrapperBase::IsProtectedPatient(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
+    s.BindInt64(0, internalId);
+    return !s.Step();
+  }
+
+  void DatabaseWrapperBase::SetProtectedPatient(int64_t internalId, 
+                                                bool isProtected)
+  {
+    if (isProtected)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+      s.BindInt64(0, internalId);
+      s.Run();
+    }
+    else if (IsProtectedPatient(internalId))
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+      s.BindInt64(0, internalId);
+      s.Run();
+    }
+    else
+    {
+      // Nothing to do: The patient is already unprotected
+    }
+  }
+
+
+
+  bool DatabaseWrapperBase::IsExistingResource(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT * FROM Resources WHERE internalId=?");
+    s.BindInt64(0, internalId);
+    return s.Step();
+  }
+
+
+
+  void DatabaseWrapperBase::LookupIdentifier(std::list<int64_t>& target,
+                                             ResourceType level,
+                                             const DicomTag& tag,
+                                             IdentifierConstraintType type,
+                                             const std::string& value)
+  {
+    static const char* COMMON = ("SELECT d.id FROM DicomIdentifiers AS d, Resources AS r WHERE "
+                                 "d.id = r.internalId AND r.resourceType=? AND "
+                                 "d.tagGroup=? AND d.tagElement=? AND ");
+
+    std::auto_ptr<SQLite::Statement> s;
+
+    switch (type)
+    {
+      case IdentifierConstraintType_GreaterOrEqual:
+        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value>=?"));
+        break;
+
+      case IdentifierConstraintType_SmallerOrEqual:
+        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value<=?"));
+        break;
+
+      case IdentifierConstraintType_Wildcard:
+        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value GLOB ?"));
+        break;
+
+      case IdentifierConstraintType_Equal:
+      default:
+        s.reset(new SQLite::Statement(db_, std::string(COMMON) + "d.value=?"));
+        break;
+    }
+
+    assert(s.get() != NULL);
+
+    s->BindInt(0, level);
+    s->BindInt(1, tag.GetGroup());
+    s->BindInt(2, tag.GetElement());
+    s->BindString(3, value);
+
+    target.clear();
+
+    while (s->Step())
+    {
+      target.push_back(s->ColumnInt64(0));
+    }    
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DatabaseWrapperBase.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,203 @@
+/**
+ * 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 "../Core/DicomFormat/DicomMap.h"
+#include "../Core/DicomFormat/DicomTag.h"
+#include "../Core/Enumerations.h"
+#include "../Core/FileStorage/FileInfo.h"
+#include "../Core/SQLite/Connection.h"
+#include "../OrthancServer/ExportedResource.h"
+#include "../OrthancServer/ServerIndexChange.h"
+#include "ServerEnumerations.h"
+
+#include <list>
+
+
+namespace Orthanc
+{
+  /**
+   * This class is shared between the Orthanc core and the sample
+   * database plugin whose code is in
+   * "../Plugins/Samples/DatabasePlugin".
+   **/
+  class DatabaseWrapperBase
+  {
+  private:
+    SQLite::Connection&  db_;
+
+    ErrorCode GetChangesInternal(std::list<ServerIndexChange>& target,
+                                 bool& done,
+                                 SQLite::Statement& s,
+                                 uint32_t maxResults);
+
+    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                      bool& done,
+                                      SQLite::Statement& s,
+                                      uint32_t maxResults);
+
+  public:
+    DatabaseWrapperBase(SQLite::Connection& db) : db_(db)
+    {
+    }
+
+    void SetGlobalProperty(GlobalProperty property,
+                           const std::string& value);
+
+    bool LookupGlobalProperty(std::string& target,
+                              GlobalProperty property);
+
+    int64_t CreateResource(const std::string& publicId,
+                           ResourceType type);
+
+    bool LookupResource(int64_t& id,
+                        ResourceType& type,
+                        const std::string& publicId);
+
+    ErrorCode LookupParent(bool& found,
+                           int64_t& parentId,
+                           int64_t resourceId);
+
+    bool GetPublicId(std::string& result,
+                     int64_t resourceId);
+
+    ErrorCode GetResourceType(ResourceType& result,
+                              int64_t resourceId);
+
+    void AttachChild(int64_t parent,
+                     int64_t child);
+
+    void SetMetadata(int64_t id,
+                     MetadataType type,
+                     const std::string& value);
+
+    void DeleteMetadata(int64_t id,
+                        MetadataType type);
+
+    bool LookupMetadata(std::string& target,
+                        int64_t id,
+                        MetadataType type);
+
+    void ListAvailableMetadata(std::list<MetadataType>& target,
+                               int64_t id);
+
+    void AddAttachment(int64_t id,
+                       const FileInfo& attachment);
+
+    void DeleteAttachment(int64_t id,
+                          FileContentType attachment);
+
+    void ListAvailableAttachments(std::list<FileContentType>& target,
+                                  int64_t id);
+
+    bool LookupAttachment(FileInfo& attachment,
+                          int64_t id,
+                          FileContentType contentType);
+
+
+    void ClearMainDicomTags(int64_t id);
+
+
+    void SetMainDicomTag(int64_t id,
+                         const DicomTag& tag,
+                         const std::string& value);
+
+    void SetIdentifierTag(int64_t id,
+                          const DicomTag& tag,
+                          const std::string& value);
+
+    void GetMainDicomTags(DicomMap& map,
+                          int64_t id);
+
+    void GetChildrenPublicId(std::list<std::string>& target,
+                             int64_t id);
+
+    void GetChildrenInternalId(std::list<int64_t>& target,
+                               int64_t id);
+
+    void LogChange(int64_t internalId,
+                   const ServerIndexChange& change);
+
+    ErrorCode GetChanges(std::list<ServerIndexChange>& target,
+                         bool& done,
+                         int64_t since,
+                         uint32_t maxResults);
+
+    ErrorCode GetLastChange(std::list<ServerIndexChange>& target);
+
+    void LogExportedResource(const ExportedResource& resource);
+
+    void GetExportedResources(std::list<ExportedResource>& target,
+                              bool& done,
+                              int64_t since,
+                              uint32_t maxResults);
+    
+    void GetLastExportedResource(std::list<ExportedResource>& target);
+    
+    uint64_t GetTotalCompressedSize();
+    
+    uint64_t GetTotalUncompressedSize();
+
+    void GetAllInternalIds(std::list<int64_t>& target,
+                           ResourceType resourceType);
+
+    void GetAllPublicIds(std::list<std::string>& target,
+                         ResourceType resourceType);
+
+    void GetAllPublicIds(std::list<std::string>& target,
+                         ResourceType resourceType,
+                         size_t since,
+                         size_t limit);
+
+    uint64_t GetResourceCount(ResourceType resourceType);
+
+    bool SelectPatientToRecycle(int64_t& internalId);
+
+    bool SelectPatientToRecycle(int64_t& internalId,
+                                int64_t patientIdToAvoid);
+
+    bool IsProtectedPatient(int64_t internalId);
+
+    void SetProtectedPatient(int64_t internalId, 
+                             bool isProtected);
+
+    bool IsExistingResource(int64_t internalId);
+
+    void LookupIdentifier(std::list<int64_t>& result,
+                          ResourceType level,
+                          const DicomTag& tag,
+                          IdentifierConstraintType type,
+                          const std::string& value);
+  };
+}
+
--- a/OrthancServer/DicomFindQuery.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,389 +0,0 @@
-/**
- * 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 "DicomFindQuery.h"
-
-#include "FromDcmtkBridge.h"
-
-#include <boost/regex.hpp> 
-
-
-namespace Orthanc
-{
-  class DicomFindQuery::ValueConstraint : public DicomFindQuery::IConstraint
-  {
-  private:
-    bool          isCaseSensitive_;
-    std::string   expected_;
-
-  public:
-    ValueConstraint(const std::string& value,
-                    bool caseSensitive) :
-      isCaseSensitive_(caseSensitive),
-      expected_(value)
-    {
-    }
-
-    const std::string& GetValue() const
-    {
-      return expected_;
-    }
-
-    virtual bool IsExactConstraint() const
-    {
-      return isCaseSensitive_;
-    }
-
-    virtual bool Apply(const std::string& value) const
-    {
-      if (isCaseSensitive_)
-      {
-        return expected_ == value;
-      }
-      else
-      {
-        std::string v, c;
-        Toolbox::ToLowerCase(v, value);
-        Toolbox::ToLowerCase(c, expected_);
-        return v == c;
-      }
-    }
-  };
-
-
-  class DicomFindQuery::ListConstraint : public DicomFindQuery::IConstraint
-  {
-  private:
-    std::set<std::string>  values_;
-
-  public:
-    ListConstraint(const std::string& values)
-    {
-      std::vector<std::string> items;
-      Toolbox::TokenizeString(items, values, '\\');
-
-      for (size_t i = 0; i < items.size(); i++)
-      {
-        std::string lower;
-        Toolbox::ToLowerCase(lower, items[i]);
-        values_.insert(lower);
-      }
-    }
-
-    virtual bool Apply(const std::string& value) const
-    {
-      std::string tmp;
-      Toolbox::ToLowerCase(tmp, value);
-      return values_.find(tmp) != values_.end();
-    }
-  };
-
-
-  class DicomFindQuery::RangeConstraint : public DicomFindQuery::IConstraint
-  {
-  private:
-    std::string lower_;
-    std::string upper_;
-
-  public:
-    RangeConstraint(const std::string& range)
-    {
-      size_t separator = range.find('-');
-      Toolbox::ToLowerCase(lower_, range.substr(0, separator));
-      Toolbox::ToLowerCase(upper_, range.substr(separator + 1));
-    }
-
-    virtual bool Apply(const std::string& value) const
-    {
-      std::string v;
-      Toolbox::ToLowerCase(v, value);
-
-      if (lower_.size() == 0 && 
-          upper_.size() == 0)
-      {
-        return false;
-      }
-
-      if (lower_.size() == 0)
-      {
-        return v <= upper_;
-      }
-
-      if (upper_.size() == 0)
-      {
-        return v >= lower_;
-      }
-    
-      return (v >= lower_ && v <= upper_);
-    }
-  };
-
-
-  class DicomFindQuery::WildcardConstraint : public DicomFindQuery::IConstraint
-  {
-  private:
-    boost::regex pattern_;
-
-  public:
-    WildcardConstraint(const std::string& wildcard,
-                       bool caseSensitive)
-    {
-      std::string re = Toolbox::WildcardToRegularExpression(wildcard);
-
-      if (caseSensitive)
-      {
-        pattern_ = boost::regex(re);
-      }
-      else
-      {
-        pattern_ = boost::regex(re, boost::regex::icase /* case insensitive search */);
-      }
-    }
-
-    virtual bool Apply(const std::string& value) const
-    {
-      return boost::regex_match(value, pattern_);
-    }
-  };
-
-
-  void DicomFindQuery::PrepareMainDicomTags(ResourceType level)
-  {
-    std::set<DicomTag> tags;
-    DicomMap::GetMainDicomTags(tags, level);
-
-    for (std::set<DicomTag>::const_iterator
-           it = tags.begin(); it != tags.end(); ++it)
-    {
-      mainDicomTags_[*it] = level;
-    }
-  }
-
-
-  DicomFindQuery::DicomFindQuery() : 
-    level_(ResourceType_Patient),
-    filterJson_(false)
-  {
-    PrepareMainDicomTags(ResourceType_Patient);
-    PrepareMainDicomTags(ResourceType_Study);
-    PrepareMainDicomTags(ResourceType_Series);
-    PrepareMainDicomTags(ResourceType_Instance);
-  }
-
-
-  DicomFindQuery::~DicomFindQuery()
-  {
-    for (Constraints::iterator it = constraints_.begin();
-         it != constraints_.end(); ++it)
-    {
-      delete it->second;
-    }
-  }
-
-
-
-
-  void DicomFindQuery::AssignConstraint(const DicomTag& tag,
-                                        IConstraint* constraint)
-  {
-    Constraints::iterator it = constraints_.find(tag);
-
-    if (it != constraints_.end())
-    {
-      constraints_.erase(it);
-    }
-
-    constraints_[tag] = constraint;
-
-    MainDicomTags::const_iterator tmp = mainDicomTags_.find(tag);
-    if (tmp == mainDicomTags_.end())
-    {
-      // The query depends upon a DICOM tag that is not a main tag
-      // from the point of view of Orthanc, we need to decode the
-      // JSON file on the disk.
-      filterJson_ = true;
-    }
-    else
-    {
-      filteredLevels_.insert(tmp->second);
-    }
-  }
-
-
-  void DicomFindQuery::SetConstraint(const DicomTag& tag,
-                                     const std::string& constraint,
-                                     bool caseSensitivePN)
-  {
-    ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
-
-    bool sensitive = true;
-    if (vr == ValueRepresentation_PatientName)
-    {
-      sensitive = caseSensitivePN;
-    }
-
-    // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
-    // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html  
-
-    if ((vr == ValueRepresentation_Date ||
-         vr == ValueRepresentation_DateTime ||
-         vr == ValueRepresentation_Time) &&
-        constraint.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").
-       **/
-      AssignConstraint(tag, new RangeConstraint(constraint));
-    }
-    else if (constraint.find('\\') != std::string::npos)
-    {
-      AssignConstraint(tag, new ListConstraint(constraint));
-    }
-    else if (constraint.find('*') != std::string::npos ||
-             constraint.find('?') != std::string::npos)
-    {
-      AssignConstraint(tag, new WildcardConstraint(constraint, sensitive));
-    }
-    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
-      **/
-
-      AssignConstraint(tag, new ValueConstraint(constraint, sensitive));
-    }
-  }
-
-
-  bool DicomFindQuery::RestrictIdentifier(std::string& value,
-                                          DicomTag identifier) const
-  {
-    Constraints::const_iterator it = constraints_.find(identifier);
-    if (it == constraints_.end() ||
-        !it->second->IsExactConstraint())
-    {
-      return false;
-    }
-    else
-    {
-      value = dynamic_cast<ValueConstraint*>(it->second)->GetValue();
-      return true;
-    }
-  }
-
-  bool DicomFindQuery::HasMainDicomTagsFilter(ResourceType level) const
-  {
-    return filteredLevels_.find(level) != filteredLevels_.end();
-  }
-
-  bool DicomFindQuery::FilterMainDicomTags(const std::string& resourceId,
-                                           ResourceType level,
-                                           const DicomMap& mainTags) const
-  {
-    std::set<DicomTag> tags;
-    mainTags.GetTags(tags);
-
-    for (std::set<DicomTag>::const_iterator
-           it = tags.begin(); it != tags.end(); ++it)
-    {
-      Constraints::const_iterator constraint = constraints_.find(*it);
-      if (constraint != constraints_.end() &&
-          !constraint->second->Apply(mainTags.GetValue(*it).AsString()))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  bool DicomFindQuery::HasInstanceFilter() const
-  {
-    return filterJson_;
-  }
-
-  bool DicomFindQuery::FilterInstance(const std::string& instanceId,
-                                      const Json::Value& content) const
-  {
-    for (Constraints::const_iterator it = constraints_.begin();
-         it != constraints_.end(); ++it)
-    {
-      std::string tag = it->first.Format();
-      std::string value;
-      if (content.isMember(tag))
-      {
-        value = content.get(tag, Json::arrayValue).get("Value", "").asString();
-      }
-
-      if (!it->second->Apply(value))
-      {
-        return false;
-      }
-    }
-
-    return true;
-  }
-}
--- a/OrthancServer/DicomFindQuery.h	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/**
- * 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 "ResourceFinder.h"
-
-namespace Orthanc
-{
-  class DicomFindQuery : public ResourceFinder::IQuery
-  {
-  private:
-    class  IConstraint : public boost::noncopyable
-    {
-    public:
-      virtual ~IConstraint()
-      {
-      }
-
-      virtual bool IsExactConstraint() const
-      {
-        return false;
-      }
-
-      virtual bool Apply(const std::string& value) const = 0;
-    };
-
-
-    class ValueConstraint;
-    class RangeConstraint;
-    class ListConstraint;
-    class WildcardConstraint;
-
-    typedef std::map<DicomTag, IConstraint*>  Constraints;
-    typedef std::map<DicomTag, ResourceType>  MainDicomTags;
-
-    MainDicomTags           mainDicomTags_;
-    ResourceType            level_;
-    bool                    filterJson_;
-    Constraints             constraints_;
-    std::set<ResourceType>  filteredLevels_;
-
-    void AssignConstraint(const DicomTag& tag,
-                          IConstraint* constraint);
-
-    void PrepareMainDicomTags(ResourceType level);
-
-
-  public:
-    DicomFindQuery();
-
-    virtual ~DicomFindQuery();
-
-    void SetLevel(ResourceType level)
-    {
-      level_ = level;
-    }
-
-    virtual ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    void SetConstraint(const DicomTag& tag,
-                       const std::string& constraint,
-                       bool caseSensitivePN);
-
-    virtual bool RestrictIdentifier(std::string& value,
-                                    DicomTag identifier) const;
-
-    virtual bool HasMainDicomTagsFilter(ResourceType level) const;
-
-    virtual bool FilterMainDicomTags(const std::string& resourceId,
-                                     ResourceType level,
-                                     const DicomMap& mainTags) const;
-
-    virtual bool HasInstanceFilter() const;
-
-    virtual bool FilterInstance(const std::string& instanceId,
-                                const Json::Value& content) const;
-  };
-}
--- a/OrthancServer/DicomInstanceToStore.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomInstanceToStore.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -109,7 +109,10 @@
     if (!json_.HasContent())
     {
       json_.Allocate();
-      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()));
+      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()), 
+                              DicomToJsonFormat_Full, 
+                              DicomToJsonFlags_Default,
+                              256 /* max string length */);
     }
   }
 
--- a/OrthancServer/DicomModification.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomModification.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -44,13 +44,56 @@
 
 namespace Orthanc
 {
+  void DicomModification::RemoveInternal(const DicomTag& tag)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      replacements_.erase(it);
+    }    
+  }
+
+
+  void DicomModification::ReplaceInternal(const DicomTag& tag,
+                                          const Json::Value& value)
+  {
+    Replacements::iterator it = replacements_.find(tag);
+
+    if (it != replacements_.end())
+    {
+      delete it->second;
+      it->second = NULL;   // In the case of an exception during the clone
+      it->second = new Json::Value(value);  // Clone
+    }
+    else
+    {
+      replacements_[tag] = new Json::Value(value);  // Clone
+    }
+  }
+
+
+  void DicomModification::ClearReplacements()
+  {
+    for (Replacements::iterator it = replacements_.begin();
+         it != replacements_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    replacements_.clear();
+  }
+
+
   void DicomModification::MarkNotOrthancAnonymization()
   {
     Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD);
 
     if (it != replacements_.end() &&
-        it->second == ORTHANC_DEIDENTIFICATION_METHOD)
+        it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD)
     {
+      delete it->second;
       replacements_.erase(it);
     }
   }
@@ -100,7 +143,7 @@
 
     dicom.Replace(*tag, mapped);
   }
-
+  
   DicomModification::DicomModification()
   {
     removePrivateTags_ = false;
@@ -108,10 +151,15 @@
     allowManualIdentifiers_ = true;
   }
 
+  DicomModification::~DicomModification()
+  {
+    ClearReplacements();
+  }
+
   void DicomModification::Keep(const DicomTag& tag)
   {
     removals_.erase(tag);
-    replacements_.erase(tag);
+    RemoveInternal(tag);
 
     if (FromDcmtkBridge::IsPrivateTag(tag))
     {
@@ -124,7 +172,7 @@
   void DicomModification::Remove(const DicomTag& tag)
   {
     removals_.insert(tag);
-    replacements_.erase(tag);
+    RemoveInternal(tag);
     privateTagsToKeep_.erase(tag);
 
     MarkNotOrthancAnonymization();
@@ -136,12 +184,12 @@
   }
 
   void DicomModification::Replace(const DicomTag& tag,
-                                  const std::string& value,
+                                  const Json::Value& value,
                                   bool safeForAnonymization)
   {
     removals_.erase(tag);
     privateTagsToKeep_.erase(tag);
-    replacements_[tag] = value;
+    ReplaceInternal(tag, value);
 
     if (!safeForAnonymization)
     {
@@ -149,12 +197,13 @@
     }
   }
 
+
   bool DicomModification::IsReplaced(const DicomTag& tag) const
   {
     return replacements_.find(tag) != replacements_.end();
   }
 
-  const std::string& DicomModification::GetReplacement(const DicomTag& tag) const
+  const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const
   {
     Replacements::const_iterator it = replacements_.find(tag);
 
@@ -164,10 +213,26 @@
     }
     else
     {
-      return it->second;
+      return *it->second;
     } 
   }
 
+
+  std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const
+  {
+    const Json::Value& json = GetReplacement(tag);
+
+    if (json.type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return json.asString();
+    }    
+  }
+
+
   void DicomModification::SetRemovePrivateTags(bool removed)
   {
     removePrivateTags_ = removed;
@@ -192,7 +257,7 @@
   void DicomModification::SetupAnonymization()
   {
     removals_.clear();
-    replacements_.clear();
+    ClearReplacements();
     removePrivateTags_ = true;
     level_ = ResourceType_Patient;
     uidMap_.clear();
@@ -255,15 +320,15 @@
     removals_.insert(DicomTag(0x0010, 0x2000));  // Medical Alerts
 
     // Set the DeidentificationMethod tag
-    replacements_.insert(std::make_pair(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD));
+    ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD);
 
     // Set the PatientIdentityRemoved tag
-    replacements_.insert(std::make_pair(DicomTag(0x0012, 0x0062), "YES"));
+    ReplaceInternal(DicomTag(0x0012, 0x0062), "YES");
 
     // (*) Choose a random patient name and ID
     std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient);
-    replacements_[DICOM_TAG_PATIENT_ID] = patientId;
-    replacements_[DICOM_TAG_PATIENT_NAME] = patientId;
+    ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId);
+    ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId);
   }
 
   void DicomModification::Apply(ParsedDicomFile& toModify)
@@ -394,7 +459,7 @@
     for (Replacements::const_iterator it = replacements_.begin(); 
          it != replacements_.end(); ++it)
     {
-      toModify.Replace(it->first, it->second, DicomReplaceMode_InsertIfAbsent);
+      toModify.Replace(it->first, *it->second, DicomReplaceMode_InsertIfAbsent);
     }
 
     // (4) Update the DICOM identifiers
--- a/OrthancServer/DicomModification.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomModification.h	Wed Nov 18 10:16:21 2015 +0100
@@ -36,7 +36,7 @@
 
 namespace Orthanc
 {
-  class DicomModification
+  class DicomModification : public boost::noncopyable
   {
     /**
      * Process:
@@ -47,7 +47,7 @@
 
   private:
     typedef std::set<DicomTag> SetOfTags;
-    typedef std::map<DicomTag, std::string> Replacements;
+    typedef std::map<DicomTag, Json::Value*> Replacements;
     typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
 
     SetOfTags removals_;
@@ -63,9 +63,18 @@
 
     void MarkNotOrthancAnonymization();
 
+    void ClearReplacements();
+
+    void RemoveInternal(const DicomTag& tag);
+
+    void ReplaceInternal(const DicomTag& tag,
+                         const Json::Value& value);
+
   public:
     DicomModification();
 
+    ~DicomModification();
+
     void Keep(const DicomTag& tag);
 
     void Remove(const DicomTag& tag);
@@ -73,12 +82,14 @@
     bool IsRemoved(const DicomTag& tag) const;
 
     void Replace(const DicomTag& tag,
-                 const std::string& value,
+                 const Json::Value& value,   // Encoded using UTF-8
                  bool safeForAnonymization = false);
 
     bool IsReplaced(const DicomTag& tag) const;
 
-    const std::string& GetReplacement(const DicomTag& tag) const;
+    const Json::Value& GetReplacement(const DicomTag& tag) const;
+
+    std::string GetReplacementAsString(const DicomTag& tag) const;
 
     void SetRemovePrivateTags(bool removed);
 
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -40,10 +40,9 @@
 #include "../Internals/CommandDispatcher.h"
 #include "../OrthancInitialization.h"
 #include "EmbeddedResources.h"
+#include "../../Core/MultiThreading/RunnableWorkersPool.h"
 
 #include <boost/thread.hpp>
-#include <boost/filesystem.hpp>
-#include <dcmtk/dcmdata/dcdict.h>
 
 #if defined(__linux)
 #include <cstdlib>
@@ -54,155 +53,37 @@
 {
   struct DicomServer::PImpl
   {
-    boost::thread thread_;
-
-    //std::set<
+    boost::thread  thread_;
+    T_ASC_Network *network_;
+    std::auto_ptr<RunnableWorkersPool>  workers_;
   };
 
 
-#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
-  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
-                                     EmbeddedResources::FileResourceId resource)
-  {
-    Toolbox::TemporaryFile tmp;
-
-    FILE* fp = fopen(tmp.GetPath().c_str(), "wb");
-    fwrite(EmbeddedResources::GetFileResourceBuffer(resource), 
-           EmbeddedResources::GetFileResourceSize(resource), 1, fp);
-    fclose(fp);
-
-    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-                             
-#else
-  static void LoadExternalDictionary(DcmDataDictionary& dictionary,
-                                     const std::string& directory,
-                                     const std::string& filename)
-  {
-    boost::filesystem::path p = directory;
-    p = p / filename;
-
-    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
-
-    if (!dictionary.loadDictionary(p.string().c_str()))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-                            
-#endif
-
-
-  void DicomServer::InitializeDictionary()
-  {
-    /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */
-    dcmDisableGethostbyaddr.set(OFTrue);
-
-    dcmDataDict.clear();
-    DcmDataDictionary& d = dcmDataDict.wrlock();
-
-#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
-    LOG(WARNING) << "Loading the embedded dictionaries";
-    /**
-     * Do not load DICONDE dictionary, it breaks the other tags. The
-     * command "strace storescu 2>&1 |grep dic" shows that DICONDE
-     * dictionary is not loaded by storescu.
-     **/
-    //LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICONDE);
-
-    LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICOM);
-    LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE);
-
-#elif defined(__linux) || defined(__FreeBSD_kernel__)
-    std::string path = DCMTK_DICTIONARY_DIR;
-
-    const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
-    if (env != NULL)
-    {
-      path = std::string(env);
-    }
-
-    LoadExternalDictionary(d, path, "dicom.dic");
-    LoadExternalDictionary(d, path, "private.dic");
-
-#else
-#error Support your platform here
-#endif
-
-    dcmDataDict.unlock();
-
-    /* make sure data dictionary is loaded */
-    if (!dcmDataDict.isDictionaryLoaded())
-    {
-      LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    {
-      // Test the dictionary with a simple DICOM tag
-      DcmTag key(0x0010, 0x1030); // This is PatientWeight
-      if (key.getEVR() != EVR_DS)
-      {
-        LOG(ERROR) << "The DICOM dictionary has not been correctly read";
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-  }
-
-
   void DicomServer::ServerThread(DicomServer* server)
   {
-    /* initialize network, i.e. create an instance of T_ASC_Network*. */
-    T_ASC_Network *net;
-    OFCondition cond = ASC_initializeNetwork
-      (NET_ACCEPTOR, OFstatic_cast(int, server->port_), /*opt_acse_timeout*/ 30, &net);
-    if (cond.bad())
-    {
-      LOG(ERROR) << "cannot create network: " << cond.text();
-      throw OrthancException(ErrorCode_DicomPortInUse);
-    }
-
     LOG(INFO) << "DICOM server started";
 
-    server->started_ = true;
-
     while (server->continue_)
     {
       /* receive an association and acknowledge or reject it. If the association was */
       /* acknowledged, offer corresponding services and invoke one or more if required. */
-      std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, net));
+      std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
 
-      if (dispatcher.get() != NULL)
+      try
       {
-        if (server->isThreaded_)
+        if (dispatcher.get() != NULL)
         {
-          server->bagOfDispatchers_.Add(dispatcher.release());
+          server->pimpl_->workers_->Add(dispatcher.release());
         }
-        else
-        {
-          IRunnableBySteps::RunUntilDone(*dispatcher);
-        }
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Exception in the DICOM server thread: " << e.What();
       }
     }
 
     LOG(INFO) << "DICOM server stopping";
-
-    if (server->isThreaded_)
-    {
-      server->bagOfDispatchers_.StopAll();
-    }
-
-    /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
-    /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
-    cond = ASC_dropNetwork(&net);
-    if (cond.bad())
-    {
-      LOG(ERROR) << "Error while dropping the network: " << cond.text();
-    }
-  }                           
+  }
 
 
   DicomServer::DicomServer() : 
@@ -216,9 +97,7 @@
     applicationEntityFilter_ = NULL;
     checkCalledAet_ = true;
     clientTimeout_ = 30;
-    isThreaded_ = true;
     continue_ = false;
-    started_ = false;
   }
 
   DicomServer::~DicomServer()
@@ -241,17 +120,6 @@
     return port_;
   }
 
-  void DicomServer::SetThreaded(bool isThreaded)
-  {
-    Stop();
-    isThreaded_ = isThreaded;
-  }
-
-  bool DicomServer::IsThreaded() const
-  {
-    return isThreaded_;
-  }
-
   void DicomServer::SetClientTimeout(uint32_t timeout)
   {
     Stop();
@@ -403,14 +271,19 @@
   void DicomServer::Start()
   {
     Stop();
-    continue_ = true;
-    started_ = false;
-    pimpl_->thread_ = boost::thread(ServerThread, this);
 
-    while (!started_)
+    /* initialize network, i.e. create an instance of T_ASC_Network*. */
+    OFCondition cond = ASC_initializeNetwork
+      (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
+    if (cond.bad())
     {
-      Toolbox::USleep(50000);  // Wait 50ms
+      LOG(ERROR) << "cannot create network: " << cond.text();
+      throw OrthancException(ErrorCode_DicomPortInUse);
     }
+
+    continue_ = true;
+    pimpl_->workers_.reset(new RunnableWorkersPool(4));   // Use 4 workers - TODO as a parameter?
+    pimpl_->thread_ = boost::thread(ServerThread, this);
   }
 
 
@@ -425,7 +298,15 @@
         pimpl_->thread_.join();
       }
 
-      bagOfDispatchers_.Finalize();
+      pimpl_->workers_.reset(NULL);
+
+      /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
+      /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
+      OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
+      if (cond.bad())
+      {
+        LOG(ERROR) << "Error while dropping the network: " << cond.text();
+      }
     }
   }
 
--- a/OrthancServer/DicomProtocol/DicomServer.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Wed Nov 18 10:16:21 2015 +0100
@@ -36,11 +36,11 @@
 #include "IMoveRequestHandlerFactory.h"
 #include "IStoreRequestHandlerFactory.h"
 #include "IApplicationEntityFilter.h"
-#include "../../Core/MultiThreading/BagOfRunnablesBySteps.h"
 
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 
+
 namespace Orthanc
 {
   class DicomServer : public boost::noncopyable
@@ -55,19 +55,14 @@
     bool continue_;
     bool started_;
     uint32_t clientTimeout_;
-    bool isThreaded_;
     IFindRequestHandlerFactory* findRequestHandlerFactory_;
     IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
     IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
     IApplicationEntityFilter* applicationEntityFilter_;
 
-    BagOfRunnablesBySteps bagOfDispatchers_;  // This is used iff the server is threaded
-
     static void ServerThread(DicomServer* server);
 
   public:
-    static void InitializeDictionary();
-
     DicomServer();
 
     ~DicomServer();
@@ -75,9 +70,6 @@
     void SetPortNumber(uint16_t port);
     uint16_t GetPortNumber() const;
 
-    void SetThreaded(bool isThreaded);
-    bool IsThreaded() const;
-
     void SetClientTimeout(uint32_t timeout);
     uint32_t GetClientTimeout() const;
 
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -272,10 +272,22 @@
     const std::string syntax(xfer.getXferID());
     bool isGeneric = IsGenericTransferSyntax(syntax);
 
-    if (isGeneric ^ IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()))
+    bool renegociate;
+    if (isGeneric)
     {
-      // Making a generic-to-specific or specific-to-generic change of
-      // the transfer syntax. Renegotiate the connection.
+      // Are we making a generic-to-specific or specific-to-generic change of
+      // the transfer syntax? If this is the case, renegotiate the connection.
+      renegociate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax());
+    }
+    else
+    {
+      // We are using a specific transfer syntax. Renegociate if the
+      // current connection does not match this transfer syntax.
+      renegociate = (syntax != connection.GetPreferredTransferSyntax());
+    }
+
+    if (renegociate)
+    {
       LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated";
 
       if (isGeneric)
@@ -372,8 +384,9 @@
   }
 
 
-  static void CheckFindQuery(ResourceType level,
-                             const DicomMap& fields)
+  static void FixFindQuery(DicomMap& fixedQuery,
+                           ResourceType level,
+                           const DicomMap& fields)
   {
     std::set<DicomTag> allowedTags;
 
@@ -410,8 +423,11 @@
       const DicomTag& tag = query.GetElement(i).GetTag();
       if (allowedTags.find(tag) == allowedTags.end())
       {
-        LOG(ERROR) << "Tag not allowed for this C-Find level: " << tag;
-        throw OrthancException(ErrorCode_BadRequest);
+        LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag;
+      }
+      else
+      {
+        fixedQuery.SetValue(tag, query.GetElement(i).GetValue());
       }
     }
   }
@@ -441,7 +457,8 @@
             const DicomValue* value = fix->TestAndGetValue(*it);
 
             if (value != NULL && 
-                value->AsString() == "*")
+                !value->IsNull() &&
+                value->GetContent() == "*")
             {
               fix->SetValue(*it, "");
             }
@@ -459,9 +476,10 @@
 
   void DicomUserConnection::Find(DicomFindAnswers& result,
                                  ResourceType level,
-                                 const DicomMap& fields)
+                                 const DicomMap& originalFields)
   {
-    CheckFindQuery(level, fields);
+    DicomMap fields;
+    FixFindQuery(fields, level, originalFields);
 
     CheckIsOpen();
 
@@ -931,7 +949,7 @@
       throw OrthancException(ErrorCode_InternalError);
     }
 
-    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).AsString();
+    const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent();
     ResourceType level = StringToResourceType(tmp.c_str());
 
     DicomMap move;
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -50,7 +50,7 @@
 
   RemoteModalityParameters::RemoteModalityParameters(const std::string& aet,
                                                      const std::string& host,
-                                                     int port,
+                                                     uint16_t port,
                                                      ModalityManufacturer manufacturer)
   {
     SetApplicationEntityTitle(aet);
@@ -60,16 +60,6 @@
   }
 
 
-  void RemoteModalityParameters::SetPort(int port)
-  {
-    if (port <= 0 || port >= 65535)
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    port_ = port;
-  }
-
   void RemoteModalityParameters::FromJson(const Json::Value& modality)
   {
     if (!modality.isArray() ||
@@ -84,13 +74,20 @@
     const Json::Value& portValue = modality.get(2u, "");
     try
     {
-      SetPort(portValue.asInt());
+      int tmp = portValue.asInt();
+
+      if (tmp <= 0 || tmp >= 65535)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      SetPort(static_cast<uint16_t>(tmp));
     }
     catch (std::runtime_error /* error inside JsonCpp */)
     {
       try
       {
-        SetPort(boost::lexical_cast<int>(portValue.asString()));
+        SetPort(boost::lexical_cast<uint16_t>(portValue.asString()));
       }
       catch (boost::bad_lexical_cast)
       {
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.h	Wed Nov 18 10:16:21 2015 +0100
@@ -34,6 +34,7 @@
 
 #include "../ServerEnumerations.h"
 
+#include <stdint.h>
 #include <string>
 #include <json/json.h>
 
@@ -44,7 +45,7 @@
   private:
     std::string aet_;
     std::string host_;
-    int port_;
+    uint16_t port_;
     ModalityManufacturer manufacturer_;
 
   public:
@@ -52,7 +53,7 @@
 
     RemoteModalityParameters(const std::string& aet,
                              const std::string& host,
-                             int port,
+                             uint16_t port,
                              ModalityManufacturer manufacturer);
 
     const std::string& GetApplicationEntityTitle() const
@@ -75,12 +76,15 @@
       host_ = host;
     }
     
-    int GetPort() const
+    uint16_t GetPort() const
     {
       return port_;
     }
 
-    void SetPort(int port);
+    void SetPort(uint16_t port)
+    {
+      port_ = port;
+    }
 
     ModalityManufacturer GetManufacturer() const
     {
--- a/OrthancServer/FromDcmtkBridge.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -47,14 +47,13 @@
 #include "../Core/OrthancException.h"
 #include "../Core/Images/PngWriter.h"
 #include "../Core/Uuid.h"
-#include "../Core/DicomFormat/DicomString.h"
-#include "../Core/DicomFormat/DicomNullValue.h"
 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 
 #include <list>
 #include <limits>
 
 #include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
 
 #include <dcmtk/dcmdata/dcchrstr.h>
 #include <dcmtk/dcmdata/dcdicent.h>
@@ -91,8 +90,10 @@
 #include <dcmtk/dcmdata/dcpxitem.h>
 #include <dcmtk/dcmdata/dcvrat.h>
 
+#include <dcmtk/dcmnet/dul.h>
 
 #include <boost/math/special_functions/round.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 #include <dcmtk/dcmdata/dcostrmb.h>
 
 
@@ -119,6 +120,175 @@
   }
 
 
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+  static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
+                                     EmbeddedResources::FileResourceId resource)
+  {
+    Toolbox::TemporaryFile tmp;
+
+    FILE* fp = fopen(tmp.GetPath().c_str(), "wb");
+    fwrite(EmbeddedResources::GetFileResourceBuffer(resource), 
+           EmbeddedResources::GetFileResourceSize(resource), 1, fp);
+    fclose(fp);
+
+    if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+                             
+#else
+  static void LoadExternalDictionary(DcmDataDictionary& dictionary,
+                                     const std::string& directory,
+                                     const std::string& filename)
+  {
+    boost::filesystem::path p = directory;
+    p = p / filename;
+
+    LOG(WARNING) << "Loading the external DICOM dictionary " << p;
+
+    if (!dictionary.loadDictionary(p.string().c_str()))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+                            
+#endif
+
+
+  namespace
+  {
+    class DictionaryLocker
+    {
+    private:
+      DcmDataDictionary& dictionary_;
+
+    public:
+      DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
+      {
+      }
+
+      ~DictionaryLocker()
+      {
+        dcmDataDict.unlock();
+      }
+
+      DcmDataDictionary& operator*()
+      {
+        return dictionary_;
+      }
+
+      DcmDataDictionary* operator->()
+      {
+        return &dictionary_;
+      }
+    };
+  }
+
+
+  void FromDcmtkBridge::InitializeDictionary()
+  {
+    /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */
+    dcmDisableGethostbyaddr.set(OFTrue);
+
+    {
+      DictionaryLocker locker;
+
+      locker->clear();
+
+#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
+      LOG(WARNING) << "Loading the embedded dictionaries";
+      /**
+       * Do not load DICONDE dictionary, it breaks the other tags. The
+       * command "strace storescu 2>&1 |grep dic" shows that DICONDE
+       * dictionary is not loaded by storescu.
+       **/
+      //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE);
+
+      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM);
+      LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE);
+
+#elif defined(__linux) || defined(__FreeBSD_kernel__)
+      std::string path = DCMTK_DICTIONARY_DIR;
+
+      const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
+      if (env != NULL)
+      {
+        path = std::string(env);
+      }
+
+      LoadExternalDictionary(*locker, path, "dicom.dic");
+      LoadExternalDictionary(*locker, path, "private.dic");
+
+#else
+#error Support your platform here
+#endif
+    }
+
+    /* make sure data dictionary is loaded */
+    if (!dcmDataDict.isDictionaryLoaded())
+    {
+      LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE;
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    {
+      // Test the dictionary with a simple DICOM tag
+      DcmTag key(0x0010, 0x1030); // This is PatientWeight
+      if (key.getEVR() != EVR_DS)
+      {
+        LOG(ERROR) << "The DICOM dictionary has not been correctly read";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  }
+
+
+  void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
+                                              const DcmEVR& vr,
+                                              const std::string& name,
+                                              unsigned int minMultiplicity,
+                                              unsigned int maxMultiplicity)
+  {
+    if (minMultiplicity < 1)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    bool arbitrary = false;
+    if (maxMultiplicity == 0)
+    {
+      maxMultiplicity = DcmVariableVM;
+      arbitrary = true;
+    }
+    else if (maxMultiplicity < minMultiplicity)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(vr).getValidVRName()) << " " 
+              << name << " (multiplicity: " << minMultiplicity << "-" 
+              << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
+
+    std::auto_ptr<DcmDictEntry>  entry(new DcmDictEntry(tag.GetGroup(),
+                                                        tag.GetElement(),
+                                                        vr, name.c_str(),
+                                                        static_cast<int>(minMultiplicity),
+                                                        static_cast<int>(maxMultiplicity),
+                                                        NULL    /* version */,
+                                                        OFTrue  /* doCopyString */,
+                                                        NULL    /* private creator */));
+
+    entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
+    entry->setElementRangeRestriction(DcmDictRange_Unspecified);
+
+    {
+      DictionaryLocker locker;
+      locker->addEntry(entry.release());
+    }
+  }
+
+
   Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset)
   {
     // By default, Latin1 encoding is assumed
@@ -166,7 +336,7 @@
       {
         target.SetValue(element->getTag().getGTag(),
                         element->getTag().getETag(),
-                        ConvertLeafElement(*element, encoding));
+                        ConvertLeafElement(*element, DicomToJsonFlags_Default, encoding));
       }
     }
   }
@@ -184,19 +354,6 @@
   }
 
 
-  bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag)
-  {
-#if 1
-    DcmTagKey tmp(tag.getGTag(), tag.getETag());
-    return tmp.isPrivate();
-#else
-    // Implementation for Orthanc versions <= 0.8.5
-    return (tag.getPrivateCreator() != NULL ||
-            !strcmp("PrivateCreator", tag.getTagName()));  // TODO - This may change with future versions of DCMTK
-#endif
-  }
-
-
   bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag)
   {
 #if 1
@@ -211,6 +368,7 @@
 
 
   DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
+                                                  DicomToJsonFlags flags,
                                                   Encoding encoding)
   {
     if (!element.isLeaf())
@@ -226,18 +384,18 @@
       {
         if (c == NULL)  // This case corresponds to the empty string
         {
-          return new DicomString("");
+          return new DicomValue("", false);
         }
         else
         {
           std::string s(c);
           std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
-          return new DicomString(utf8);
+          return new DicomValue(utf8, false);
         }
       }
       else
       {
-        return new DicomNullValue;
+        return new DicomValue;
       }
     }
 
@@ -248,14 +406,26 @@
       {
 
         /**
-         * TODO.
+         * Deal with binary data (including PixelData).
          **/
 
         case EVR_OB:  // other byte
         case EVR_OF:  // other float
         case EVR_OW:  // other word
         case EVR_UN:  // unknown value representation
-          return new DicomNullValue;
+        case EVR_ox:  // OB or OW depending on context
+        {
+          if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
+          {
+            Uint8* data = NULL;
+            if (element.getUint8Array(data) == EC_Normal)
+            {
+              return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
+            }
+          }
+
+          return new DicomValue;
+        }
     
           /**
            * String types, should never happen at this point because of
@@ -277,7 +447,7 @@
         case EVR_UT:  // unlimited text
         case EVR_PN:  // person name
         case EVR_UI:  // unique identifier
-          return new DicomNullValue;
+          return new DicomValue;
 
 
           /**
@@ -288,54 +458,54 @@
         {
           Sint32 f;
           if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
         case EVR_SS:  // signed short
         {
           Sint16 f;
           if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
         case EVR_UL:  // unsigned long
         {
           Uint32 f;
           if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
         case EVR_US:  // unsigned short
         {
           Uint16 f;
           if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
         case EVR_FL:  // float single-precision
         {
           Float32 f;
           if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
         case EVR_FD:  // float double-precision
         {
           Float64 f;
           if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
-            return new DicomString(boost::lexical_cast<std::string>(f));
+            return new DicomValue(boost::lexical_cast<std::string>(f), false);
           else
-            return new DicomNullValue;
+            return new DicomValue;
         }
 
 
@@ -349,11 +519,11 @@
           if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
           {
             DicomTag t(tag.getGroup(), tag.getElement());
-            return new DicomString(t.Format());
+            return new DicomValue(t.Format(), false);
           }
           else
           {
-            return new DicomNullValue;
+            return new DicomValue;
           }
         }
 
@@ -364,14 +534,13 @@
          **/
 
         case EVR_SQ:  // sequence of items
-          return new DicomNullValue;
+          return new DicomValue;
 
 
           /**
            * Internal to DCMTK.
            **/ 
 
-        case EVR_ox:  // OB or OW depending on context
         case EVR_xs:  // SS or US depending on context
         case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
         case EVR_na:  // na="not applicable", for data which has no VR
@@ -388,7 +557,7 @@
         case EVR_PixelData:  // used internally for uncompressed pixeld data
         case EVR_OverlayData:  // used internally for overlay data
         case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-          return new DicomNullValue;
+          return new DicomValue;
 
 
           /**
@@ -396,141 +565,271 @@
            **/ 
 
         default:
-          return new DicomNullValue;
+          return new DicomValue;
       }
     }
     catch (boost::bad_lexical_cast)
     {
-      return new DicomNullValue;
+      return new DicomValue;
     }
     catch (std::bad_cast)
     {
-      return new DicomNullValue;
+      return new DicomValue;
     }
   }
 
 
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength,
-                           Encoding encoding);
-
-  static void StoreItem(Json::Value& target,
-                        DcmItem& item,
-                        unsigned int maxStringLength,
-                        Encoding encoding)
+  static Json::Value& PrepareNode(Json::Value& parent,
+                                  DcmElement& element,
+                                  DicomToJsonFormat format)
   {
-    target = Json::Value(Json::objectValue);
-
-    for (unsigned long i = 0; i < item.card(); i++)
-    {
-      DcmElement* element = item.getElement(i);
-      StoreElement(target, *element, maxStringLength, encoding);
-    }
-  }
-
-
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength,
-                           Encoding encoding)
-  {
-    assert(target.type() == Json::objectValue);
+    assert(parent.type() == Json::objectValue);
 
     DicomTag tag(FromDcmtkBridge::GetTag(element));
     const std::string formattedTag = tag.Format();
 
-#if 0
-    const std::string tagName = FromDcmtkBridge::GetName(tag);
-#else
-    // This version of the code gives access to the name of the private tags
+    if (format == DicomToJsonFormat_Short)
+    {
+      parent[formattedTag] = Json::nullValue;
+      return parent[formattedTag];
+    }
+
+    // This code gives access to the name of the private tags
     DcmTag tagbis(element.getTag());
     const std::string tagName(tagbis.getTagName());      
-#endif
+    
+    switch (format)
+    {
+      case DicomToJsonFormat_Simple:
+        parent[tagName] = Json::nullValue;
+        return parent[tagName];
+
+      case DicomToJsonFormat_Full:
+      {
+        parent[formattedTag] = Json::objectValue;
+        Json::Value& node = parent[formattedTag];
+
+        if (element.isLeaf())
+        {
+          node["Name"] = tagName;
+
+          if (tagbis.getPrivateCreator() != NULL)
+          {
+            node["PrivateCreator"] = tagbis.getPrivateCreator();
+          }
+
+          return node;
+        }
+        else
+        {
+          node["Name"] = tagName;
+          node["Type"] = "Sequence";
+          node["Value"] = Json::nullValue;
+          return node["Value"];
+        }
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void LeafValueToJson(Json::Value& target,
+                              const DicomValue& value,
+                              DicomToJsonFormat format,
+                              DicomToJsonFlags flags,
+                              unsigned int maxStringLength)
+  {
+    Json::Value* targetValue = NULL;
+    Json::Value* targetType = NULL;
+
+    switch (format)
+    {
+      case DicomToJsonFormat_Short:
+      case DicomToJsonFormat_Simple:
+      {
+        assert(target.type() == Json::nullValue);
+        targetValue = &target;
+        break;
+      }      
+
+      case DicomToJsonFormat_Full:
+      {
+        assert(target.type() == Json::objectValue);
+        target["Value"] = Json::nullValue;
+        target["Type"] = Json::nullValue;
+        targetType = &target["Type"];
+        targetValue = &target["Value"];
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    assert(targetValue != NULL);
+    assert(targetValue->type() == Json::nullValue);
+    assert(targetType == NULL || targetType->type() == Json::nullValue);
+
+    if (value.IsNull())
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "Null";
+      }
+    }
+    else if (value.IsBinary())
+    {
+      if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
+      {
+        *targetValue = Toolbox::ConvertToAscii(value.GetContent());
+      }
+      else
+      {
+        std::string s;
+        value.FormatDataUriScheme(s);
+        *targetValue = s;
+      }
+
+      if (targetType != NULL)
+      {
+        *targetType = "Binary";
+      }
+    }
+    else if (maxStringLength == 0 ||
+             value.GetContent().size() <= maxStringLength)
+    {
+      *targetValue = value.GetContent();
+
+      if (targetType != NULL)
+      {
+        *targetType = "String";
+      }
+    }
+    else
+    {
+      if (targetType != NULL)
+      {
+        *targetType = "TooLong";
+      }
+    }
+  }                              
+
+
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            DicomToJsonFlags flags,
+                            unsigned int maxStringLength,
+                            Encoding encoding);
+
+
+  void FromDcmtkBridge::ToJson(Json::Value& parent,
+                               DcmElement& element,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength,
+                               Encoding encoding)
+  {
+    if (parent.type() == Json::nullValue)
+    {
+      parent = Json::objectValue;
+    }
+
+    assert(parent.type() == Json::objectValue);
+    Json::Value& target = PrepareNode(parent, element, format);
 
     if (element.isLeaf())
     {
-      Json::Value value(Json::objectValue);
-      value["Name"] = tagName;
-
-      if (tagbis.getPrivateCreator() != NULL)
-      {
-        value["PrivateCreator"] = tagbis.getPrivateCreator();
-      }
-
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, encoding));
-      if (v->IsNull())
-      {
-        value["Type"] = "Null";
-        value["Value"] = Json::nullValue;
-      }
-      else
-      {
-        std::string s = v->AsString();
-        if (maxStringLength == 0 ||
-            s.size() <= maxStringLength)
-        {
-          value["Type"] = "String";
-          value["Value"] = s;
-        }
-        else
-        {
-          value["Type"] = "TooLong";
-          value["Value"] = Json::nullValue;
-        }
-      }
-
-      target[formattedTag] = value;
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, encoding));
+      LeafValueToJson(target, *v, format, flags, maxStringLength);
     }
     else
     {
-      Json::Value children(Json::arrayValue);
+      assert(target.type() == Json::nullValue);
+      target = Json::arrayValue;
 
       // "All subclasses of DcmElement except for DcmSequenceOfItems
       // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
-      // etc. are not." The following cast is thus OK.
+      // etc. are not." The following dynamic_cast is thus OK.
       DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
 
       for (unsigned long i = 0; i < sequence.card(); i++)
       {
         DcmItem* child = sequence.getItem(i);
-        Json::Value& v = children.append(Json::objectValue);
-        StoreItem(v, *child, maxStringLength, encoding);
-      }  
-
-      target[formattedTag]["Name"] = tagName;
-      target[formattedTag]["Type"] = "Sequence";
-      target[formattedTag]["Value"] = children;
+        Json::Value& v = target.append(Json::objectValue);
+        DatasetToJson(v, *child, format, flags, maxStringLength, encoding);
+      }
     }
   }
 
 
-  void FromDcmtkBridge::ToJson(Json::Value& root, 
-                               DcmDataset& dataset,
-                               unsigned int maxStringLength)
+  static void DatasetToJson(Json::Value& parent,
+                            DcmItem& item,
+                            DicomToJsonFormat format,
+                            DicomToJsonFlags flags,
+                            unsigned int maxStringLength,
+                            Encoding encoding)
   {
-    StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset));
+    assert(parent.type() == Json::objectValue);
+
+    for (unsigned long i = 0; i < item.card(); i++)
+    {
+      DcmElement* element = item.getElement(i);
+      if (element == NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (!(flags & DicomToJsonFlags_IncludePrivateTags) &&
+          element->getTag().isPrivate())
+      {
+        continue;
+      }
+
+      if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
+      {
+        DictionaryLocker locker;
+        if (locker->findEntry(element->getTag(), NULL) == NULL)
+        {
+          continue;
+        }
+      }
+
+      DcmEVR evr = element->getTag().getEVR();
+      if (evr == EVR_OB ||
+          evr == EVR_OF ||
+          evr == EVR_OW ||
+          evr == EVR_UN ||
+          evr == EVR_ox)
+      {
+        // This is a binary tag
+        DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
+
+        if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
+            (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
+        {
+          continue;
+        }
+      }
+
+      FromDcmtkBridge::ToJson(parent, *element, format, flags, maxStringLength, encoding);
+    }
   }
 
 
-
   void FromDcmtkBridge::ToJson(Json::Value& target, 
-                               const std::string& path,
+                               DcmDataset& dataset,
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
                                unsigned int maxStringLength)
   {
-    DcmFileFormat dicom;
-    if (!dicom.loadFile(path.c_str()).good())
-    {
-      throw OrthancException(ErrorCode_BadFileFormat);
-    }
-    else
-    {
-      FromDcmtkBridge::ToJson(target, *dicom.getDataset(), maxStringLength);
-    }
+    target = Json::objectValue;
+    DatasetToJson(target, dataset, format, flags, maxStringLength, DetectEncoding(dataset));
   }
 
 
-
   std::string FromDcmtkBridge::GetName(const DicomTag& t)
   {
     // Some patches for important tags because of different DICOM
@@ -618,15 +917,10 @@
   }
 
 
-  void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m)
+  bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
   {
-    for (DicomMap::Map::const_iterator 
-           it = m.map_.begin(); it != m.map_.end(); ++it)
-    {
-      DicomTag t = it->first;
-      std::string s = it->second->AsString();
-      fprintf(fp, "0x%04x 0x%04x (%s) [%s]\n", t.GetGroup(), t.GetElement(), GetName(t).c_str(), s.c_str());
-    }
+    DcmTag tmp(tag.GetGroup(), tag.GetElement());
+    return tmp.isUnknownVR();
   }
 
 
@@ -646,7 +940,15 @@
     {
       if (simplify)
       {
-        result[GetName(it->first)] = it->second->AsString();
+        if (it->second->IsNull())
+        {
+          result[GetName(it->first)] = Json::nullValue;
+        }
+        else
+        {
+          // TODO IsBinary
+          result[GetName(it->first)] = it->second->GetContent();
+        }
       }
       else
       {
@@ -661,8 +963,9 @@
         }
         else
         {
+          // TODO IsBinary
           value["Type"] = "String";
-          value["Value"] = it->second->AsString();
+          value["Value"] = it->second->GetContent();
         }
 
         result[it->first.Format()] = value;
@@ -775,4 +1078,467 @@
     }
   }
 
+
+  static bool IsBinaryTag(const DcmTag& key)
+  {
+    return (key.isPrivate() || 
+            key.isUnknownVR() || 
+            key.getEVR() == EVR_OB ||
+            key.getEVR() == EVR_OF ||
+            key.getEVR() == EVR_OW ||
+            key.getEVR() == EVR_UN ||
+            key.getEVR() == EVR_ox);
+  }
+
+
+  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (IsBinaryTag(key))
+    {
+      return new DcmOtherByteOtherWord(key);
+    }
+
+    switch (key.getEVR())
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+      /**
+       * Binary types, handled above
+       **/
+    
+      case EVR_OB:  // other byte
+      case EVR_OF:  // other float
+      case EVR_OW:  // other word
+      case EVR_UN:  // unknown value representation
+      case EVR_ox:  // OB or OW depending on context
+        throw OrthancException(ErrorCode_InternalError);
+
+
+      /**
+       * String types.
+       * http://support.dcmtk.org/docs/classDcmByteString.html
+       **/
+      
+      case EVR_AS:  // age string
+        return new DcmAgeString(key);
+
+      case EVR_AE:  // application entity title
+        return new DcmApplicationEntity(key);
+
+      case EVR_CS:  // code string
+        return new DcmCodeString(key);        
+
+      case EVR_DA:  // date string
+        return new DcmDate(key);
+        
+      case EVR_DT:  // date time string
+        return new DcmDateTime(key);
+
+      case EVR_DS:  // decimal string
+        return new DcmDecimalString(key);
+
+      case EVR_IS:  // integer string
+        return new DcmIntegerString(key);
+
+      case EVR_TM:  // time string
+        return new DcmTime(key);
+
+      case EVR_UI:  // unique identifier
+        return new DcmUniqueIdentifier(key);
+
+      case EVR_ST:  // short text
+        return new DcmShortText(key);
+
+      case EVR_LO:  // long string
+        return new DcmLongString(key);
+
+      case EVR_LT:  // long text
+        return new DcmLongText(key);
+
+      case EVR_UT:  // unlimited text
+        return new DcmUnlimitedText(key);
+
+      case EVR_SH:  // short string
+        return new DcmShortString(key);
+
+      case EVR_PN:  // person name
+        return new DcmPersonName(key);
+
+        
+      /**
+       * Numerical types
+       **/ 
+      
+      case EVR_SL:  // signed long
+        return new DcmSignedLong(key);
+
+      case EVR_SS:  // signed short
+        return new DcmSignedShort(key);
+
+      case EVR_UL:  // unsigned long
+        return new DcmUnsignedLong(key);
+
+      case EVR_US:  // unsigned short
+        return new DcmUnsignedShort(key);
+
+      case EVR_FL:  // float single-precision
+        return new DcmFloatingPointSingle(key);
+
+      case EVR_FD:  // float double-precision
+        return new DcmFloatingPointDouble(key);
+
+
+      /**
+       * Sequence types, should never occur at this point.
+       **/
+
+      case EVR_SQ:  // sequence of items
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+      /**
+       * TODO
+       **/
+
+      case EVR_AT:  // attribute tag
+        throw OrthancException(ErrorCode_NotImplemented);
+
+
+      /**
+       * Internal to DCMTK.
+       **/ 
+
+      case EVR_xs:  // SS or US depending on context
+      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+      case EVR_na:  // na="not applicable", for data which has no VR
+      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+      case EVR_item:  // used internally for items
+      case EVR_metainfo:  // used internally for meta info datasets
+      case EVR_dataset:  // used internally for datasets
+      case EVR_fileFormat:  // used internally for DICOM files
+      case EVR_dicomDir:  // used internally for DICOMDIR objects
+      case EVR_dirRecord:  // used internally for DICOMDIR records
+      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+      case EVR_pixelItem:  // used internally for pixel items in a compressed image
+      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+      case EVR_PixelData:  // used internally for uncompressed pixeld data
+      case EVR_OverlayData:  // used internally for overlay data
+      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+      default:
+        break;
+    }
+
+    throw OrthancException(ErrorCode_InternalError);          
+  }
+
+
+
+  void FromDcmtkBridge::FillElementWithString(DcmElement& element,
+                                              const DicomTag& tag,
+                                              const std::string& utf8Value,
+                                              bool decodeBinaryTags,
+                                              Encoding dicomEncoding)
+  {
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeBinaryTags &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      Toolbox::DecodeDataUriScheme(mime, binary, utf8Value);
+      decoded = &binary;
+    }
+    else if (dicomEncoding != Encoding_Utf8)
+    {
+      binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
+      decoded = &binary;
+    }
+
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (IsBinaryTag(key))
+    {
+      if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good())
+      {
+        return;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    bool ok = false;
+    
+    try
+    {
+      switch (key.getEVR())
+      {
+        // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+        /**
+         * TODO.
+         **/
+
+        case EVR_OB:  // other byte
+        case EVR_OF:  // other float
+        case EVR_OW:  // other word
+        case EVR_AT:  // attribute tag
+          throw OrthancException(ErrorCode_NotImplemented);
+    
+        case EVR_UN:  // unknown value representation
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+        /**
+         * String types.
+         **/
+      
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
+        case EVR_AE:  // application entity title
+        case EVR_CS:  // code string
+        case EVR_SH:  // short string
+        case EVR_LO:  // long string
+        case EVR_ST:  // short text
+        case EVR_LT:  // long text
+        case EVR_UT:  // unlimited text
+        case EVR_PN:  // person name
+        case EVR_UI:  // unique identifier
+        {
+          ok = element.putString(decoded->c_str()).good();
+          break;
+        }
+
+        
+        /**
+         * Numerical types
+         **/ 
+      
+        case EVR_SL:  // signed long
+        {
+          ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
+          break;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
+          break;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
+          break;
+        }
+
+
+        /**
+         * Sequence types, should never occur at this point.
+         **/
+
+        case EVR_SQ:  // sequence of items
+        {
+          ok = false;
+          break;
+        }
+
+
+        /**
+         * Internal to DCMTK.
+         **/ 
+
+        case EVR_ox:  // OB or OW depending on context
+        case EVR_xs:  // SS or US depending on context
+        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+        case EVR_na:  // na="not applicable", for data which has no VR
+        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+        case EVR_item:  // used internally for items
+        case EVR_metainfo:  // used internally for meta info datasets
+        case EVR_dataset:  // used internally for datasets
+        case EVR_fileFormat:  // used internally for DICOM files
+        case EVR_dicomDir:  // used internally for DICOMDIR objects
+        case EVR_dirRecord:  // used internally for DICOMDIR records
+        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+        case EVR_pixelItem:  // used internally for pixel items in a compressed image
+        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+        case EVR_PixelData:  // used internally for uncompressed pixeld data
+        case EVR_OverlayData:  // used internally for overlay data
+        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+        default:
+          break;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ok = false;
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
+                                        const Json::Value& value,
+                                        bool decodeBinaryTags,
+                                        Encoding dicomEncoding)
+  {
+    std::auto_ptr<DcmElement> element;
+
+    switch (value.type())
+    {
+      case Json::stringValue:
+        element.reset(CreateElementForTag(tag));
+        FillElementWithString(*element, tag, value.asString(), decodeBinaryTags, dicomEncoding);
+        break;
+
+      case Json::arrayValue:
+      {
+        DcmTag key(tag.GetGroup(), tag.GetElement());
+        if (key.getEVR() != EVR_SQ)
+        {
+          throw OrthancException(ErrorCode_BadParameterType);
+        }
+
+        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size());
+        element.reset(sequence);
+        
+        for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+        {
+          std::auto_ptr<DcmItem> item(new DcmItem);
+
+          Json::Value::Members members = value[i].getMemberNames();
+          for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
+          {
+            item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeBinaryTags, dicomEncoding));
+          }
+
+          sequence->append(item.release());
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return element.release();
+  }
+
+
+  DcmEVR FromDcmtkBridge::ParseValueRepresentation(const std::string& s)
+  {
+    if (s == "AE")
+      return EVR_AE;
+
+    if (s == "AS")
+      return EVR_AS;
+
+    if (s == "AT")
+      return EVR_AT;
+
+    if (s == "CS")
+      return EVR_CS;
+
+    if (s == "DA")
+      return EVR_DA;
+
+    if (s == "DS")
+      return EVR_DS;
+
+    if (s == "DT")
+      return EVR_DT;
+
+    if (s == "FD")
+      return EVR_FD;
+
+    if (s == "FL")
+      return EVR_FL;
+
+    if (s == "IS")
+      return EVR_IS;
+
+    if (s == "LO")
+      return EVR_LO;
+
+    if (s == "LT")
+      return EVR_LT;
+
+    if (s == "OB")
+      return EVR_OB;
+
+    if (s == "OF")
+      return EVR_OF;
+
+    if (s == "OW")
+      return EVR_OW;
+
+    if (s == "PN")
+      return EVR_PN;
+
+    if (s == "SH")
+      return EVR_SH;
+
+    if (s == "SL")
+      return EVR_SL;
+
+    if (s == "SQ")
+      return EVR_SQ;
+
+    if (s == "SS")
+      return EVR_SS;
+
+    if (s == "ST")
+      return EVR_ST;
+
+    if (s == "TM")
+      return EVR_TM;
+
+    if (s == "UI")
+      return EVR_UI;
+
+    if (s == "UL")
+      return EVR_UL;
+
+    if (s == "UN")
+      return EVR_UN;
+
+    if (s == "US")
+      return EVR_US;
+
+    if (s == "UT")
+      return EVR_UT;
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
 }
--- a/OrthancServer/FromDcmtkBridge.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Nov 18 10:16:21 2015 +0100
@@ -44,6 +44,14 @@
   class FromDcmtkBridge
   {
   public:
+    static void InitializeDictionary();
+
+    static void RegisterDictionaryTag(const DicomTag& tag,
+                                      const DcmEVR& vr,
+                                      const std::string& name,
+                                      unsigned int minMultiplicity,
+                                      unsigned int maxMultiplicity);
+
     static Encoding DetectEncoding(DcmDataset& dataset);
 
     static void Convert(DicomMap& target, DcmDataset& dataset);
@@ -52,20 +60,26 @@
 
     static DicomTag GetTag(const DcmElement& element);
 
-    static bool IsPrivateTag(DcmTag& tag);
-
     static bool IsPrivateTag(const DicomTag& tag);
 
+    static bool IsUnknownTag(const DicomTag& tag);
+
     static DicomValue* ConvertLeafElement(DcmElement& element,
+                                          DicomToJsonFlags flags,
                                           Encoding encoding);
 
+    static void ToJson(Json::Value& parent,
+                       DcmElement& element,
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength,
+                       Encoding dicomEncoding);
+
     static void ToJson(Json::Value& target, 
                        DcmDataset& dataset,
-                       unsigned int maxStringLength = 256);       
-
-    static void ToJson(Json::Value& target, 
-                       const std::string& path,
-                       unsigned int maxStringLength = 256);
+                       DicomToJsonFormat format,
+                       DicomToJsonFlags flags,
+                       unsigned int maxStringLength);
 
     static std::string GetName(const DicomTag& tag);
 
@@ -95,9 +109,6 @@
       target.SetValue(ParseTag(tagName), value);
     }
 
-    static void Print(FILE* fp, 
-                      const DicomMap& m);
-
     static void ToJson(Json::Value& result,
                        const DicomMap& values,
                        bool simplify);
@@ -108,5 +119,20 @@
                                    DcmDataset& dataSet);
 
     static ValueRepresentation GetValueRepresentation(const DicomTag& tag);
+
+    static DcmElement* CreateElementForTag(const DicomTag& tag);
+    
+    static void FillElementWithString(DcmElement& element,
+                                      const DicomTag& tag,
+                                      const std::string& utf8alue,  // Encoded using UTF-8
+                                      bool interpretBinaryTags,
+                                      Encoding dicomEncoding);
+
+    static DcmElement* FromJson(const DicomTag& tag,
+                                const Json::Value& element,  // Encoding using UTF-8
+                                bool interpretBinaryTags,
+                                Encoding dicomEncoding);
+
+    static DcmEVR ParseValueRepresentation(const std::string& s);
   };
 }
--- a/OrthancServer/IDatabaseWrapper.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/IDatabaseWrapper.h	Wed Nov 18 10:16:21 2015 +0100
@@ -51,6 +51,10 @@
     {
     }
 
+    virtual void Open() = 0;
+
+    virtual void Close() = 0;
+
     virtual void AddAttachment(int64_t id,
                                const FileInfo& attachment) = 0;
 
@@ -79,6 +83,9 @@
     virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
                                 int64_t id) = 0;
 
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   ResourceType resourceType) = 0;
+
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  ResourceType resourceType) = 0;
 
@@ -142,11 +149,10 @@
     virtual bool LookupGlobalProperty(std::string& target,
                                       GlobalProperty property) = 0;
 
-    virtual void LookupIdentifier(std::list<int64_t>& target,
+    virtual void LookupIdentifier(std::list<int64_t>& result,
+                                  ResourceType level,
                                   const DicomTag& tag,
-                                  const std::string& value) = 0;
-
-    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  IdentifierConstraintType type,
                                   const std::string& value) = 0;
 
     virtual bool LookupMetadata(std::string& target,
@@ -168,10 +174,16 @@
     virtual void SetGlobalProperty(GlobalProperty property,
                                    const std::string& value) = 0;
 
+    virtual void ClearMainDicomTags(int64_t id) = 0;
+
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
                                  const std::string& value) = 0;
 
+    virtual void SetIdentifierTag(int64_t id,
+                                  const DicomTag& tag,
+                                  const std::string& value) = 0;
+
     virtual void SetMetadata(int64_t id,
                              MetadataType type,
                              const std::string& value) = 0;
--- a/OrthancServer/Internals/StoreScp.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -168,7 +168,10 @@
           try
           {
             FromDcmtkBridge::Convert(summary, **imageDataSet);
-            FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
+            FromDcmtkBridge::ToJson(dicomJson, **imageDataSet,
+                                    DicomToJsonFormat_Full, 
+                                    DicomToJsonFlags_Default, 
+                                    256 /* max string length */);
 
             if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
             {
@@ -211,7 +214,7 @@
 
                 if (e.GetErrorCode() == ErrorCode_InexistentTag)
                 {
-                  LogMissingRequiredTag(summary);
+                  Toolbox::LogMissingRequiredTag(summary);
                 }
                 else
                 {
--- a/OrthancServer/LuaScripting.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/LuaScripting.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -83,18 +83,23 @@
     const char* uri = lua_tostring(state, 1);
     bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false);
 
-    std::string result;
-    if (HttpToolbox::SimpleGet(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                               RequestOrigin_Lua, uri))
+    try
     {
-      lua_pushlstring(state, result.c_str(), result.size());
+      std::string result;
+      if (HttpToolbox::SimpleGet(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                 RequestOrigin_Lua, uri))
+      {
+        lua_pushlstring(state, result.c_str(), result.size());
+        return 1;
+      }
     }
-    else
+    catch (OrthancException& e)
     {
-      LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri;
-      lua_pushnil(state);
+      LOG(ERROR) << "Lua: " << e.What();
     }
 
+    LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri;
+    lua_pushnil(state);
     return 1;
   }
 
@@ -127,21 +132,26 @@
     const char* bodyData = lua_tolstring(state, 2, &bodySize);
     bool builtin = (nArgs == 3 ? lua_toboolean(state, 3) != 0 : false);
 
-    std::string result;
-    if (isPost ?
-        HttpToolbox::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                RequestOrigin_Lua, uri, bodyData, bodySize) :
-        HttpToolbox::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                               RequestOrigin_Lua, uri, bodyData, bodySize))
+    try
     {
-      lua_pushlstring(state, result.c_str(), result.size());
+      std::string result;
+      if (isPost ?
+          HttpToolbox::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                  RequestOrigin_Lua, uri, bodyData, bodySize) :
+          HttpToolbox::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                 RequestOrigin_Lua, uri, bodyData, bodySize))
+      {
+        lua_pushlstring(state, result.c_str(), result.size());
+        return 1;
+      }
     }
-    else
+    catch (OrthancException& e)
     {
-      LOG(ERROR) << "Lua: Error in " << (isPost ? "RestApiPost()" : "RestApiPut()") << " for URI: " << uri;
-      lua_pushnil(state);
+      LOG(ERROR) << "Lua: " << e.What();
     }
 
+    LOG(ERROR) << "Lua: Error in " << (isPost ? "RestApiPost()" : "RestApiPut()") << " for URI: " << uri;
+    lua_pushnil(state);
     return 1;
   }
 
@@ -185,16 +195,22 @@
     const char* uri = lua_tostring(state, 1);
     bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false);
 
-    if (HttpToolbox::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                  RequestOrigin_Lua, uri))
+    try
     {
-      lua_pushboolean(state, 1);
+      if (HttpToolbox::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                    RequestOrigin_Lua, uri))
+      {
+        lua_pushboolean(state, 1);
+        return 1;
+      }
     }
-    else
+    catch (OrthancException& e)
     {
-      LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri;
+      LOG(ERROR) << "Lua: " << e.What();
+    }
+
+    LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri;
       lua_pushnil(state);
-    }
 
     return 1;
   }
@@ -254,10 +270,12 @@
     if (operation == "modify")
     {
       LOG(INFO) << "Lua script to modify resource " << parameters["Resource"].asString();
-      DicomModification modification;
-      OrthancRestApi::ParseModifyRequest(modification, parameters);
+      std::auto_ptr<DicomModification> modification(new DicomModification);
+      OrthancRestApi::ParseModifyRequest(*modification, parameters);
 
-      std::auto_ptr<ModifyInstanceCommand> command(new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification));
+      std::auto_ptr<ModifyInstanceCommand> command
+        (new ModifyInstanceCommand(context_, RequestOrigin_Lua, modification.release()));
+
       return command.release();
     }
 
@@ -316,7 +334,7 @@
   {
     Json::Value operations;
     LuaFunctionCall call2(lua_, "_AccessJob");
-    call2.ExecuteToJson(operations);
+    call2.ExecuteToJson(operations, false);
      
     if (operations.type() != Json::arrayValue)
     {
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -33,14 +33,12 @@
 #include "PrecompiledHeadersServer.h"
 #include "OrthancFindRequestHandler.h"
 
+#include "../Core/DicomFormat/DicomArray.h"
 #include "../Core/Logging.h"
-#include "../Core/DicomFormat/DicomArray.h"
-#include "ServerToolbox.h"
+#include "FromDcmtkBridge.h"
 #include "OrthancInitialization.h"
-#include "FromDcmtkBridge.h"
-
-#include "ResourceFinder.h"
-#include "DicomFindQuery.h"
+#include "Search/LookupResource.h"
+#include "ServerToolbox.h"
 
 #include <boost/regex.hpp> 
 
@@ -90,130 +88,6 @@
   }
 
 
-  namespace
-  {
-    class CFindQuery : public DicomFindQuery
-    {
-    private:
-      DicomFindAnswers&      answers_;
-      ServerIndex&           index_;
-      const DicomArray&      query_;
-      bool                   hasModalitiesInStudy_;
-      std::set<std::string>  modalitiesInStudy_;
-
-    public:
-      CFindQuery(DicomFindAnswers& answers,
-                 ServerIndex& index,
-                 const DicomArray& query) :
-        answers_(answers),
-        index_(index),
-        query_(query),
-        hasModalitiesInStudy_(false)
-      {
-      }
-
-      void SetModalitiesInStudy(const std::string& value)
-      {
-        hasModalitiesInStudy_ = true;
-        
-        std::vector<std::string>  tmp;
-        Toolbox::TokenizeString(tmp, value, '\\'); 
-
-        for (size_t i = 0; i < tmp.size(); i++)
-        {
-          modalitiesInStudy_.insert(tmp[i]);
-        }
-      }
-
-      virtual bool HasMainDicomTagsFilter(ResourceType level) const
-      {
-        if (DicomFindQuery::HasMainDicomTagsFilter(level))
-        {
-          return true;
-        }
-
-        return (level == ResourceType_Study &&
-                hasModalitiesInStudy_);
-      }
-
-      virtual bool FilterMainDicomTags(const std::string& resourceId,
-                                       ResourceType level,
-                                       const DicomMap& mainTags) const
-      {
-        if (!DicomFindQuery::FilterMainDicomTags(resourceId, level, mainTags))
-        {
-          return false;
-        }
-
-        if (level != ResourceType_Study ||
-            !hasModalitiesInStudy_)
-        {
-          return true;
-        }
-
-        try
-        {
-          // We are considering a single study, and the
-          // "MODALITIES_IN_STUDY" tag is set in the C-Find. Check
-          // whether one of its child series matches one of the
-          // modalities.
-
-          Json::Value study;
-          if (index_.LookupResource(study, resourceId, ResourceType_Study))
-          {
-            // Loop over the series of the considered study.
-            for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++)
-            {
-              Json::Value series;
-              if (index_.LookupResource(series, study["Series"][j].asString(), ResourceType_Series))
-              {
-                // Get the modality of this series
-                if (series["MainDicomTags"].isMember("Modality"))
-                {
-                  std::string modality = series["MainDicomTags"]["Modality"].asString();
-                  if (modalitiesInStudy_.find(modality) != modalitiesInStudy_.end())
-                  {
-                    // This series of the considered study matches one
-                    // of the required modalities. Take the study into
-                    // consideration for future filtering.
-                    return true;
-                  }
-                }
-              }
-            }
-          }
-        }
-        catch (OrthancException&)
-        {
-          // This resource has probably been deleted during the find request
-        }
-
-        return false;
-      }
-
-      virtual bool HasInstanceFilter() const
-      {
-        return true;
-      }
-
-      virtual bool FilterInstance(const std::string& instanceId,
-                                  const Json::Value& content) const
-      {
-        bool ok = DicomFindQuery::FilterInstance(instanceId, content);
-
-        if (ok)
-        {
-          // Add this resource to the answers
-          AddAnswer(answers_, content, query_);
-        }
-
-        return ok;
-      }
-    };
-  }
-
-
-
   bool OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::string& remoteIp,
@@ -240,12 +114,14 @@
      **/
 
     const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
-    if (levelTmp == NULL) 
+    if (levelTmp == NULL ||
+        levelTmp->IsNull() ||
+        levelTmp->IsBinary())
     {
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
+    ResourceType level = StringToResourceType(levelTmp->GetContent().c_str());
 
     if (level != ResourceType_Patient &&
         level != ResourceType_Study &&
@@ -265,7 +141,7 @@
       {
         LOG(INFO) << "  " << query.GetElement(i).GetTag()
                   << "  " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag())
-                  << " = " << query.GetElement(i).GetValue().AsString();
+                  << " = " << query.GetElement(i).GetValue().GetContent();
       }
     }
 
@@ -274,9 +150,8 @@
      * Build up the query object.
      **/
 
-    CFindQuery findQuery(answers, context_.GetIndex(), query);
-    findQuery.SetLevel(level);
-        
+    LookupResource finder(level);
+
     for (size_t i = 0; i < query.GetSize(); i++)
     {
       const DicomTag tag = query.GetElement(i).GetTag();
@@ -288,21 +163,24 @@
         continue;
       }
 
-      std::string value = query.GetElement(i).GetValue().AsString();
+      std::string value = query.GetElement(i).GetValue().GetContent();
       if (value.size() == 0)
       {
         // An empty string corresponds to a "*" wildcard constraint, so we ignore it
         continue;
       }
 
-      if (tag == DICOM_TAG_MODALITIES_IN_STUDY)
+      ValueRepresentation vr = FromDcmtkBridge::GetValueRepresentation(tag);
+
+      // DICOM specifies that searches must be case sensitive, except
+      // for tags with a PN value representation
+      bool sensitive = true;
+      if (vr == ValueRepresentation_PatientName)
       {
-        findQuery.SetModalitiesInStudy(value);
+        sensitive = caseSensitivePN;
       }
-      else
-      {
-        findQuery.SetConstraint(tag, value, caseSensitivePN);
-      }
+
+      finder.AddDicomConstraint(tag, value, sensitive);
     }
 
 
@@ -310,28 +188,35 @@
      * Run the query.
      **/
 
-    ResourceFinder finder(context_);
+    size_t maxResults = (level == ResourceType_Instance) ? maxInstances_ : maxResults_;
+
+    std::vector<std::string> resources, instances;
+    context_.GetIndex().FindCandidates(resources, instances, finder);
 
-    switch (level)
+    assert(resources.size() == instances.size());
+    bool finished = true;
+
+    for (size_t i = 0; i < instances.size(); i++)
     {
-      case ResourceType_Patient:
-      case ResourceType_Study:
-      case ResourceType_Series:
-        finder.SetMaxResults(maxResults_);
-        break;
-
-      case ResourceType_Instance:
-        finder.SetMaxResults(maxInstances_);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
+      Json::Value dicom;
+      context_.ReadJson(dicom, instances[i]);
+      
+      if (finder.IsMatch(dicom))
+      {
+        if (maxResults != 0 &&
+            answers.GetSize() >= maxResults)
+        {
+          finished = false;
+          break;
+        }
+        else
+        {
+          AddAnswer(answers, dicom, query);
+        }
+      }
     }
 
-    std::list<std::string> tmp;
-    bool finished = finder.Apply(tmp, findQuery);
-
-    LOG(INFO) << "Number of matching resources: " << tmp.size();
+    LOG(INFO) << "Number of matching resources: " << answers.GetSize();
 
     return finished;
   }
--- a/OrthancServer/OrthancInitialization.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -39,9 +39,9 @@
 #include "../Core/Toolbox.h"
 #include "../Core/FileStorage/FilesystemStorage.h"
 
-#include "DicomProtocol/DicomServer.h"
 #include "ServerEnumerations.h"
 #include "DatabaseWrapper.h"
+#include "FromDcmtkBridge.h"
 
 #include <boost/lexical_cast.hpp>
 #include <boost/filesystem.hpp>
@@ -252,24 +252,26 @@
       Json::Value::Members members = parameter.getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
       {
-        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
-        LOG(INFO) << "Registering user-defined metadata: " << info;
+        const std::string& name = members[i];
 
-        if (!parameter[members[i]].asBool())
+        if (!parameter[name].isInt())
         {
-          LOG(ERROR) << "Not a number in this user-defined metadata: " << info;
+          LOG(ERROR) << "Not a number in this user-defined metadata: " << name;
           throw OrthancException(ErrorCode_BadParameterType);
         }
 
-        int metadata = parameter[members[i]].asInt();
+        int metadata = parameter[name].asInt();        
+
+        LOG(INFO) << "Registering user-defined metadata: " << name << " (index " 
+                  << metadata << ")";
 
         try
         {
-          RegisterUserMetadata(metadata, members[i]);
+          RegisterUserMetadata(metadata, name);
         }
         catch (OrthancException&)
         {
-          LOG(ERROR) << "Cannot register this user-defined metadata: " << info;
+          LOG(ERROR) << "Cannot register this user-defined metadata: " << name;
           throw;
         }
       }
@@ -286,24 +288,39 @@
       Json::Value::Members members = parameter.getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
       {
-        std::string info = "\"" + members[i] + "\" = " + parameter[members[i]].toStyledString();
-        LOG(INFO) << "Registering user-defined attachment type: " << info;
+        const std::string& name = members[i];
+        std::string mime = "application/octet-stream";
+
+        const Json::Value& value = parameter[name];
+        int contentType;
 
-        if (!parameter[members[i]].asBool())
+        if (value.isArray() &&
+            value.size() == 2 &&
+            value[0].isInt() &&
+            value[1].isString())
         {
-          LOG(ERROR) << "Not a number in this user-defined attachment type: " << info;
+          contentType = value[0].asInt();
+          mime = value[1].asString();
+        }
+        else if (value.isInt())
+        {
+          contentType = value.asInt();
+        }
+        else
+        {
+          LOG(ERROR) << "Not a number in this user-defined attachment type: " << name;
           throw OrthancException(ErrorCode_BadParameterType);
         }
 
-        int contentType = parameter[members[i]].asInt();
+        LOG(INFO) << "Registering user-defined attachment type: " << name << " (index " 
+                  << contentType << ") with MIME type \"" << mime << "\"";
 
         try
         {
-          RegisterUserContentType(contentType, members[i]);
+          RegisterUserContentType(contentType, name, mime);
         }
         catch (OrthancException&)
         {
-          LOG(ERROR) << "Cannot register this user-defined attachment type: " << info;
           throw;
         }
       }
@@ -311,6 +328,43 @@
   }
 
 
+  static void LoadCustomDictionary(const Json::Value& configuration)
+  {
+    if (configuration.type() != Json::objectValue ||
+        !configuration.isMember("Dictionary") ||
+        configuration["Dictionary"].type() != Json::objectValue)
+    {
+      return;
+    }
+
+    Json::Value::Members tags(configuration["Dictionary"].getMemberNames());
+
+    for (Json::Value::ArrayIndex i = 0; i < tags.size(); i++)
+    {
+      const Json::Value& content = configuration["Dictionary"][tags[i]];
+      if (content.type() != Json::arrayValue ||
+          content.size() < 2 ||
+          content.size() > 4 ||
+          content[0].type() != Json::stringValue ||
+          content[1].type() != Json::stringValue ||
+          (content.size() >= 3 && content[2].type() != Json::intValue) ||
+          (content.size() >= 4 && content[3].type() != Json::intValue))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      DicomTag tag(FromDcmtkBridge::ParseTag(tags[i]));
+      DcmEVR vr = FromDcmtkBridge::ParseValueRepresentation(content[0].asString());
+      std::string name = content[1].asString();
+      unsigned int minMultiplicity = (content.size() >= 2) ? content[2].asUInt() : 1;
+      unsigned int maxMultiplicity = (content.size() >= 3) ? content[3].asUInt() : 1;
+
+      FromDcmtkBridge::RegisterDictionaryTag(tag, vr, name, minMultiplicity, maxMultiplicity);
+    }
+  }
+
+
+
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
@@ -338,7 +392,8 @@
     RegisterUserMetadata();
     RegisterUserContentType();
 
-    DicomServer::InitializeDictionary();
+    FromDcmtkBridge::InitializeDictionary();
+    LoadCustomDictionary(configuration_);
 
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
     LOG(WARNING) << "Registering JPEG Lossless codecs";
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -105,18 +105,50 @@
 
 
   bool OrthancMoveRequestHandler::LookupIdentifier(std::string& publicId,
-                                                   DicomTag tag,
+                                                   ResourceType level,
                                                    const DicomMap& input)
   {
+    DicomTag tag(0, 0);   // Dummy initialization
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tag = DICOM_TAG_PATIENT_ID;
+        break;
+
+      case ResourceType_Study:
+        tag = (input.HasTag(DICOM_TAG_ACCESSION_NUMBER) ? 
+               DICOM_TAG_ACCESSION_NUMBER : DICOM_TAG_STUDY_INSTANCE_UID);
+        break;
+        
+      case ResourceType_Series:
+        tag = DICOM_TAG_SERIES_INSTANCE_UID;
+        break;
+        
+      case ResourceType_Instance:
+        tag = DICOM_TAG_SOP_INSTANCE_UID;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
     if (!input.HasTag(tag))
     {
       return false;
     }
 
-    std::string value = input.GetValue(tag).AsString();
+    const DicomValue& value = input.GetValue(tag);
+    if (value.IsNull() ||
+        value.IsBinary())
+    {
+      return false;
+    }
+
+    const std::string& content = value.GetContent();
 
     std::list<std::string> ids;
-    context_.GetIndex().LookupIdentifier(ids, tag, value);
+    context_.GetIndex().LookupIdentifierExact(ids, level, tag, content);
 
     if (ids.size() != 1)
     {
@@ -145,7 +177,7 @@
         {
           LOG(INFO) << "  " << query.GetElement(i).GetTag()
                     << "  " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag())
-                    << " = " << query.GetElement(i).GetValue().AsString();
+                    << " = " << query.GetElement(i).GetValue().GetContent();
         }
       }
     }
@@ -156,14 +188,11 @@
      * Retrieve the query level.
      **/
 
-    ResourceType level;
     const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
 
-    if (levelTmp != NULL) 
-    {
-      level = StringToResourceType(levelTmp->AsString().c_str());
-    }
-    else
+    if (levelTmp == NULL ||
+        levelTmp->IsNull() ||
+        levelTmp->IsBinary())
     {
       // The query level is not present in the C-Move request, which
       // does not follow the DICOM standard. This is for instance the
@@ -173,10 +202,10 @@
 
       std::string publicId;
 
-      if (LookupIdentifier(publicId, DICOM_TAG_SOP_INSTANCE_UID, input) ||
-          LookupIdentifier(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input) ||
-          LookupIdentifier(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input) ||
-          LookupIdentifier(publicId, DICOM_TAG_PATIENT_ID, input))
+      if (LookupIdentifier(publicId, ResourceType_Instance, input) ||
+          LookupIdentifier(publicId, ResourceType_Series, input) ||
+          LookupIdentifier(publicId, ResourceType_Study, input) ||
+          LookupIdentifier(publicId, ResourceType_Patient, input))
       {
         return new OrthancMoveRequestIterator(context_, targetAet, publicId);
       }
@@ -187,42 +216,23 @@
       }
     }
 
+    assert(levelTmp != NULL);
+    ResourceType level = StringToResourceType(levelTmp->GetContent().c_str());      
 
 
     /**
      * Lookup for the resource to be sent.
      **/
 
-    bool ok;
     std::string publicId;
 
-    switch (level)
+    if (LookupIdentifier(publicId, level, input))
     {
-      case ResourceType_Patient:
-        ok = LookupIdentifier(publicId, DICOM_TAG_PATIENT_ID, input);
-        break;
-
-      case ResourceType_Study:
-        ok = LookupIdentifier(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input);
-        break;
-
-      case ResourceType_Series:
-        ok = LookupIdentifier(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input);
-        break;
-
-      case ResourceType_Instance:
-        ok = LookupIdentifier(publicId, DICOM_TAG_SOP_INSTANCE_UID, input);
-        break;
-
-      default:
-        ok = false;
+      return new OrthancMoveRequestIterator(context_, targetAet, publicId);
     }
-
-    if (!ok)
+    else
     {
       throw OrthancException(ErrorCode_BadRequest);
     }
-
-    return new OrthancMoveRequestIterator(context_, targetAet, publicId);
   }
 }
--- a/OrthancServer/OrthancMoveRequestHandler.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Wed Nov 18 10:16:21 2015 +0100
@@ -42,7 +42,7 @@
     ServerContext& context_;
 
     bool LookupIdentifier(std::string& publicId,
-                          DicomTag tag,
+                          ResourceType level,
                           const DicomMap& input);
 
   public:
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -40,6 +40,7 @@
 #include "../OrthancInitialization.h"
 
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/predicate.hpp>
 
 namespace Orthanc
 {
@@ -97,12 +98,13 @@
     for (size_t i = 0; i < members.size(); i++)
     {
       const std::string& name = members[i];
-      std::string value = replacements[name].asString();
+      const Json::Value& value = replacements[name];
 
       DicomTag tag = FromDcmtkBridge::ParseTag(name);
       target.Replace(tag, value);
 
-      VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl;
+      VLOG(1) << "Replace: " << name << " " << tag 
+              << " == " << value.toStyledString() << std::endl;
     }
   }
 
@@ -167,7 +169,7 @@
     // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm
 
     target.SetupAnonymization();
-    std::string patientName = target.GetReplacement(DICOM_TAG_PATIENT_NAME);
+    std::string patientName = target.GetReplacementAsString(DICOM_TAG_PATIENT_NAME);
 
     Json::Value request;
     if (call.ParseJsonRequest(request) && request.isObject())
@@ -485,7 +487,8 @@
 
 
   static void InjectTags(ParsedDicomFile& dicom,
-                         const Json::Value& tags)
+                         const Json::Value& tags,
+                         bool decodeBinaryTags)
   {
     if (tags.type() != Json::objectValue)
     {
@@ -497,17 +500,21 @@
     for (size_t i = 0; i < members.size(); i++)
     {
       const std::string& name = members[i];
-      if (tags[name].type() != Json::stringValue)
-      {
-        throw OrthancException(ErrorCode_CreateDicomNotString);
-      }
+      DicomTag tag = FromDcmtkBridge::ParseTag(name);
 
-      std::string value = tags[name].asString();
-
-      DicomTag tag = FromDcmtkBridge::ParseTag(name);
       if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
       {
         if (tag != DICOM_TAG_PATIENT_ID &&
+            tag != DICOM_TAG_ACQUISITION_DATE &&
+            tag != DICOM_TAG_ACQUISITION_TIME &&
+            tag != DICOM_TAG_CONTENT_DATE &&
+            tag != DICOM_TAG_CONTENT_TIME &&
+            tag != DICOM_TAG_INSTANCE_CREATION_DATE &&
+            tag != DICOM_TAG_INSTANCE_CREATION_TIME &&
+            tag != DICOM_TAG_SERIES_DATE &&
+            tag != DICOM_TAG_SERIES_TIME &&
+            tag != DICOM_TAG_STUDY_DATE &&
+            tag != DICOM_TAG_STUDY_TIME &&
             dicom.HasTag(tag))
         {
           throw OrthancException(ErrorCode_CreateDicomOverrideTag);
@@ -519,7 +526,7 @@
         }
         else
         {
-          dicom.Replace(tag, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding()));
+          dicom.Replace(tag, tags[name], decodeBinaryTags);
         }
       }
     }
@@ -528,7 +535,8 @@
 
   static void CreateSeries(RestApiPostCall& call,
                            ParsedDicomFile& base /* in */,
-                           const Json::Value& content)
+                           const Json::Value& content,
+                           bool decodeBinaryTags)
   {
     assert(content.isArray());
     assert(content.size() > 0);
@@ -561,7 +569,7 @@
 
           if (content[i].isMember("Tags"))
           {
-            InjectTags(*dicom, content[i]["Tags"]);
+            InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags);
           }
         }
 
@@ -734,6 +742,19 @@
       }
     }
 
+
+    bool decodeBinaryTags = true;
+    if (request.isMember("InterpretBinaryTags"))
+    {
+      const Json::Value& v = request["InterpretBinaryTags"];
+      if (v.type() != Json::booleanValue)
+      {
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      decodeBinaryTags = v.asBool();
+    }
+
     
     // Inject time-related information
     std::string date, time;
@@ -761,7 +782,7 @@
     }
 
 
-    InjectTags(dicom, request["Tags"]);
+    InjectTags(dicom, request["Tags"], decodeBinaryTags);
 
 
     // Inject the content (either an image, or a PDF file)
@@ -779,7 +800,7 @@
         if (content.size() > 0)
         {
           // Let's create a series instead of a single instance
-          CreateSeries(call, dicom, content);
+          CreateSeries(call, dicom, content, decodeBinaryTags);
           return;
         }
       }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -59,11 +59,20 @@
 
   void OrthancRestApi::ResetOrthanc(RestApiPostCall& call)
   {
+    OrthancRestApi::GetApi(call).leaveBarrier_ = true;
     OrthancRestApi::GetApi(call).resetRequestReceived_ = true;
     call.GetOutput().AnswerBuffer("{}", "application/json");
   }
 
 
+  void OrthancRestApi::ShutdownOrthanc(RestApiPostCall& call)
+  {
+    OrthancRestApi::GetApi(call).leaveBarrier_ = true;
+    call.GetOutput().AnswerBuffer("{}", "application/json");
+    LOG(WARNING) << "Shutdown request received";
+  }
+
+
 
 
 
@@ -99,6 +108,7 @@
 
   OrthancRestApi::OrthancRestApi(ServerContext& context) : 
     context_(context),
+    leaveBarrier_(false),
     resetRequestReceived_(false)
   {
     RegisterSystem();
@@ -114,6 +124,7 @@
     // Auto-generated directories
     Register("/tools", RestApi::AutoListChildren);
     Register("/tools/reset", ResetOrthanc);
+    Register("/tools/shutdown", ShutdownOrthanc);
     Register("/instances/{id}/frames/{frame}", RestApi::AutoListChildren);
   }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed Nov 18 10:16:21 2015 +0100
@@ -49,6 +49,7 @@
 
   private:
     ServerContext& context_;
+    bool leaveBarrier_;
     bool resetRequestReceived_;
 
     void RegisterSystem();
@@ -65,10 +66,17 @@
 
     static void ResetOrthanc(RestApiPostCall& call);
 
+    static void ShutdownOrthanc(RestApiPostCall& call);
+
   public:
     OrthancRestApi(ServerContext& context);
 
-    const bool& ResetRequestReceivedFlag() const
+    const bool& LeaveBarrierFlag() const
+    {
+      return leaveBarrier_;
+    }
+
+    bool IsResetRequestReceived() const
     {
       return resetRequestReceived_;
     }
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -34,6 +34,7 @@
 #include "OrthancRestApi.h"
 
 #include "../DicomDirWriter.h"
+#include "../../Core/FileStorage/StorageAccessor.h"
 #include "../../Core/Compression/HierarchicalZipWriter.h"
 #include "../../Core/HttpServer/FilesystemHttpSender.h"
 #include "../../Core/Logging.h"
@@ -53,200 +54,11 @@
 {
   // Download of ZIP files ----------------------------------------------------
  
-  static std::string GetDirectoryNameInArchive(const Json::Value& resource,
-                                               ResourceType resourceType)
+  static bool IsZip64Required(uint64_t uncompressedSize,
+                              unsigned int countInstances)
   {
-    std::string s;
-    const Json::Value& tags = resource["MainDicomTags"];
-
-    switch (resourceType)
-    {
-      case ResourceType_Patient:
-      {
-        std::string p = tags["PatientID"].asString();
-        std::string n = tags["PatientName"].asString();
-        s = p + " " + n;
-        break;
-      }
-
-      case ResourceType_Study:
-      {
-        std::string p;
-        if (tags.isMember("AccessionNumber"))
-        {
-          p = tags["AccessionNumber"].asString() + " ";
-        }
-
-        s = p + tags["StudyDescription"].asString();
-        break;
-      }
-        
-      case ResourceType_Series:
-      {
-        std::string d = tags["SeriesDescription"].asString();
-        std::string m = tags["Modality"].asString();
-        s = m + " " + d;
-        break;
-      }
-        
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    // Get rid of special characters
-    return Toolbox::ConvertToAscii(s);
-  }
-
-  static bool CreateRootDirectoryInArchive(HierarchicalZipWriter& writer,
-                                           ServerContext& context,
-                                           const Json::Value& resource,
-                                           ResourceType resourceType)
-  {
-    if (resourceType == ResourceType_Patient)
-    {
-      return true;
-    }
-
-    ResourceType parentType = GetParentResourceType(resourceType);
-    Json::Value parent;
-
-    switch (resourceType)
-    {
-      case ResourceType_Study:
-      {
-        if (!context.GetIndex().LookupResource(parent, resource["ParentPatient"].asString(), parentType))
-        {
-          return false;
-        }
-
-        break;
-      }
-        
-      case ResourceType_Series:
-        if (!context.GetIndex().LookupResource(parent, resource["ParentStudy"].asString(), parentType) ||
-            !CreateRootDirectoryInArchive(writer, context, parent, parentType))
-        {
-          return false;
-        }
-        break;
-        
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-
-    writer.OpenDirectory(GetDirectoryNameInArchive(parent, parentType).c_str());
-    return true;
-  }
-
-  static bool ArchiveInstance(HierarchicalZipWriter& writer,
-                              ServerContext& context,
-                              const std::string& instancePublicId,
-                              const char* filename)
-  {
-    writer.OpenFile(filename);
-
-    std::string dicom;
-    context.ReadFile(dicom, instancePublicId, FileContentType_Dicom);
-    writer.Write(dicom);
+    static const uint64_t  SAFETY_MARGIN = 64 * MEGA_BYTES;
 
-    return true;
-  }
-
-  static bool ArchiveInternal(HierarchicalZipWriter& writer,
-                              ServerContext& context,
-                              const std::string& publicId,
-                              ResourceType resourceType,
-                              bool isFirstLevel)
-  { 
-    Json::Value resource;
-    if (!context.GetIndex().LookupResource(resource, publicId, resourceType))
-    {
-      return false;
-    }    
-
-    if (isFirstLevel && 
-        !CreateRootDirectoryInArchive(writer, context, resource, resourceType))
-    {
-      return false;
-    }
-
-    writer.OpenDirectory(GetDirectoryNameInArchive(resource, resourceType).c_str());
-
-    switch (resourceType)
-    {
-      case ResourceType_Patient:
-        for (Json::Value::ArrayIndex i = 0; i < resource["Studies"].size(); i++)
-        {
-          std::string studyId = resource["Studies"][i].asString();
-          if (!ArchiveInternal(writer, context, studyId, ResourceType_Study, false))
-          {
-            return false;
-          }
-        }
-        break;
-
-      case ResourceType_Study:
-        for (Json::Value::ArrayIndex i = 0; i < resource["Series"].size(); i++)
-        {
-          std::string seriesId = resource["Series"][i].asString();
-          if (!ArchiveInternal(writer, context, seriesId, ResourceType_Series, false))
-          {
-            return false;
-          }
-        }
-        break;
-
-      case ResourceType_Series:
-      {
-        // Create a filename prefix, depending on the modality
-        char format[24] = "%08d.dcm";
-
-        if (resource["MainDicomTags"].isMember("Modality"))
-        {
-          std::string modality = resource["MainDicomTags"]["Modality"].asString();
-
-          if (modality.size() == 1)
-          {
-            snprintf(format, sizeof(format) - 1, "%c%%07d.dcm", toupper(modality[0]));
-          }
-          else if (modality.size() >= 2)
-          {
-            snprintf(format, sizeof(format) - 1, "%c%c%%06d.dcm", toupper(modality[0]), toupper(modality[1]));
-          }
-        }
-
-        char filename[24];
-
-        for (Json::Value::ArrayIndex i = 0; i < resource["Instances"].size(); i++)
-        {
-          snprintf(filename, sizeof(filename) - 1, format, i);
-
-          std::string publicId = resource["Instances"][i].asString();
-
-          // This was the implementation up to Orthanc 0.7.0:
-          // std::string filename = instance["MainDicomTags"]["SOPInstanceUID"].asString() + ".dcm";
-
-          if (!ArchiveInstance(writer, context, publicId, filename))
-          {
-            return false;
-          }
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    writer.CloseDirectory();
-    return true;
-  }                                 
-
-
-  static bool IsZip64Required(ServerIndex& index,
-                              const std::string& id)
-  {
     /**
      * Determine whether ZIP64 is required. Original ZIP format can
      * store up to 2GB of data (some implementation supporting up to
@@ -254,14 +66,7 @@
      * https://en.wikipedia.org/wiki/Zip_(file_format)#ZIP64
      **/
 
-    uint64_t uncompressedSize;
-    uint64_t compressedSize;
-    unsigned int countStudies;
-    unsigned int countSeries;
-    unsigned int countInstances;
-    index.GetStatistics(compressedSize, uncompressedSize, 
-                        countStudies, countSeries, countInstances, id);
-    const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES ||
+    const bool isZip64 = (uncompressedSize >= 2 * GIGA_BYTES - SAFETY_MARGIN ||
                           countInstances >= 65535);
 
     LOG(INFO) << "Creating a ZIP file with " << countInstances << " files of size "
@@ -270,112 +75,701 @@
 
     return isZip64;
   }
-                              
+
+
+  namespace
+  {
+    class ResourceIdentifiers
+    {
+    private:
+      ResourceType   level_;
+      std::string    patient_;
+      std::string    study_;
+      std::string    series_;
+      std::string    instance_;
+
+      static void GoToParent(ServerIndex& index,
+                             std::string& current)
+      {
+        std::string tmp;
+
+        if (index.LookupParent(tmp, current))
+        {
+          current = tmp;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+      }
 
-  template <enum ResourceType resourceType>
-  static void GetArchive(RestApiGetCall& call)
-  {
-    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    public:
+      ResourceIdentifiers(ServerIndex& index,
+                          const std::string& publicId)
+      {
+        if (!index.LookupResourceType(level_, publicId))
+        {
+          throw OrthancException(ErrorCode_UnknownResource);
+        }
+
+        std::string current = publicId;;
+        switch (level_)  // Do not add "break" below!
+        {
+          case ResourceType_Instance:
+            instance_ = current;
+            GoToParent(index, current);
+            
+          case ResourceType_Series:
+            series_ = current;
+            GoToParent(index, current);
+
+          case ResourceType_Study:
+            study_ = current;
+            GoToParent(index, current);
+
+          case ResourceType_Patient:
+            patient_ = current;
+            break;
 
-    std::string id = call.GetUriComponent("id", "");
-    bool isZip64 = IsZip64Required(context.GetIndex(), id);
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+
+      ResourceType GetLevel() const
+      {
+        return level_;
+      }
+
+      const std::string& GetIdentifier(ResourceType level) const
+      {
+        // Some sanity check to ensure enumerations are not altered
+        assert(ResourceType_Patient < ResourceType_Study);
+        assert(ResourceType_Study < ResourceType_Series);
+        assert(ResourceType_Series < ResourceType_Instance);
+
+        if (level > level_)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        switch (level)
+        {
+          case ResourceType_Patient:
+            return patient_;
+
+          case ResourceType_Study:
+            return study_;
 
-    // Create a RAII for the temporary file to manage the ZIP file
-    Toolbox::TemporaryFile tmp;
+          case ResourceType_Series:
+            return series_;
+
+          case ResourceType_Instance:
+            return instance_;
 
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    };
+
+
+    class IArchiveVisitor : public boost::noncopyable
     {
-      // Create a ZIP writer
-      HierarchicalZipWriter writer(tmp.GetPath().c_str());
-      writer.SetZip64(isZip64);
+    public:
+      virtual ~IArchiveVisitor()
+      {
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId) = 0;
+
+      virtual void Close() = 0;
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom) = 0;
+    };
+
 
-      // Store the requested resource into the ZIP
-      if (!ArchiveInternal(writer, context, id, resourceType, true))
+    class ArchiveIndex
+    {
+    private:
+      struct Instance
+      {
+        std::string  id_;
+        FileInfo     dicom_;
+
+        Instance(const std::string& id,
+                 const FileInfo& dicom) : 
+          id_(id), dicom_(dicom)
+        {
+        }
+      };
+
+      // A "NULL" value for ArchiveIndex indicates a non-expanded node
+      typedef std::map<std::string, ArchiveIndex*>   Resources;
+
+      ResourceType         level_;
+      Resources            resources_;   // Only at patient/study/series level
+      std::list<Instance>  instances_;   // Only at instance level
+
+
+      void AddResourceToExpand(ServerIndex& index,
+                               const std::string& id)
       {
-        return;
+        if (level_ == ResourceType_Instance)
+        {
+          FileInfo tmp;
+          if (index.LookupAttachment(tmp, id, FileContentType_Dicom))
+          {
+            instances_.push_back(Instance(id, tmp));
+          }
+        }
+        else
+        {
+          resources_[id] = NULL;
+        }
       }
-    }
+
+
+    public:
+      ArchiveIndex(ResourceType level) :
+        level_(level)
+      {
+      }
+
+      ~ArchiveIndex()
+      {
+        for (Resources::iterator it = resources_.begin();
+             it != resources_.end(); ++it)
+        {
+          delete it->second;
+        }
+      }
+
+
+      void Add(ServerIndex& index,
+               const ResourceIdentifiers& resource)
+      {
+        const std::string& id = resource.GetIdentifier(level_);
+        Resources::iterator previous = resources_.find(id);
 
-    // Prepare the sending of the ZIP file
-    FilesystemHttpSender sender(tmp.GetPath());
-    sender.SetContentType("application/zip");
-    sender.SetContentFilename(id + ".zip");
+        if (level_ == ResourceType_Instance)
+        {
+          AddResourceToExpand(index, id);
+        }
+        else if (resource.GetLevel() == level_)
+        {
+          // Mark this resource for further expansion
+          if (previous != resources_.end())
+          {
+            delete previous->second;
+          }
+
+          resources_[id] = NULL;
+        }
+        else if (previous == resources_.end())
+        {
+          // This is the first time we meet this resource
+          std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
+          child->Add(index, resource);
+          resources_[id] = child.release();
+        }
+        else if (previous->second != NULL)
+        {
+          previous->second->Add(index, resource);
+        }
+        else
+        {
+          // Nothing to do: This item is marked for further expansion
+        }
+      }
+
 
-    // Send the ZIP
-    call.GetOutput().AnswerStream(sender);
+      void Expand(ServerIndex& index)
+      {
+        if (level_ == ResourceType_Instance)
+        {
+          // Expanding an instance node makes no sense
+          return;
+        }
 
-    // The temporary file is automatically removed thanks to the RAII
-  }
+        for (Resources::iterator it = resources_.begin();
+             it != resources_.end(); ++it)
+        {
+          if (it->second == NULL)
+          {
+            // This is resource is marked for expansion
+            std::list<std::string> children;
+            index.GetChildren(children, it->first);
+
+            std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
+
+            for (std::list<std::string>::const_iterator 
+                   it2 = children.begin(); it2 != children.end(); ++it2)
+            {
+              child->AddResourceToExpand(index, *it2);
+            }
+
+            it->second = child.release();
+          }
+
+          assert(it->second != NULL);
+          it->second->Expand(index);
+        }        
+      }
 
 
-  static void GetMediaArchive(RestApiGetCall& call)
-  {
-    ServerContext& context = OrthancRestApi::GetContext(call);
+      void Apply(IArchiveVisitor& visitor) const
+      {
+        if (level_ == ResourceType_Instance)
+        {
+          for (std::list<Instance>::const_iterator 
+                 it = instances_.begin(); it != instances_.end(); ++it)
+          {
+            visitor.AddInstance(it->id_, it->dicom_);
+          }          
+        }
+        else
+        {
+          for (Resources::const_iterator it = resources_.begin();
+               it != resources_.end(); ++it)
+          {
+            assert(it->second != NULL);  // There must have been a call to "Expand()"
+            visitor.Open(level_, it->first);
+            it->second->Apply(visitor);
+            visitor.Close();
+          }
+        }
+      }
+    };
+
+
+    class StatisticsVisitor : public IArchiveVisitor
+    {
+    private:
+      uint64_t       size_;
+      unsigned int   instances_;
+      
+    public:
+      StatisticsVisitor() : size_(0), instances_(0)
+      {
+      }
+
+      uint64_t GetUncompressedSize() const
+      {
+        return size_;
+      }
+
+      unsigned int GetInstancesCount() const
+      {
+        return instances_;
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+      }
 
-    std::string id = call.GetUriComponent("id", "");
-    bool isZip64 = IsZip64Required(context.GetIndex(), id);
+      virtual void Close()
+      {
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
+      {
+        instances_ ++;
+        size_ += dicom.GetUncompressedSize();
+      }
+    };
+
+
+    class PrintVisitor : public IArchiveVisitor
+    {
+    private:
+      std::ostream& out_;
+      std::string   indent_;
+
+    public:
+      PrintVisitor(std::ostream& out) : out_(out)
+      {
+      }
 
-    // Create a RAII for the temporary file to manage the ZIP file
-    Toolbox::TemporaryFile tmp;
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+        switch (level)
+        {
+          case ResourceType_Patient:  indent_ = "";       break;
+          case ResourceType_Study:    indent_ = "  ";     break;
+          case ResourceType_Series:   indent_ = "    ";   break;
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
 
+        out_ << indent_ << publicId << std::endl;
+      }
+
+      virtual void Close()
+      {
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
+      {
+        out_ << "      " << instanceId << std::endl;
+      }
+    };
+
+
+    class ArchiveWriterVisitor : public IArchiveVisitor
     {
-      // Create a ZIP writer
-      HierarchicalZipWriter writer(tmp.GetPath().c_str());
-      writer.SetZip64(isZip64);
-      writer.OpenDirectory("IMAGES");
+    private:
+      HierarchicalZipWriter&  writer_;
+      ServerContext&            context_;
+      char                    instanceFormat_[24];
+      unsigned int            countInstances_;
+
+      static std::string GetTag(const DicomMap& tags,
+                                const DicomTag& tag)
+      {
+        const DicomValue* v = tags.TestAndGetValue(tag);
+        if (v != NULL &&
+            !v->IsBinary() &&
+            !v->IsNull())
+        {
+          return v->GetContent();
+        }
+        else
+        {
+          return "";
+        }
+      }
+
+    public:
+      ArchiveWriterVisitor(HierarchicalZipWriter& writer,
+                           ServerContext& context) :
+        writer_(writer),
+        context_(context)
+      {
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+        std::string path;
+
+        DicomMap tags;
+        if (context_.GetIndex().GetMainDicomTags(tags, publicId, level, level))
+        {
+          switch (level)
+          {
+            case ResourceType_Patient:
+              path = GetTag(tags, DICOM_TAG_PATIENT_ID) + " " + GetTag(tags, DICOM_TAG_PATIENT_NAME);
+              break;
 
-      // Create the DICOMDIR writer
-      DicomDirWriter dicomDir;
+            case ResourceType_Study:
+              path = GetTag(tags, DICOM_TAG_ACCESSION_NUMBER) + " " + GetTag(tags, DICOM_TAG_STUDY_DESCRIPTION);
+              break;
+
+            case ResourceType_Series:
+            {
+              std::string modality = GetTag(tags, DICOM_TAG_MODALITY);
+              path = modality + " " + GetTag(tags, DICOM_TAG_SERIES_DESCRIPTION);
+
+              if (modality.size() == 0)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%%08d.dcm");
+              }
+              else if (modality.size() == 1)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%%07d.dcm", 
+                         toupper(modality[0]));
+              }
+              else if (modality.size() >= 2)
+              {
+                snprintf(instanceFormat_, sizeof(instanceFormat_) - 1, "%c%c%%06d.dcm", 
+                         toupper(modality[0]), toupper(modality[1]));
+              }
+
+              countInstances_ = 0;
+
+              break;
+            }
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+        }
+
+        path = Toolbox::StripSpaces(Toolbox::ConvertToAscii(path));
+
+        if (path.empty())
+        {
+          path = std::string("Unknown ") + EnumerationToString(level);
+        }
+
+        writer_.OpenDirectory(path.c_str());
+      }
 
-      // Retrieve the list of the instances
-      std::list<std::string> instances;
-      context.GetIndex().GetChildInstances(instances, id);
+      virtual void Close()
+      {
+        writer_.CloseDirectory();
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
+      {
+        std::string content;
+        context_.ReadFile(content, dicom);
+
+        char filename[24];
+        snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_);
+        countInstances_ ++;
+
+        writer_.OpenFile(filename);
+        writer_.Write(content);
+      }
+
+      static void Apply(RestApiOutput& output,
+                        ServerContext& context,
+                        ArchiveIndex& archive,
+                        const std::string& filename)
+      {
+        archive.Expand(context.GetIndex());
+
+        StatisticsVisitor stats;
+        archive.Apply(stats);
+
+        const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount());
+
+        // Create a RAII for the temporary file to manage the ZIP file
+        Toolbox::TemporaryFile tmp;
+
+        {
+          // Create a ZIP writer
+          HierarchicalZipWriter writer(tmp.GetPath().c_str());
+          writer.SetZip64(isZip64);
+
+          ArchiveWriterVisitor v(writer, context);
+          archive.Apply(v);
+        }
 
-      size_t pos = 0;
-      for (std::list<std::string>::const_iterator
-             it = instances.begin(); it != instances.end(); ++it, ++pos)
+        // Prepare the sending of the ZIP file
+        FilesystemHttpSender sender(tmp.GetPath());
+        sender.SetContentType("application/zip");
+        sender.SetContentFilename(filename);
+
+        // Send the ZIP
+        output.AnswerStream(sender);
+
+        // The temporary file is automatically removed thanks to the RAII
+      }
+    };
+
+    
+    class MediaWriterVisitor : public IArchiveVisitor
+    {
+    private:
+      HierarchicalZipWriter&  writer_;
+      DicomDirWriter          dicomDir_;
+      ServerContext&          context_;
+      unsigned int            countInstances_;
+
+    public:
+      MediaWriterVisitor(HierarchicalZipWriter& writer,
+                         ServerContext& context) :
+        writer_(writer),
+        context_(context),
+        countInstances_(0)
+      {
+      }
+
+      void EncodeDicomDir(std::string& result)
+      {
+        dicomDir_.Encode(result);
+      }
+
+      virtual void Open(ResourceType level,
+                        const std::string& publicId)
+      {
+      }
+
+      virtual void Close()
+      {
+      }
+
+      virtual void AddInstance(const std::string& instanceId,
+                               const FileInfo& dicom)
       {
         // "DICOM restricts the filenames on DICOM media to 8
         // characters (some systems wrongly use 8.3, but this does not
         // conform to the standard)."
-        std::string filename = "IM" + boost::lexical_cast<std::string>(pos);
-        writer.OpenFile(filename.c_str());
+        std::string filename = "IM" + boost::lexical_cast<std::string>(countInstances_);
+        writer_.OpenFile(filename.c_str());
 
-        std::string dicom;
-        context.ReadFile(dicom, *it, FileContentType_Dicom);
-        writer.Write(dicom);
+        std::string content;
+        context_.ReadFile(content, dicom);
+        writer_.Write(content);
 
-        ParsedDicomFile parsed(dicom);
-        dicomDir.Add("IMAGES", filename, parsed);
+        ParsedDicomFile parsed(content);
+        dicomDir_.Add("IMAGES", filename, parsed);
+
+        countInstances_ ++;
       }
 
-      // Add the DICOMDIR
-      writer.CloseDirectory();
-      writer.OpenFile("DICOMDIR");
-      std::string s;
-      dicomDir.Encode(s);
-      writer.Write(s);
-    }
+      static void Apply(RestApiOutput& output,
+                        ServerContext& context,
+                        ArchiveIndex& archive,
+                        const std::string& filename)
+      {
+        archive.Expand(context.GetIndex());
+
+        StatisticsVisitor stats;
+        archive.Apply(stats);
+
+        const bool isZip64 = IsZip64Required(stats.GetUncompressedSize(), stats.GetInstancesCount());
+
+        // Create a RAII for the temporary file to manage the ZIP file
+        Toolbox::TemporaryFile tmp;
+
+        {
+          // Create a ZIP writer
+          HierarchicalZipWriter writer(tmp.GetPath().c_str());
+          writer.SetZip64(isZip64);
+          writer.OpenDirectory("IMAGES");
+
+          // Create the DICOMDIR writer
+          DicomDirWriter dicomDir;
+
+          MediaWriterVisitor v(writer, context);
+          archive.Apply(v);
+
+          // Add the DICOMDIR
+          writer.CloseDirectory();
+          writer.OpenFile("DICOMDIR");
+          std::string s;
+          v.EncodeDicomDir(s);
+          writer.Write(s);
+        }
+
+        // Prepare the sending of the ZIP file
+        FilesystemHttpSender sender(tmp.GetPath());
+        sender.SetContentType("application/zip");
+        sender.SetContentFilename(filename);
+
+        // Send the ZIP
+        output.AnswerStream(sender);
+
+        // The temporary file is automatically removed thanks to the RAII
+      }
+    };
+  }
+
+
+  static bool AddResourcesOfInterest(ArchiveIndex& archive,
+                                     RestApiPostCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    Json::Value resources;
+    if (call.ParseJsonRequest(resources) &&
+        resources.type() == Json::arrayValue)
+    {
+      for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
+      {
+        if (resources[i].type() != Json::stringValue)
+        {
+          return false;   // Bad request
+        }
+
+        ResourceIdentifiers resource(index, resources[i].asString());
+        archive.Add(index, resource);
+      }
 
-    // Prepare the sending of the ZIP file
-    FilesystemHttpSender sender(tmp.GetPath());
-    sender.SetContentType("application/zip");
-    sender.SetContentFilename(id + ".zip");
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  static void CreateBatchArchive(RestApiPostCall& call)
+  {
+    ArchiveIndex archive(ResourceType_Patient);  // root
+
+    if (AddResourcesOfInterest(archive, call))
+    {
+      ArchiveWriterVisitor::Apply(call.GetOutput(),
+                                  OrthancRestApi::GetContext(call),
+                                  archive,
+                                  "Archive.zip");
+    }
+  }  
+
+
+  static void CreateBatchMedia(RestApiPostCall& call)
+  {
+    ArchiveIndex archive(ResourceType_Patient);  // root
 
-    // Send the ZIP
-    call.GetOutput().AnswerStream(sender);
+    if (AddResourcesOfInterest(archive, call))
+    {
+      MediaWriterVisitor::Apply(call.GetOutput(),
+                                OrthancRestApi::GetContext(call),
+                                archive,
+                                "Archive.zip");
+    }
+  }  
+
+
+  static void CreateArchive(RestApiGetCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    ResourceIdentifiers resource(index, id);
+
+    ArchiveIndex archive(ResourceType_Patient);  // root
+    archive.Add(OrthancRestApi::GetIndex(call), resource);
 
-    // The temporary file is automatically removed thanks to the RAII
+    ArchiveWriterVisitor::Apply(call.GetOutput(),
+                                OrthancRestApi::GetContext(call),
+                                archive,
+                                id + ".zip");
+  }
+
+
+  static void CreateMedia(RestApiGetCall& call)
+  {
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+
+    std::string id = call.GetUriComponent("id", "");
+    ResourceIdentifiers resource(index, id);
+
+    ArchiveIndex archive(ResourceType_Patient);  // root
+    archive.Add(OrthancRestApi::GetIndex(call), resource);
+
+    MediaWriterVisitor::Apply(call.GetOutput(),
+                              OrthancRestApi::GetContext(call),
+                              archive,
+                              id + ".zip");
   }
 
 
   void OrthancRestApi::RegisterArchive()
   {
-    Register("/patients/{id}/archive", GetArchive<ResourceType_Patient>);
-    Register("/studies/{id}/archive", GetArchive<ResourceType_Study>);
-    Register("/series/{id}/archive", GetArchive<ResourceType_Series>);
+    Register("/patients/{id}/archive", CreateArchive);
+    Register("/studies/{id}/archive", CreateArchive);
+    Register("/series/{id}/archive", CreateArchive);
 
-    Register("/patients/{id}/media", GetMediaArchive);
-    Register("/studies/{id}/media", GetMediaArchive);
-    Register("/series/{id}/media", GetMediaArchive);
+    Register("/patients/{id}/media", CreateMedia);
+    Register("/studies/{id}/media", CreateMedia);
+    Register("/series/{id}/media", CreateMedia);
+
+    Register("/tools/create-archive", CreateBatchArchive);
+    Register("/tools/create-media", CreateBatchMedia);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -198,8 +198,8 @@
       return;
     }
 
-    if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
-        fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2)
+    if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 &&
+        fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2)
     {
       return;
     }        
@@ -228,9 +228,9 @@
       return;
     }
 
-    if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
-         fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) ||
-        fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2)
+    if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 &&
+         fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2) ||
+        fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent().size() <= 2)
     {
       return;
     }        
@@ -259,10 +259,10 @@
       return;
     }
 
-    if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 &&
-         fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) ||
-        fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 ||
-        fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2)
+    if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).GetContent().size() <= 2 &&
+         fields.GetValue(DICOM_TAG_PATIENT_ID).GetContent().size() <= 2) ||
+        fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent().size() <= 2 ||
+        fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent().size() <= 2)
     {
       return;
     }        
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -34,11 +34,11 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Logging.h"
+#include "../../Core/HttpServer/HttpContentNegociation.h"
 #include "../ServerToolbox.h"
 #include "../FromDcmtkBridge.h"
-#include "../ResourceFinder.h"
-#include "../DicomFindQuery.h"
 #include "../ServerContext.h"
+#include "../SliceOrdering.h"
 
 
 namespace Orthanc
@@ -208,7 +208,7 @@
       context.ReadJson(full, publicId);
 
       Json::Value simplified;
-      SimplifyTags(simplified, full);
+      Toolbox::SimplifyTags(simplified, full);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -260,6 +260,120 @@
   }
 
 
+  namespace
+  {
+    class ImageToEncode
+    {
+    private:
+      std::string         format_;
+      std::string         encoded_;
+      ParsedDicomFile&    dicom_;
+      unsigned int        frame_;
+      ImageExtractionMode mode_;
+
+    public:
+      ImageToEncode(ParsedDicomFile& dicom,
+                    unsigned int frame,
+                    ImageExtractionMode mode) : 
+        dicom_(dicom),
+        frame_(frame),
+        mode_(mode)
+      {
+      }
+
+      ParsedDicomFile& GetDicom() const
+      {
+        return dicom_;
+      }
+
+      unsigned int GetFrame() const
+      {
+        return frame_;
+      }
+
+      ImageExtractionMode GetMode() const
+      {
+        return mode_;
+      }
+
+      void SetFormat(const std::string& format)
+      {
+        format_ = format;
+      }
+
+      std::string& GetTarget()
+      {
+        return encoded_;
+      }
+
+      void Answer(RestApiOutput& output)
+      {
+        output.AnswerBuffer(encoded_, format_);
+      }
+    };
+
+    class EncodePng : public HttpContentNegociation::IHandler
+    {
+    private:
+      ImageToEncode&  image_;
+
+    public:
+      EncodePng(ImageToEncode& image) : image_(image)
+      {
+      }
+
+      virtual void Handle(const std::string& type,
+                          const std::string& subtype)
+      {
+        assert(type == "image");
+        assert(subtype == "png");
+        image_.GetDicom().ExtractPngImage(image_.GetTarget(), image_.GetFrame(), image_.GetMode());
+        image_.SetFormat("image/png");
+      }
+    };
+
+    class EncodeJpeg : public HttpContentNegociation::IHandler
+    {
+    private:
+      ImageToEncode&  image_;
+      unsigned int    quality_;
+
+    public:
+      EncodeJpeg(ImageToEncode& image,
+                 const RestApiGetCall& call) :
+        image_(image)
+      {
+        std::string v = call.GetArgument("quality", "90" /* default JPEG quality */);
+        bool ok = false;
+
+        try
+        {
+          quality_ = boost::lexical_cast<unsigned int>(v);
+          ok = (quality_ >= 0 && quality_ <= 100);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+
+        if (!ok)
+        {
+          LOG(ERROR) << "Bad quality for a JPEG encoding (must be a number between 0 and 100): " << v;
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+
+      virtual void Handle(const std::string& type,
+                          const std::string& subtype)
+      {
+        assert(type == "image");
+        assert(subtype == "jpeg");
+        image_.GetDicom().ExtractJpegImage(image_.GetTarget(), image_.GetFrame(), image_.GetMode(), quality_);
+        image_.SetFormat("image/jpeg");
+      }
+    };
+  }
+
+
   template <enum ImageExtractionMode mode>
   static void GetImage(RestApiGetCall& call)
   {
@@ -278,15 +392,23 @@
     }
 
     std::string publicId = call.GetUriComponent("id", "");
-    std::string dicomContent, png;
+    std::string dicomContent;
     context.ReadFile(dicomContent, publicId, FileContentType_Dicom);
 
     ParsedDicomFile dicom(dicomContent);
 
     try
     {
-      dicom.ExtractPngImage(png, frame, mode);
-      call.GetOutput().AnswerBuffer(png, "image/png");
+      ImageToEncode image(dicom, frame, mode);
+
+      HttpContentNegociation negociation;
+      EncodePng png(image);          negociation.Register("image/png", png);
+      EncodeJpeg jpeg(image, call);  negociation.Register("image/jpeg", jpeg);
+
+      if (negociation.Apply(call.GetHttpHeaders()))
+      {
+        image.Answer(call.GetOutput());
+      }
     }
     catch (OrthancException& e)
     {
@@ -406,10 +528,8 @@
     std::string name = call.GetUriComponent("name", "");
     MetadataType metadata = StringToMetadata(name);
 
-    if (metadata >= MetadataType_StartUser &&
-        metadata <= MetadataType_EndUser)
-    {
-      // It is forbidden to modify internal metadata
+    if (IsUserMetadata(metadata))  // It is forbidden to modify internal metadata
+    {      
       OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata);
       call.GetOutput().AnswerBuffer("", "text/plain");
     }
@@ -427,8 +547,7 @@
     std::string value;
     call.BodyToString(value);
 
-    if (metadata >= MetadataType_StartUser &&
-        metadata <= MetadataType_EndUser)
+    if (IsUserMetadata(metadata))  // It is forbidden to modify internal metadata
     {
       // It is forbidden to modify internal metadata
       OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value);
@@ -479,6 +598,7 @@
     {
       Json::Value operations = Json::arrayValue;
 
+      operations.append("compress");
       operations.append("compressed-data");
 
       if (info.GetCompressedMD5() != "")
@@ -488,6 +608,7 @@
 
       operations.append("compressed-size");
       operations.append("data");
+      operations.append("is-compressed");
 
       if (info.GetUncompressedMD5() != "")
       {
@@ -495,6 +616,7 @@
       }
 
       operations.append("size");
+      operations.append("uncompress");
 
       if (info.GetCompressedMD5() != "" &&
           info.GetUncompressedMD5() != "")
@@ -636,8 +758,7 @@
     std::string name = call.GetUriComponent("name", "");
 
     FileContentType contentType = StringToContentType(name);
-    if (contentType >= FileContentType_StartUser &&  // It is forbidden to modify internal attachments
-        contentType <= FileContentType_EndUser &&
+    if (IsUserContentType(contentType) &&  // It is forbidden to modify internal attachments
         context.AddAttachment(publicId, StringToContentType(name), call.GetBodyData(), call.GetBodySize()))
     {
       call.GetOutput().AnswerBuffer("{}", "application/json");
@@ -653,16 +774,39 @@
     std::string name = call.GetUriComponent("name", "");
     FileContentType contentType = StringToContentType(name);
 
-    if (contentType >= FileContentType_StartUser &&
-        contentType <= FileContentType_EndUser)
+    if (IsUserContentType(contentType))  // It is forbidden to delete internal attachments
     {
-      // It is forbidden to delete internal attachments
       OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType);
       call.GetOutput().AnswerBuffer("{}", "application/json");
     }
   }
 
 
+  template <enum CompressionType compression>
+  static void ChangeAttachmentCompression(RestApiPostCall& call)
+  {
+    CheckValidResourceType(call);
+
+    std::string publicId = call.GetUriComponent("id", "");
+    std::string name = call.GetUriComponent("name", "");
+    FileContentType contentType = StringToContentType(name);
+
+    OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, contentType, compression);
+    call.GetOutput().AnswerBuffer("{}", "application/json");
+  }
+
+
+  static void IsAttachmentCompressed(RestApiGetCall& call)
+  {
+    FileInfo info;
+    if (GetAttachmentInfo(info, call))
+    {
+      std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1";
+      call.GetOutput().AnswerBuffer(answer, "text/plain");
+    }
+  }
+
+
   // Raw access to the DICOM tags of an instance ------------------------------
 
   static void GetRawContent(RestApiGetCall& call)
@@ -764,7 +908,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, sharedTags);
+        Toolbox::SimplifyTags(simplified, sharedTags);
         call.GetOutput().AnswerJson(simplified);
       }
       else
@@ -831,7 +975,7 @@
     if (simplify)
     {
       Json::Value simplified;
-      SimplifyTags(simplified, result);
+      Toolbox::SimplifyTags(simplified, result);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -850,20 +994,44 @@
   }
 
 
+  namespace
+  {
+    typedef std::list< std::pair<ResourceType, std::string> >  LookupResults;
+  }
+
+
+  static void AccumulateLookupResults(LookupResults& result,
+                                      ServerIndex& index,
+                                      const DicomTag& tag,
+                                      const std::string& value,
+                                      ResourceType level)
+  {
+    std::list<std::string> tmp;
+    index.LookupIdentifierExact(tmp, level, tag, value);
+
+    for (std::list<std::string>::const_iterator
+           it = tmp.begin(); it != tmp.end(); ++it)
+    {
+      result.push_back(std::make_pair(level, *it));
+    }
+  }
+
+
   static void Lookup(RestApiPostCall& call)
   {
-    typedef std::list< std::pair<ResourceType, std::string> >  Resources;
-
     std::string tag;
     call.BodyToString(tag);
-    Resources resources;
-
-    OrthancRestApi::GetIndex(call).LookupIdentifier(resources, tag);
 
-    Json::Value result = Json::arrayValue;
-    
-    for (Resources::const_iterator it = resources.begin();
-         it != resources.end(); ++it)
+    LookupResults resources;
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+    AccumulateLookupResults(resources, index, DICOM_TAG_PATIENT_ID, tag, ResourceType_Patient);
+    AccumulateLookupResults(resources, index, DICOM_TAG_STUDY_INSTANCE_UID, tag, ResourceType_Study);
+    AccumulateLookupResults(resources, index, DICOM_TAG_SERIES_INSTANCE_UID, tag, ResourceType_Series);
+    AccumulateLookupResults(resources, index, DICOM_TAG_SOP_INSTANCE_UID, tag, ResourceType_Instance);
+
+    Json::Value result = Json::arrayValue;    
+    for (LookupResults::const_iterator 
+           it = resources.begin(); it != resources.end(); ++it)
     {     
       ResourceType type = it->first;
       const std::string& id = it->second;
@@ -891,7 +1059,8 @@
         request.isMember("Query") &&
         request["Level"].type() == Json::stringValue &&
         request["Query"].type() == Json::objectValue &&
-        (!request.isMember("CaseSensitive") || request["CaseSensitive"].type() == Json::booleanValue))
+        (!request.isMember("CaseSensitive") || request["CaseSensitive"].type() == Json::booleanValue) &&
+        (!request.isMember("Limit") || request["Limit"].type() == Json::intValue))
     {
       bool expand = false;
       if (request.isMember("Expand"))
@@ -905,10 +1074,19 @@
         caseSensitive = request["CaseSensitive"].asBool();
       }
 
+      size_t limit = 0;
+      if (request.isMember("Limit"))
+      {
+        limit = request["CaseSensitive"].asInt();
+        if (limit < 0)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+
       std::string level = request["Level"].asString();
 
-      DicomFindQuery query;
-      query.SetLevel(StringToResourceType(level.c_str()));
+      LookupResource query(StringToResourceType(level.c_str()));
 
       Json::Value::Members members = request["Query"].getMemberNames();
       for (size_t i = 0; i < members.size(); i++)
@@ -918,14 +1096,13 @@
           throw OrthancException(ErrorCode_BadRequest);
         }
 
-        query.SetConstraint(FromDcmtkBridge::ParseTag(members[i]), 
-                            request["Query"][members[i]].asString(),
-                            caseSensitive);
+        query.AddDicomConstraint(FromDcmtkBridge::ParseTag(members[i]), 
+                                 request["Query"][members[i]].asString(),
+                                 caseSensitive);
       }
       
       std::list<std::string> resources;
-      ResourceFinder finder(context);
-      finder.Apply(resources, query);
+      context.Apply(resources, query, limit);
       AnswerListOfResources(call.GetOutput(), context.GetIndex(), resources, query.GetLevel(), expand);
     }
     else
@@ -1002,7 +1179,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, full);
+        Toolbox::SimplifyTags(simplified, full);
         result[*it] = simplified;
       }
       else
@@ -1065,6 +1242,19 @@
   }
 
 
+  static void OrderSlices(RestApiGetCall& call)
+  {
+    const std::string id = call.GetUriComponent("id", "");
+
+    ServerIndex& index = OrthancRestApi::GetIndex(call);
+    SliceOrdering ordering(index, id);
+
+    Json::Value result;
+    ordering.Format(result);
+    call.GetOutput().AnswerJson(result);
+  }
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -1125,14 +1315,17 @@
     Register("/{resourceType}/{id}/attachments", ListAttachments);
     Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment);
     Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations);
+    Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment);
+    Register("/{resourceType}/{id}/attachments/{name}/compress", ChangeAttachmentCompression<CompressionType_ZlibWithSize>);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5);
     Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize);
     Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>);
+    Register("/{resourceType}/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed);
     Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5);
     Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize);
+    Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>);
     Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment);
-    Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment);
 
     Register("/tools/lookup", Lookup);
     Register("/tools/find", Find);
@@ -1156,5 +1349,7 @@
     Register("/series/{id}/instances-tags", GetChildInstancesTags);
 
     Register("/instances/{id}/content/*", GetRawContent);
+
+    Register("/series/{id}/ordered-slices", OrderSlices);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -53,9 +53,7 @@
   {
     Json::Value result = Json::objectValue;
 
-    std::string dbVersion = OrthancRestApi::GetIndex(call).GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "0");
-
-    result["DatabaseVersion"] = boost::lexical_cast<int>(dbVersion);
+    result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion();
     result["DicomAet"] = Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC");
     result["DicomPort"] = Configuration::GetGlobalIntegerParameter("DicomPort", 4242);
     result["HttpPort"] = Configuration::GetGlobalIntegerParameter("HttpPort", 8042);
--- a/OrthancServer/ParsedDicomFile.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -84,16 +84,16 @@
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
 #include "Internals/DicomImageDecoder.h"
-#include "../Core/Logging.h"
-#include "../Core/Toolbox.h"
-#include "../Core/OrthancException.h"
+#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 #include "../Core/Images/ImageBuffer.h"
+#include "../Core/Images/JpegWriter.h"
+#include "../Core/Images/JpegReader.h"
+#include "../Core/Images/PngReader.h"
 #include "../Core/Images/PngWriter.h"
+#include "../Core/Logging.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Toolbox.h"
 #include "../Core/Uuid.h"
-#include "../Core/DicomFormat/DicomString.h"
-#include "../Core/DicomFormat/DicomNullValue.h"
-#include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
-#include "../Core/Images/PngReader.h"
 
 #include <list>
 #include <limits>
@@ -137,6 +137,7 @@
 
 #include <boost/math/special_functions/round.hpp>
 #include <dcmtk/dcmdata/dcostrmb.h>
+#include <boost/algorithm/string/predicate.hpp>
 
 
 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
@@ -148,7 +149,6 @@
   struct ParsedDicomFile::PImpl
   {
     std::auto_ptr<DcmFileFormat> file_;
-    Encoding encoding_;
   };
 
 
@@ -173,8 +173,6 @@
     }
     pimpl_->file_->loadAllDataIntoMemory();
     pimpl_->file_->transferEnd();
-
-    pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
   }
 
 
@@ -519,284 +517,6 @@
   }
 
 
-  
-
-
-  static DcmElement* CreateElementForTag(const DicomTag& tag)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    switch (key.getEVR())
-    {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-      /**
-       * TODO.
-       **/
-    
-      case EVR_OB:  // other byte
-      case EVR_OF:  // other float
-      case EVR_OW:  // other word
-      case EVR_AT:  // attribute tag
-        throw OrthancException(ErrorCode_NotImplemented);
-
-      case EVR_UN:  // unknown value representation
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * String types.
-       * http://support.dcmtk.org/docs/classDcmByteString.html
-       **/
-      
-      case EVR_AS:  // age string
-        return new DcmAgeString(key);
-
-      case EVR_AE:  // application entity title
-        return new DcmApplicationEntity(key);
-
-      case EVR_CS:  // code string
-        return new DcmCodeString(key);        
-
-      case EVR_DA:  // date string
-        return new DcmDate(key);
-        
-      case EVR_DT:  // date time string
-        return new DcmDateTime(key);
-
-      case EVR_DS:  // decimal string
-        return new DcmDecimalString(key);
-
-      case EVR_IS:  // integer string
-        return new DcmIntegerString(key);
-
-      case EVR_TM:  // time string
-        return new DcmTime(key);
-
-      case EVR_UI:  // unique identifier
-        return new DcmUniqueIdentifier(key);
-
-      case EVR_ST:  // short text
-        return new DcmShortText(key);
-
-      case EVR_LO:  // long string
-        return new DcmLongString(key);
-
-      case EVR_LT:  // long text
-        return new DcmLongText(key);
-
-      case EVR_UT:  // unlimited text
-        return new DcmUnlimitedText(key);
-
-      case EVR_SH:  // short string
-        return new DcmShortString(key);
-
-      case EVR_PN:  // person name
-        return new DcmPersonName(key);
-
-        
-      /**
-       * Numerical types
-       **/ 
-      
-      case EVR_SL:  // signed long
-        return new DcmSignedLong(key);
-
-      case EVR_SS:  // signed short
-        return new DcmSignedShort(key);
-
-      case EVR_UL:  // unsigned long
-        return new DcmUnsignedLong(key);
-
-      case EVR_US:  // unsigned short
-        return new DcmUnsignedShort(key);
-
-      case EVR_FL:  // float single-precision
-        return new DcmFloatingPointSingle(key);
-
-      case EVR_FD:  // float double-precision
-        return new DcmFloatingPointDouble(key);
-
-
-      /**
-       * Sequence types, should never occur at this point.
-       **/
-
-      case EVR_SQ:  // sequence of items
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * Internal to DCMTK.
-       **/ 
-
-      case EVR_ox:  // OB or OW depending on context
-      case EVR_xs:  // SS or US depending on context
-      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-      case EVR_na:  // na="not applicable", for data which has no VR
-      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-      case EVR_item:  // used internally for items
-      case EVR_metainfo:  // used internally for meta info datasets
-      case EVR_dataset:  // used internally for datasets
-      case EVR_fileFormat:  // used internally for DICOM files
-      case EVR_dicomDir:  // used internally for DICOMDIR objects
-      case EVR_dirRecord:  // used internally for DICOMDIR records
-      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-      case EVR_pixelItem:  // used internally for pixel items in a compressed image
-      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-      case EVR_PixelData:  // used internally for uncompressed pixeld data
-      case EVR_OverlayData:  // used internally for overlay data
-      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-      default:
-        break;
-    }
-
-    throw OrthancException(ErrorCode_InternalError);          
-  }
-
-
-
-  static void FillElementWithString(DcmElement& element,
-                                    const DicomTag& tag,
-                                    const std::string& value)
-  {
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-    bool ok = false;
-    
-    try
-    {
-      switch (key.getEVR())
-      {
-        // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-        /**
-         * TODO.
-         **/
-
-        case EVR_OB:  // other byte
-        case EVR_OF:  // other float
-        case EVR_OW:  // other word
-        case EVR_AT:  // attribute tag
-          throw OrthancException(ErrorCode_NotImplemented);
-    
-        case EVR_UN:  // unknown value representation
-          throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-        /**
-         * String types.
-         **/
-      
-        case EVR_DS:  // decimal string
-        case EVR_IS:  // integer string
-        case EVR_AS:  // age string
-        case EVR_DA:  // date string
-        case EVR_DT:  // date time string
-        case EVR_TM:  // time string
-        case EVR_AE:  // application entity title
-        case EVR_CS:  // code string
-        case EVR_SH:  // short string
-        case EVR_LO:  // long string
-        case EVR_ST:  // short text
-        case EVR_LT:  // long text
-        case EVR_UT:  // unlimited text
-        case EVR_PN:  // person name
-        case EVR_UI:  // unique identifier
-        {
-          ok = element.putString(value.c_str()).good();
-          break;
-        }
-
-        
-        /**
-         * Numerical types
-         **/ 
-      
-        case EVR_SL:  // signed long
-        {
-          ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good();
-          break;
-        }
-
-        case EVR_SS:  // signed short
-        {
-          ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good();
-          break;
-        }
-
-        case EVR_UL:  // unsigned long
-        {
-          ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good();
-          break;
-        }
-
-        case EVR_US:  // unsigned short
-        {
-          ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good();
-          break;
-        }
-
-        case EVR_FL:  // float single-precision
-        {
-          ok = element.putFloat32(boost::lexical_cast<float>(value)).good();
-          break;
-        }
-
-        case EVR_FD:  // float double-precision
-        {
-          ok = element.putFloat64(boost::lexical_cast<double>(value)).good();
-          break;
-        }
-
-
-        /**
-         * Sequence types, should never occur at this point.
-         **/
-
-        case EVR_SQ:  // sequence of items
-        {
-          ok = false;
-          break;
-        }
-
-
-        /**
-         * Internal to DCMTK.
-         **/ 
-
-        case EVR_ox:  // OB or OW depending on context
-        case EVR_xs:  // SS or US depending on context
-        case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-        case EVR_na:  // na="not applicable", for data which has no VR
-        case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-        case EVR_item:  // used internally for items
-        case EVR_metainfo:  // used internally for meta info datasets
-        case EVR_dataset:  // used internally for datasets
-        case EVR_fileFormat:  // used internally for DICOM files
-        case EVR_dicomDir:  // used internally for DICOMDIR objects
-        case EVR_dirRecord:  // used internally for DICOMDIR records
-        case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-        case EVR_pixelItem:  // used internally for pixel items in a compressed image
-        case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-        case EVR_PixelData:  // used internally for uncompressed pixeld data
-        case EVR_OverlayData:  // used internally for overlay data
-        case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-        default:
-          break;
-      }
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      ok = false;
-    }
-
-    if (!ok)
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
   void ParsedDicomFile::Remove(const DicomTag& tag)
   {
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
@@ -823,7 +543,7 @@
       DcmTag tag(element->getTag());
 
       // Is this a private tag?
-      if (FromDcmtkBridge::IsPrivateTag(tag))
+      if (tag.isPrivate())
       {
         bool remove = true;
 
@@ -857,53 +577,40 @@
   }
 
 
-
-
-  void ParsedDicomFile::Insert(const DicomTag& tag,
-                               const std::string& value)
+  static void InsertInternal(DcmDataset& dicom,
+                             DcmElement* element)
   {
-    OFCondition cond;
-
-    if (FromDcmtkBridge::IsPrivateTag(tag))
-    {
-      // This is a private tag
-      // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata
-
-      DcmTag key(tag.GetGroup(), tag.GetElement(), EVR_OB);
-      cond = pimpl_->file_->getDataset()->putAndInsertUint8Array
-        (key, (const Uint8*) value.c_str(), value.size(), false);
-    }
-    else
-    {
-      std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
-      FillElementWithString(*element, tag, value);
-
-      cond = pimpl_->file_->getDataset()->insert(element.release(), false, false);
-    }
-
+    OFCondition cond = dicom.insert(element, false, false);
     if (!cond.good())
     {
       // This field already exists
+      delete element;
       throw OrthancException(ErrorCode_InternalError);
     }
   }
 
 
-  void ParsedDicomFile::Replace(const DicomTag& tag,
-                                const std::string& value,
-                                DicomReplaceMode mode)
+  void ParsedDicomFile::Insert(const DicomTag& tag,
+                               const Json::Value& value,
+                               bool decodeBinaryTags)
   {
-    DcmTagKey key(tag.GetGroup(), tag.GetElement());
-    DcmElement* element = NULL;
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding()));
+    InsertInternal(*pimpl_->file_->getDataset(), element.release());
+  }
+
 
-    if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() ||
-        element == NULL)
+  static void ReplaceInternal(DcmDataset& dicom,
+                              std::auto_ptr<DcmElement>& element,
+                              DicomReplaceMode mode)
+  {
+    const DcmTagKey& tag = element->getTag();
+
+    if (!dicom.findAndDeleteElement(tag).good())
     {
       // This field does not exist, act wrt. the specified "mode"
       switch (mode)
       {
         case DicomReplaceMode_InsertIfAbsent:
-          Insert(tag, value);
           break;
 
         case DicomReplaceMode_ThrowIfAbsent:
@@ -913,22 +620,43 @@
           return;
       }
     }
+
+    // Either the tag was not existing, or the replace mode was set to
+    // "InsertIfAbsent"
+    InsertInternal(dicom, element.release());
+  }
+
+
+  void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
+                                         const std::string& utf8Value,
+                                         bool decodeBinaryTags)
+  {
+    if (tag != DICOM_TAG_SOP_CLASS_UID &&
+        tag != DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      return;
+    }
+
+    std::string binary;
+    const std::string* decoded = &utf8Value;
+
+    if (decodeBinaryTags &&
+        boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
+    {
+      std::string mime;
+      Toolbox::DecodeDataUriScheme(mime, binary, utf8Value);
+      decoded = &binary;
+    }
     else
     {
-      if (FromDcmtkBridge::IsPrivateTag(tag))
+      Encoding encoding = GetEncoding();
+      if (GetEncoding() != Encoding_Utf8)
       {
-        if (!element->putUint8Array((const Uint8*) value.c_str(), value.size()).good())
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-      }
-      else
-      {
-        FillElementWithString(*element, tag, value);
+        binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
+        decoded = &binary;
       }
     }
 
-
     /**
      * dcmodify will automatically correct 'Media Storage SOP Class
      * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
@@ -940,12 +668,44 @@
 
     if (tag == DICOM_TAG_SOP_CLASS_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
+      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
     }
 
     if (tag == DICOM_TAG_SOP_INSTANCE_UID)
     {
-      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
+      Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent);
+    }    
+  }
+
+
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const std::string& utf8Value,
+                                DicomReplaceMode mode)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+    FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, false, GetEncoding());
+    ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
+    UpdateStorageUid(tag, utf8Value, false);
+  }
+
+    
+  void ParsedDicomFile::Replace(const DicomTag& tag,
+                                const Json::Value& value,
+                                bool decodeBinaryTags,
+                                DicomReplaceMode mode)
+  {
+    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags, GetEncoding()));
+    ReplaceInternal(*pimpl_->file_->getDataset(), element, mode);
+
+    if (tag == DICOM_TAG_SOP_CLASS_UID ||
+        tag == DICOM_TAG_SOP_INSTANCE_UID)
+    {
+      if (value.type() != Json::stringValue)
+      {
+        throw OrthancException(ErrorCode_BadParameterType);
+      }
+
+      UpdateStorageUid(tag, value.asString(), decodeBinaryTags);
     }
   }
 
@@ -968,6 +728,7 @@
     DcmDataset& dataset = *pimpl_->file_->getDataset();
 
     if (FromDcmtkBridge::IsPrivateTag(tag) ||
+        FromDcmtkBridge::IsUnknownTag(tag) ||
         tag == DICOM_TAG_PIXEL_DATA ||
         tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
     {
@@ -1002,15 +763,18 @@
         return false;
       }
 
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_));
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
+                                  (*element, DicomToJsonFlags_Default, GetEncoding()));
       
-      if (v.get() == NULL)
+      if (v.get() == NULL ||
+          v->IsNull())
       {
         value = "";
       }
       else
       {
-        value = v->AsString();
+        // TODO v->IsBinary()
+        value = v->GetContent();
       }
       
       return true;
@@ -1034,36 +798,6 @@
   }
 
 
-  template <typename T>
-  static void ExtractPngImageTruncate(std::string& result,
-                                      DicomIntegerPixelAccessor& accessor,
-                                      PixelFormat format)
-  {
-    assert(accessor.GetInformation().GetChannelCount() == 1);
-
-    PngWriter w;
-
-    std::vector<T> image(accessor.GetInformation().GetWidth() * accessor.GetInformation().GetHeight(), 0);
-    T* pixel = &image[0];
-    for (unsigned int y = 0; y < accessor.GetInformation().GetHeight(); y++)
-    {
-      for (unsigned int x = 0; x < accessor.GetInformation().GetWidth(); x++, pixel++)
-      {
-        int32_t v = accessor.GetValue(x, y);
-        if (v < static_cast<int32_t>(std::numeric_limits<T>::min()))
-          *pixel = std::numeric_limits<T>::min();
-        else if (v > static_cast<int32_t>(std::numeric_limits<T>::max()))
-          *pixel = std::numeric_limits<T>::max();
-        else
-          *pixel = static_cast<T>(v);
-      }
-    }
-
-    w.WriteToMemory(result, accessor.GetInformation().GetWidth(), accessor.GetInformation().GetHeight(),
-                    accessor.GetInformation().GetWidth() * sizeof(T), format, &image[0]);
-  }
-
-
   void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
   {
     FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset());
@@ -1082,7 +816,6 @@
   ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
   {
     pimpl_->file_.reset(new DcmFileFormat);
-    pimpl_->encoding_ = Encoding_Ascii;
     Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
     Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
     Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
@@ -1112,7 +845,6 @@
     pimpl_(new PImpl)
   {
     pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
-    pimpl_->encoding_ = other.pimpl_->encoding_;
 
     // Create a new instance-level identifier
     Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
@@ -1139,14 +871,12 @@
 
   void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
   {
-    std::string mime, base64;
-    Toolbox::DecodeDataUriScheme(mime, base64, dataUriScheme);
+    std::string mime, content;
+    Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme);
     Toolbox::ToLowerCase(mime);
 
-    std::string content;
-    Toolbox::DecodeBase64(content, base64);
-
-    if (mime == "image/png")
+    if (mime == "image/png" ||
+        mime == "image/jpeg")
     {
       EmbedImage(mime, content);
     }
@@ -1156,7 +886,7 @@
     }
     else
     {
-      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file";
+      LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
       throw OrthancException(ErrorCode_NotImplemented);
     }
   }
@@ -1171,6 +901,12 @@
       reader.ReadFromMemory(content);
       EmbedImage(reader);
     }
+    else if (mime == "image/jpeg")
+    {
+      JpegReader reader;
+      reader.ReadFromMemory(content);
+      EmbedImage(reader);
+    }
     else
     {
       throw OrthancException(ErrorCode_NotImplemented);
@@ -1343,9 +1079,30 @@
   }
 
 
+  void ParsedDicomFile::ExtractJpegImage(std::string& result,
+                                         unsigned int frame,
+                                         ImageExtractionMode mode,
+                                         uint8_t quality)
+  {
+    if (mode != ImageExtractionMode_UInt8 &&
+        mode != ImageExtractionMode_Preview)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    ImageBuffer buffer;
+    ExtractImage(buffer, frame, mode);
+
+    ImageAccessor accessor(buffer.GetConstAccessor());
+    JpegWriter writer;
+    writer.SetQuality(quality);
+    writer.WriteToMemory(result, accessor);
+  }
+
+
   Encoding ParsedDicomFile::GetEncoding() const
   {
-    return pimpl_->encoding_;
+    return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
   }
 
 
@@ -1358,24 +1115,16 @@
       return;
     }
 
-    pimpl_->encoding_ = encoding;
-
     std::string s = GetDicomSpecificCharacterSet(encoding);
     Replace(DICOM_TAG_SPECIFIC_CHARACTER_SET, s, DicomReplaceMode_InsertIfAbsent);
   }
 
-  void ParsedDicomFile::ToJson(Json::Value& target, bool simplify)
+  void ParsedDicomFile::ToJson(Json::Value& target, 
+                               DicomToJsonFormat format,
+                               DicomToJsonFlags flags,
+                               unsigned int maxStringLength)
   {
-    if (simplify)
-    {
-      Json::Value tmp;
-      FromDcmtkBridge::ToJson(tmp, *pimpl_->file_->getDataset());
-      SimplifyTags(target, tmp);
-    }
-    else
-    {
-      FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset());
-    }
+    FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset(), format, flags, maxStringLength);
   }
 
 
@@ -1467,4 +1216,10 @@
 
     return true;
   }
+
+
+  void ParsedDicomFile::Convert(DicomMap& tags)
+  {
+    FromDcmtkBridge::Convert(tags, *pimpl_->file_->getDataset());
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Wed Nov 18 10:16:21 2015 +0100
@@ -37,6 +37,7 @@
 #include "ServerEnumerations.h"
 #include "../Core/Images/ImageAccessor.h"
 #include "../Core/Images/ImageBuffer.h"
+#include "../Core/IDynamicObject.h"
 
 namespace Orthanc
 {
@@ -53,6 +54,10 @@
 
     void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
 
+    void UpdateStorageUid(const DicomTag& tag,
+                          const std::string& value,
+                          bool decodeBinaryTags);
+
   public:
     ParsedDicomFile();  // Create a minimal DICOM instance
 
@@ -74,13 +79,19 @@
 
     void Remove(const DicomTag& tag);
 
-    void Insert(const DicomTag& tag,
-                const std::string& value);
+    void Replace(const DicomTag& tag,
+                 const std::string& utf8Value,
+                 DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
     void Replace(const DicomTag& tag,
-                 const std::string& value,
+                 const Json::Value& value,  // Assumed to be encoded with UTF-8
+                 bool decodeBinaryTags,
                  DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
+    void Insert(const DicomTag& tag,
+                const Json::Value& value,   // Assumed to be encoded with UTF-8
+                bool decodeBinaryTags);
+
     void RemovePrivateTags()
     {
       RemovePrivateTagsInternal(NULL);
@@ -118,18 +129,27 @@
                          unsigned int frame,
                          ImageExtractionMode mode);
 
+    void ExtractJpegImage(std::string& result,
+                          unsigned int frame,
+                          ImageExtractionMode mode,
+                          uint8_t quality);
+
     Encoding GetEncoding() const;
 
     void SetEncoding(Encoding encoding);
 
     void ToJson(Json::Value& target, 
-                bool simplify);
+                DicomToJsonFormat format,
+                DicomToJsonFlags flags,
+                unsigned int maxStringLength);
 
     bool HasTag(const DicomTag& tag) const;
 
     void EmbedPdf(const std::string& pdf);
 
     bool ExtractPdf(std::string& pdf);
+
+    void Convert(DicomMap& tags);
   };
 
 }
--- a/OrthancServer/PrepareDatabase.sql	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Wed Nov 18 10:16:21 2015 +0100
@@ -123,4 +123,4 @@
 
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
-INSERT INTO GlobalProperties VALUES (1, "5");
+INSERT INTO GlobalProperties VALUES (1, "6");
--- a/OrthancServer/ResourceFinder.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +0,0 @@
-/**
- * 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 "ResourceFinder.h"
-
-#include "../Core/Logging.h"
-#include "FromDcmtkBridge.h"
-#include "ServerContext.h"
-
-#include <boost/algorithm/string/predicate.hpp>
-
-namespace Orthanc
-{
-  class ResourceFinder::CandidateResources
-  {
-  private:
-    typedef std::map<DicomTag, std::string>  Query;
-
-    ResourceFinder&        finder_;
-    ServerIndex&           index_;
-    ResourceType           level_;
-    bool                   isFilterApplied_;
-    std::set<std::string>  filtered_;
-
-     
-    static void ListToSet(std::set<std::string>& target,
-                          const std::list<std::string>& source)
-    {
-      for (std::list<std::string>::const_iterator
-             it = source.begin(); it != source.end(); ++it)
-      {
-        target.insert(*it);
-      }
-    }
-
-
-  public:
-    CandidateResources(ResourceFinder& finder) : 
-      finder_(finder),
-      index_(finder.context_.GetIndex()),
-      level_(ResourceType_Patient), 
-      isFilterApplied_(false)
-    {
-    }
-
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-
-    void GoDown()
-    {
-      assert(level_ != ResourceType_Instance);
-
-      if (isFilterApplied_)
-      {
-        std::set<std::string> tmp = filtered_;
-
-        filtered_.clear();
-
-        for (std::set<std::string>::const_iterator 
-               it = tmp.begin(); it != tmp.end(); ++it)
-        {
-          std::list<std::string> children;
-          try
-          {
-            index_.GetChildren(children, *it);
-            ListToSet(filtered_, children);
-          }
-          catch (OrthancException&)
-          {
-            // The resource was removed in the meantime
-          }
-        }
-      }
-
-      switch (level_)
-      {
-        case ResourceType_Patient:
-          level_ = ResourceType_Study;
-          break;
-
-        case ResourceType_Study:
-          level_ = ResourceType_Series;
-          break;
-
-        case ResourceType_Series:
-          level_ = ResourceType_Instance;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-
-    void Flatten(std::list<std::string>& resources) const
-    {
-      resources.clear();
-
-      if (isFilterApplied_)
-      {
-        for (std::set<std::string>::const_iterator 
-               it = filtered_.begin(); it != filtered_.end(); ++it)
-        {
-          resources.push_back(*it);
-        }
-      }
-      else
-      {
-        index_.GetAllUuids(resources, level_);
-      }
-    }
-
-    
-    void RestrictIdentifier(const IQuery& query,
-                            const DicomTag& tag)
-    {
-      assert((level_ == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
-             (level_ == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) ||
-             (level_ == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
-             (level_ == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
-             (level_ == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
-
-      std::string value;
-      if (!query.RestrictIdentifier(value, tag))
-      {
-        return;
-      }
-
-      LOG(INFO) << "Lookup for identifier tag "
-                << FromDcmtkBridge::GetName(tag) << " (value: " << value << ")";
-
-      std::list<std::string> resources;
-      index_.LookupIdentifier(resources, tag, value, level_);
-
-      if (isFilterApplied_)
-      {
-        std::set<std::string>  s;
-        ListToSet(s, resources);
-
-        std::set<std::string> tmp = filtered_;
-        filtered_.clear();
-
-        for (std::set<std::string>::const_iterator 
-               it = tmp.begin(); it != tmp.end(); ++it)
-        {
-          if (s.find(*it) != s.end())
-          {
-            filtered_.insert(*it);
-          }
-        }
-      }
-      else
-      {
-        assert(filtered_.empty());
-        isFilterApplied_ = true;
-        ListToSet(filtered_, resources);
-      }
-    }
-
-
-    void RestrictMainDicomTags(const IQuery& query)
-    {
-      if (!query.HasMainDicomTagsFilter(level_))
-      {
-        return;
-      }
-
-      std::list<std::string> resources;
-      Flatten(resources);
-
-      isFilterApplied_ = true;
-      filtered_.clear();
-
-      for (std::list<std::string>::const_iterator
-             it = resources.begin(); it != resources.end(); ++it)
-      {
-        DicomMap mainTags;
-        if (index_.GetMainDicomTags(mainTags, *it, level_))
-        {
-          if (query.FilterMainDicomTags(*it, level_, mainTags))
-          {
-            filtered_.insert(*it);
-          }
-        }
-      }
-    }
-  };
-
-
-  ResourceFinder::ResourceFinder(ServerContext& context) : 
-    context_(context),
-    maxResults_(0)
-  {
-  }
-
-
-  void ResourceFinder::ApplyAtLevel(CandidateResources& candidates,
-                                    const IQuery& query,
-                                    ResourceType level)
-  {
-    if (level != ResourceType_Patient)
-    {
-      candidates.GoDown();
-    }
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-      {
-        candidates.RestrictIdentifier(query, DICOM_TAG_PATIENT_ID);
-        break;
-      }
-
-      case ResourceType_Study:
-      {
-        candidates.RestrictIdentifier(query, DICOM_TAG_STUDY_INSTANCE_UID);
-        candidates.RestrictIdentifier(query, DICOM_TAG_ACCESSION_NUMBER);
-        break;
-      }
-
-      case ResourceType_Series:
-      {
-        candidates.RestrictIdentifier(query, DICOM_TAG_SERIES_INSTANCE_UID);
-        break;
-      }
-
-      case ResourceType_Instance:
-      {
-        candidates.RestrictIdentifier(query, DICOM_TAG_SOP_INSTANCE_UID);
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    candidates.RestrictMainDicomTags(query);
-  }
-
-
-
-  static bool LookupOneInstance(std::string& result,
-                                ServerIndex& index,
-                                const std::string& id,
-                                ResourceType type)
-  {
-    if (type == ResourceType_Instance)
-    {
-      result = id;
-      return true;
-    }
-
-    std::string childId;
-    
-    {
-      std::list<std::string> children;
-      index.GetChildInstances(children, id);
-
-      if (children.empty())
-      {
-        return false;
-      }
-
-      childId = children.front();
-    }
-
-    return LookupOneInstance(result, index, childId, GetChildResourceType(type));
-  }
-
-
-  bool ResourceFinder::Apply(std::list<std::string>& result,
-                             const IQuery& query)
-  {
-    CandidateResources candidates(*this);
-
-    ApplyAtLevel(candidates, query, ResourceType_Patient);
-
-    const ResourceType level = query.GetLevel();
-
-    if (level == ResourceType_Study ||
-        level == ResourceType_Series ||
-        level == ResourceType_Instance)
-    {
-      ApplyAtLevel(candidates, query, ResourceType_Study);
-    }
-        
-    if (level == ResourceType_Series ||
-        level == ResourceType_Instance)
-    {
-      ApplyAtLevel(candidates, query, ResourceType_Series);
-    }
-        
-    if (level == ResourceType_Instance)
-    {
-      ApplyAtLevel(candidates, query, ResourceType_Instance);
-    }
-
-    if (!query.HasInstanceFilter())
-    {
-      candidates.Flatten(result);
-
-      if (maxResults_ != 0 &&
-          result.size() >= maxResults_)
-      {
-        result.resize(maxResults_);
-        return false;
-      }
-      else
-      {
-        return true;
-      }
-    }
-    else
-    {
-      std::list<std::string> tmp;
-      candidates.Flatten(tmp);
-      
-      result.clear();
-      for (std::list<std::string>::const_iterator 
-             resource = tmp.begin(); resource != tmp.end(); ++resource)
-      {
-        try
-        {
-          std::string instance;
-          if (LookupOneInstance(instance, context_.GetIndex(), *resource, level))
-          {
-            Json::Value content;
-            context_.ReadJson(content, instance);
-            if (query.FilterInstance(*resource, content))
-            {
-              result.push_back(*resource);
-
-              if (maxResults_ != 0 &&
-                  result.size() >= maxResults_)
-              {
-                // Too many results, stop before recording this new match
-                return false;
-              }
-            }
-          }
-        }
-        catch (OrthancException&)
-        {
-          // This resource has been deleted since the search was started
-        }
-      }      
-    }
-
-    return true;  // All the matching resources have been returned
-  }
-}
--- a/OrthancServer/ResourceFinder.h	Wed Sep 23 10:29:06 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * 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 "ServerIndex.h"
-
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class ResourceFinder : public boost::noncopyable
-  {
-  public:
-    class IQuery : public boost::noncopyable
-    {
-    public:
-      virtual ~IQuery()
-      {
-      }
-
-      virtual ResourceType GetLevel() const = 0;
-
-      virtual bool RestrictIdentifier(std::string& value,
-                                      DicomTag identifier) const = 0;
-
-      virtual bool HasMainDicomTagsFilter(ResourceType level) const = 0;
-
-      virtual bool FilterMainDicomTags(const std::string& resourceId,
-                                       ResourceType level,
-                                       const DicomMap& mainTags) const = 0;
-
-      virtual bool HasInstanceFilter() const = 0;
-
-      virtual bool FilterInstance(const std::string& instanceId,
-                                  const Json::Value& content) const = 0;
-    };
-
-
-  private:
-    typedef std::map<DicomTag, std::string>  Identifiers;
-
-    class CandidateResources;
-
-    ServerContext&    context_;
-    size_t            maxResults_;
-
-    void ApplyAtLevel(CandidateResources& candidates,
-                      const IQuery& query,
-                      ResourceType level);
-
-  public:
-    ResourceFinder(ServerContext& context);
-
-    void SetMaxResults(size_t value)
-    {
-      maxResults_ = value;
-    }
-
-    size_t GetMaxResults() const
-    {
-      return maxResults_;
-    }
-
-    // Returns "true" iff. all the matching resources have been
-    // returned. Will be "false" if the results were truncated by
-    // "SetMaxResults()".
-    bool Apply(std::list<std::string>& result,
-               const IQuery& query);
-  };
-
-}
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -39,28 +39,28 @@
 {
   ModifyInstanceCommand::ModifyInstanceCommand(ServerContext& context,
                                                RequestOrigin origin,
-                                               const DicomModification& modification) :
+                                               DicomModification* modification) :
     context_(context),
     origin_(origin),
     modification_(modification)
   {
-    modification_.SetAllowManualIdentifiers(true);
+    modification_->SetAllowManualIdentifiers(true);
 
-    if (modification_.IsReplaced(DICOM_TAG_PATIENT_ID))
+    if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID))
     {
-      modification_.SetLevel(ResourceType_Patient);
+      modification_->SetLevel(ResourceType_Patient);
     }
-    else if (modification_.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
+    else if (modification_->IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
     {
-      modification_.SetLevel(ResourceType_Study);
+      modification_->SetLevel(ResourceType_Study);
     }
-    else if (modification_.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
+    else if (modification_->IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
     {
-      modification_.SetLevel(ResourceType_Series);
+      modification_->SetLevel(ResourceType_Series);
     }
     else
     {
-      modification_.SetLevel(ResourceType_Instance);
+      modification_->SetLevel(ResourceType_Instance);
     }
 
     if (origin_ != RequestOrigin_Lua)
@@ -71,6 +71,15 @@
   }
 
 
+  ModifyInstanceCommand::~ModifyInstanceCommand()
+  {
+    if (modification_)
+    {
+      delete modification_;
+    }
+  }
+
+
   bool ModifyInstanceCommand::Apply(ListOfStrings& outputs,
                                     const ListOfStrings& inputs)
   {
@@ -88,7 +97,7 @@
           modified.reset(lock.GetDicom().Clone());
         }
 
-        modification_.Apply(*modified);
+        modification_->Apply(*modified);
 
         DicomInstanceToStore toStore;
         assert(origin_ == RequestOrigin_Lua);
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h	Wed Nov 18 10:16:21 2015 +0100
@@ -43,16 +43,18 @@
   private:
     ServerContext& context_;
     RequestOrigin origin_;
-    DicomModification modification_;
+    DicomModification* modification_;
 
   public:
     ModifyInstanceCommand(ServerContext& context,
                           RequestOrigin origin,
-                          const DicomModification& modification);
+                          DicomModification* modification);  // takes the ownership
+
+    virtual ~ModifyInstanceCommand();
 
     const DicomModification& GetModification() const
     {
-      return modification_;
+      return *modification_;
     }
 
     virtual bool Apply(ListOfStrings& outputs,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/IFindConstraint.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,53 @@
+/**
+ * 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 "LookupIdentifierQuery.h"
+
+namespace Orthanc
+{
+  class IFindConstraint : public boost::noncopyable
+  {
+  public:
+    virtual ~IFindConstraint()
+    {
+    }
+
+    virtual IFindConstraint* Clone() const = 0;
+
+    virtual void Setup(LookupIdentifierQuery& lookup,
+                       const DicomTag& tag) const = 0;
+
+    virtual bool Match(const std::string& value) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/ListConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,78 @@
+/**
+ * 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 "ListConstraint.h"
+
+
+namespace Orthanc
+{
+  void ListConstraint::AddAllowedValue(const std::string& value)
+  {
+    if (isCaseSensitive_)
+    {
+      allowedValues_.insert(value);
+    }
+    else
+    {
+      std::string s = value;
+      Toolbox::ToUpperCase(s);
+      allowedValues_.insert(s);      
+    }
+  }
+
+
+  void ListConstraint::Setup(LookupIdentifierQuery& lookup, 
+                             const DicomTag& tag) const
+  {
+    LookupIdentifierQuery::Disjunction& target = lookup.AddDisjunction();
+
+    for (std::set<std::string>::const_iterator
+           it = allowedValues_.begin(); it != allowedValues_.end(); ++it)
+    {
+      target.Add(tag, IdentifierConstraintType_Equal, *it);
+    }
+  }
+
+
+  bool ListConstraint::Match(const std::string& value) const
+  {
+    std::string v = value;
+
+    if (!isCaseSensitive_)
+    {
+      Toolbox::ToUpperCase(v);
+    }
+
+    return allowedValues_.find(v) != allowedValues_.end();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/ListConstraint.h	Wed Nov 18 10:16:21 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/>.
+ **/
+
+
+#pragma once
+
+#include "IFindConstraint.h"
+
+#include <set>
+
+namespace Orthanc
+{
+  class ListConstraint : public IFindConstraint
+  {
+  private:
+    std::set<std::string>  allowedValues_;
+    bool                   isCaseSensitive_;
+
+    ListConstraint(const ListConstraint& other) : 
+      allowedValues_(other.allowedValues_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
+  public:
+    ListConstraint(bool isCaseSensitive) : 
+      isCaseSensitive_(isCaseSensitive)
+    {
+    }
+
+    void AddAllowedValue(const std::string& value);
+
+    virtual IFindConstraint* Clone() const
+    {
+      return new ListConstraint(*this);
+    }
+
+    virtual void Setup(LookupIdentifierQuery& lookup,
+                       const DicomTag& tag) const;
+
+    virtual bool Match(const std::string& value) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/LookupIdentifierQuery.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,283 @@
+/**
+ * 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 "LookupIdentifierQuery.h"
+
+#include "../../Core/OrthancException.h"
+#include "SetOfResources.h"
+#include "../FromDcmtkBridge.h"
+
+#include <cassert>
+
+
+
+namespace Orthanc
+{
+  static const DicomTag patientIdentifiers[] = 
+  {
+    DICOM_TAG_PATIENT_ID,
+    DICOM_TAG_PATIENT_NAME,
+    DICOM_TAG_PATIENT_BIRTH_DATE
+  };
+
+  static const DicomTag studyIdentifiers[] = 
+  {
+    DICOM_TAG_PATIENT_ID,
+    DICOM_TAG_PATIENT_NAME,
+    DICOM_TAG_PATIENT_BIRTH_DATE,
+    DICOM_TAG_STUDY_INSTANCE_UID,
+    DICOM_TAG_ACCESSION_NUMBER,
+    DICOM_TAG_STUDY_DESCRIPTION,
+    DICOM_TAG_STUDY_DATE
+  };
+
+  static const DicomTag seriesIdentifiers[] = 
+  {
+    DICOM_TAG_SERIES_INSTANCE_UID
+  };
+
+  static const DicomTag instanceIdentifiers[] = 
+  {
+    DICOM_TAG_SOP_INSTANCE_UID
+  };
+
+
+  void LookupIdentifierQuery::LoadIdentifiers(const DicomTag*& tags,
+                                              size_t& size,
+                                              ResourceType level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientIdentifiers;
+        size = sizeof(patientIdentifiers) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyIdentifiers;
+        size = sizeof(studyIdentifiers) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesIdentifiers;
+        size = sizeof(seriesIdentifiers) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceIdentifiers;
+        size = sizeof(instanceIdentifiers) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  LookupIdentifierQuery::Disjunction::~Disjunction()
+  {
+    for (size_t i = 0; i < disjunction_.size(); i++)
+    {
+      delete disjunction_[i];
+    }
+  }
+
+
+  void LookupIdentifierQuery::Disjunction::Add(const DicomTag& tag,
+                                               IdentifierConstraintType type,
+                                               const std::string& value)
+  {
+    disjunction_.push_back(new Constraint(tag, type, value));
+  }
+
+
+  LookupIdentifierQuery::~LookupIdentifierQuery()
+  {
+    for (Constraints::iterator it = constraints_.begin();
+         it != constraints_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+
+  bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag,
+                                           ResourceType level)
+  {
+    const DicomTag* tags;
+    size_t size;
+
+    LoadIdentifiers(tags, size, level);
+
+    for (size_t i = 0; i < size; i++)
+    {
+      if (tag == tags[i])
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  void LookupIdentifierQuery::AddConstraint(DicomTag tag,
+                                            IdentifierConstraintType type,
+                                            const std::string& value)
+  {
+    assert(IsIdentifier(tag));
+    constraints_.push_back(new Disjunction);
+    constraints_.back()->Add(tag, type, value);
+  }
+
+
+  LookupIdentifierQuery::Disjunction& LookupIdentifierQuery::AddDisjunction()
+  {
+    constraints_.push_back(new Disjunction);
+    return *constraints_.back();
+  }
+
+
+  std::string LookupIdentifierQuery::NormalizeIdentifier(const std::string& value)
+  {
+    std::string t;
+    t.reserve(value.size());
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == '%' ||
+          value[i] == '_')
+      {
+        t.push_back(' ');  // These characters might break wildcard queries in SQL
+      }
+      else if (isascii(value[i]) &&
+               !iscntrl(value[i]) &&
+               (!isspace(value[i]) || value[i] == ' '))
+      {
+        t.push_back(value[i]);
+      }
+    }
+
+    Toolbox::ToUpperCase(t);
+
+    return Toolbox::StripSpaces(t);
+  }
+
+
+  void LookupIdentifierQuery::StoreIdentifiers(IDatabaseWrapper& database,
+                                               int64_t resource,
+                                               ResourceType level,
+                                               const DicomMap& map)
+  {
+    const DicomTag* tags;
+    size_t size;
+
+    LoadIdentifiers(tags, size, level);
+
+    for (size_t i = 0; i < size; i++)
+    {
+      const DicomValue* value = map.TestAndGetValue(tags[i]);
+      if (value != NULL &&
+          !value->IsNull() &&
+          !value->IsBinary())
+      {
+        std::string s = NormalizeIdentifier(value->GetContent());
+        database.SetIdentifierTag(resource, tags[i], s);
+      }
+    }
+  }
+
+
+  void LookupIdentifierQuery::Apply(std::list<std::string>& result,
+                                    IDatabaseWrapper& database)
+  {
+    SetOfResources resources(database, level_);
+    Apply(resources, database);
+
+    resources.Flatten(result);
+  }
+
+
+  void LookupIdentifierQuery::Apply(SetOfResources& result,
+                                    IDatabaseWrapper& database)
+  {
+    for (size_t i = 0; i < GetSize(); i++)
+    {
+      std::list<int64_t> a;
+
+      for (size_t j = 0; j < constraints_[i]->GetSize(); j++)
+      {
+        const Constraint& constraint = constraints_[i]->GetConstraint(j);
+        std::list<int64_t> b;
+        database.LookupIdentifier(b, level_, constraint.GetTag(), constraint.GetType(), constraint.GetValue());
+
+        a.splice(a.end(), b);
+      }
+
+      result.Intersect(a);
+    }
+  }
+
+
+  void LookupIdentifierQuery::Print(std::ostream& s) const
+  {
+    s << "Constraint: " << std::endl;
+    for (Constraints::const_iterator
+           it = constraints_.begin(); it != constraints_.end(); ++it)
+    {
+      if (it == constraints_.begin())
+        s << "   ";
+      else
+        s << "OR ";
+
+      for (size_t j = 0; j < (*it)->GetSize(); j++)
+      {
+        const Constraint& c = (*it)->GetConstraint(j);
+        s << FromDcmtkBridge::GetName(c.GetTag());
+
+        switch (c.GetType())
+        {
+          case IdentifierConstraintType_Equal: s << " == "; break;
+          case IdentifierConstraintType_SmallerOrEqual: s << " <= "; break;
+          case IdentifierConstraintType_GreaterOrEqual: s << " >= "; break;
+          case IdentifierConstraintType_Wildcard: s << " ~= "; break;
+          default:
+            s << " ? ";
+        }
+
+        s << c.GetValue() << std::endl;
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/LookupIdentifierQuery.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,183 @@
+/**
+ * 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 "../ServerEnumerations.h"
+#include "../IDatabaseWrapper.h"
+
+#include "SetOfResources.h"
+
+#include <vector>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  /**
+   * Primitive for wildcard matching, as defined in DICOM:
+   * http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_C.2.html#sect_C.2.2.2.4
+   * 
+   * "Any occurrence of an "*" or a "?", then "*" shall match any
+   * sequence of characters (including a zero length value) and "?"
+   * shall match any single character. This matching is case
+   * sensitive, except for Attributes with an PN Value
+   * Representation (e.g., Patient Name (0010,0010))."
+   * 
+   * Pay attention to the fact that "*" (resp. "?") generally
+   * corresponds to "%" (resp. "_") in primitive LIKE of SQL. The
+   * values "%", "_", "\" should in the user request should
+   * respectively be escaped as "\%", "\_" and "\\".
+   *
+   * This matching must be case sensitive: The special case of PN VR
+   * is taken into consideration by normalizing the query string in
+   * method "NormalizeIdentifier()".
+   **/
+
+  class LookupIdentifierQuery : public boost::noncopyable
+  {
+  public:
+    class Constraint
+    {
+    private:
+      DicomTag                  tag_;
+      IdentifierConstraintType  type_;
+      std::string               value_;
+
+    public:
+      Constraint(const DicomTag& tag,
+                 IdentifierConstraintType type,
+                 const std::string& value) : 
+        tag_(tag),
+        type_(type),
+        value_(NormalizeIdentifier(value))
+      {
+      }
+
+      const DicomTag& GetTag() const
+      {
+        return tag_;
+      }
+
+      IdentifierConstraintType GetType() const
+      {
+        return type_;
+      }
+      
+      const std::string& GetValue() const
+      {
+        return value_;
+      }
+    };
+
+
+    class Disjunction : public boost::noncopyable
+    {
+    private:
+      std::vector<Constraint*>  disjunction_;
+
+    public:
+      ~Disjunction();
+
+      void Add(const DicomTag& tag,
+               IdentifierConstraintType type,
+               const std::string& value);
+
+      size_t GetSize() const
+      {
+        return disjunction_.size();
+      }
+
+      const Constraint&  GetConstraint(size_t i) const
+      {
+        return *disjunction_[i];
+      }
+    };
+
+
+  private:
+    typedef std::vector<Disjunction*>  Constraints;
+
+    ResourceType  level_;
+    Constraints   constraints_;
+
+  public:
+    LookupIdentifierQuery(ResourceType level) : level_(level)
+    {
+    }
+
+    ~LookupIdentifierQuery();
+
+    bool IsIdentifier(const DicomTag& tag)
+    {
+      return IsIdentifier(tag, level_);
+    }
+
+    void AddConstraint(DicomTag tag,
+                       IdentifierConstraintType type,
+                       const std::string& value);
+
+    Disjunction& AddDisjunction();
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    size_t GetSize() const
+    {
+      return constraints_.size();
+    }
+
+    // The database must be locked
+    void Apply(std::list<std::string>& result,
+               IDatabaseWrapper& database);
+
+    void Apply(SetOfResources& result,
+               IDatabaseWrapper& database);
+
+    static void LoadIdentifiers(const DicomTag*& tags,
+                                size_t& size,
+                                ResourceType level);
+
+    static bool IsIdentifier(const DicomTag& tag,
+                             ResourceType level);
+
+    static void StoreIdentifiers(IDatabaseWrapper& database,
+                                 int64_t resource,
+                                 ResourceType level,
+                                 const DicomMap& map);
+
+    static std::string NormalizeIdentifier(const std::string& value);
+
+    void Print(std::ostream& s) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/LookupResource.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,510 @@
+/**
+ * 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 "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"
+#include "../FromDcmtkBridge.h"
+
+
+namespace Orthanc
+{
+  LookupResource::Level::Level(ResourceType level) : level_(level)
+  {
+    const DicomTag* tags = NULL;
+    size_t size;
+    
+    LookupIdentifierQuery::LoadIdentifiers(tags, size, level);
+    
+    for (size_t i = 0; i < size; i++)
+    {
+      identifiers_.insert(tags[i]);
+    }
+    
+    DicomMap::LoadMainDicomTags(tags, size, level);
+    
+    for (size_t i = 0; i < size; i++)
+    {
+      if (identifiers_.find(tags[i]) == identifiers_.end())
+      {
+        mainTags_.insert(tags[i]);
+      }
+    }    
+  }
+
+  LookupResource::Level::~Level()
+  {
+    for (Constraints::iterator it = mainTagsConstraints_.begin();
+         it != mainTagsConstraints_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    for (Constraints::iterator it = identifiersConstraints_.begin();
+         it != identifiersConstraints_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+  bool LookupResource::Level::Add(const DicomTag& tag,
+                                  std::auto_ptr<IFindConstraint>& constraint)
+  {
+    if (identifiers_.find(tag) != identifiers_.end())
+    {
+      if (level_ == ResourceType_Patient)
+      {
+        // The filters on the patient level must be cloned to the study level
+        identifiersConstraints_[tag] = constraint->Clone();
+      }
+      else
+      {
+        identifiersConstraints_[tag] = constraint.release();
+      }
+
+      return true;
+    }
+    else if (mainTags_.find(tag) != mainTags_.end())
+    {
+      if (level_ == ResourceType_Patient)
+      {
+        // The filters on the patient level must be cloned to the study level
+        mainTagsConstraints_[tag] = constraint->Clone();
+      }
+      else
+      {
+        mainTagsConstraints_[tag] = constraint.release();
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  LookupResource::LookupResource(ResourceType level) : level_(level)
+  {
+    switch (level)
+    {
+      case ResourceType_Patient:
+        levels_[ResourceType_Patient] = new Level(ResourceType_Patient);
+        break;
+
+      case ResourceType_Study:
+        levels_[ResourceType_Study] = new Level(ResourceType_Study);
+        // Do not add "break" here
+
+      case ResourceType_Series:
+        levels_[ResourceType_Series] = new Level(ResourceType_Series);
+        // Do not add "break" here
+
+      case ResourceType_Instance:
+        levels_[ResourceType_Instance] = new Level(ResourceType_Instance);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  LookupResource::~LookupResource()
+  {
+    for (Levels::iterator it = levels_.begin();
+         it != levels_.end(); ++it)
+    {
+      delete it->second;
+    }
+
+    for (Constraints::iterator it = unoptimizedConstraints_.begin();
+         it != unoptimizedConstraints_.end(); ++it)
+    {
+      delete it->second;
+    }    
+  }
+
+
+
+  bool LookupResource::AddInternal(ResourceType level,
+                                   const DicomTag& tag,
+                                   std::auto_ptr<IFindConstraint>& constraint)
+  {
+    Levels::iterator it = levels_.find(level);
+    if (it != levels_.end())
+    {
+      if (it->second->Add(tag, constraint))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  void LookupResource::Add(const DicomTag& tag,
+                           IFindConstraint* constraint)
+  {
+    std::auto_ptr<IFindConstraint> c(constraint);
+
+    if (!AddInternal(ResourceType_Patient, tag, c) &&
+        !AddInternal(ResourceType_Study, tag, c) &&
+        !AddInternal(ResourceType_Series, tag, c) &&
+        !AddInternal(ResourceType_Instance, tag, c))
+    {
+      unoptimizedConstraints_[tag] = c.release();
+    }
+  }
+
+
+  static bool Match(const DicomMap& tags,
+                    const DicomTag& tag,
+                    const IFindConstraint& constraint)
+  {
+    const DicomValue* value = tags.TestAndGetValue(tag);
+
+    if (value == NULL ||
+        value->IsNull() ||
+        value->IsBinary())
+    {
+      return false;
+    }
+    else
+    {
+      return constraint.Match(value->GetContent());
+    }
+  }
+
+
+  void LookupResource::Level::Apply(SetOfResources& candidates,
+                                    IDatabaseWrapper& database) const
+  {
+    // First, use the indexed identifiers
+    LookupIdentifierQuery query(level_);
+
+    for (Constraints::const_iterator it = identifiersConstraints_.begin(); 
+         it != identifiersConstraints_.end(); ++it)
+    {
+      it->second->Setup(query, it->first);
+    }
+
+    query.Apply(candidates, database);
+
+    /*{
+      query.Print(std::cout);
+      std::list<int64_t>  source;
+      candidates.Flatten(source);
+      printf("=> %d\n", source.size());
+      }*/
+
+    // Secondly, filter using the main DICOM tags
+    if (!identifiersConstraints_.empty() ||
+        !mainTagsConstraints_.empty())
+    {
+      std::list<int64_t>  source;
+      candidates.Flatten(source);
+      candidates.Clear();
+
+      std::list<int64_t>  filtered;
+      for (std::list<int64_t>::const_iterator candidate = source.begin(); 
+           candidate != source.end(); ++candidate)
+      {
+        DicomMap tags;
+        database.GetMainDicomTags(tags, *candidate);
+
+        bool match = true;
+
+        // Re-apply the identifier constraints, as their "Setup"
+        // method is less restrictive than their "Match" method
+        for (Constraints::const_iterator it = identifiersConstraints_.begin(); 
+             match && it != identifiersConstraints_.end(); ++it)
+        {
+          if (!Match(tags, it->first, *it->second))
+          {
+            match = false;
+          }
+        }
+
+        for (Constraints::const_iterator it = mainTagsConstraints_.begin(); 
+             match && it != mainTagsConstraints_.end(); ++it)
+        {
+          if (!Match(tags, it->first, *it->second))
+          {
+            match = false;
+          }
+        }
+
+        if (match)
+        {
+          filtered.push_back(*candidate);
+        }
+      }
+      
+      candidates.Intersect(filtered);
+    }
+  }
+
+
+
+  bool LookupResource::IsMatch(const Json::Value& dicomAsJson) const
+  {
+    for (Constraints::const_iterator it = unoptimizedConstraints_.begin(); 
+         it != unoptimizedConstraints_.end(); ++it)
+    {
+      std::string tag = it->first.Format();
+      if (dicomAsJson.isMember(tag) &&
+          dicomAsJson[tag]["Type"] == "String")
+      {
+        std::string value = dicomAsJson[tag]["Value"].asString();
+        if (!it->second->Match(value))
+        {
+          return false;
+        }
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  void LookupResource::ApplyLevel(SetOfResources& candidates,
+                                  ResourceType level,
+                                  IDatabaseWrapper& database) const
+  {
+    Levels::const_iterator it = levels_.find(level);
+    if (it != levels_.end())
+    {
+      it->second->Apply(candidates, database);
+    }
+
+    if (level == ResourceType_Study &&
+        modalitiesInStudy_.get() != NULL)
+    {
+      // There is a constraint on the "ModalitiesInStudy" DICOM
+      // extension. Check out whether one child series has one of the
+      // allowed modalities
+      std::list<int64_t> allStudies, matchingStudies;
+      candidates.Flatten(allStudies);
+ 
+      for (std::list<int64_t>::const_iterator
+             study = allStudies.begin(); study != allStudies.end(); ++study)
+      {
+        std::list<int64_t> childrenSeries;
+        database.GetChildrenInternalId(childrenSeries, *study);
+
+        for (std::list<int64_t>::const_iterator
+               series = childrenSeries.begin(); series != childrenSeries.end(); ++series)
+        {
+          DicomMap tags;
+          database.GetMainDicomTags(tags, *series);
+
+          const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
+          if (value != NULL &&
+              !value->IsNull() &&
+              !value->IsBinary())
+          {
+            if (modalitiesInStudy_->Match(value->GetContent()))
+            {
+              matchingStudies.push_back(*study);
+              break;
+            }
+          }
+        }
+      }
+
+      candidates.Intersect(matchingStudies);
+    }
+  }
+
+
+  void LookupResource::FindCandidates(std::list<int64_t>& result,
+                                      IDatabaseWrapper& database) const
+  {
+    ResourceType startingLevel;
+    if (level_ == ResourceType_Patient)
+    {
+      startingLevel = ResourceType_Patient;
+    }
+    else
+    {
+      startingLevel = ResourceType_Study;
+    }
+
+    SetOfResources candidates(database, startingLevel);
+
+    switch (level_)
+    {
+      case ResourceType_Patient:
+        ApplyLevel(candidates, ResourceType_Patient, database);
+        break;
+
+      case ResourceType_Study:
+        ApplyLevel(candidates, ResourceType_Study, database);
+        break;
+
+      case ResourceType_Series:
+        ApplyLevel(candidates, ResourceType_Study, database);
+        candidates.GoDown();
+        ApplyLevel(candidates, ResourceType_Series, database);
+        break;
+
+      case ResourceType_Instance:
+        ApplyLevel(candidates, ResourceType_Study, database);
+        candidates.GoDown();
+        ApplyLevel(candidates, ResourceType_Series, database);
+        candidates.GoDown();
+        ApplyLevel(candidates, ResourceType_Instance, database);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    candidates.Flatten(result);
+  }
+
+
+  void LookupResource::SetModalitiesInStudy(const std::string& modalities)
+  {
+    modalitiesInStudy_.reset(new ListConstraint(true /* case sensitive */));
+    
+    std::vector<std::string> items;
+    Toolbox::TokenizeString(items, modalities, '\\');
+    
+    for (size_t i = 0; i < items.size(); i++)
+    {
+      modalitiesInStudy_->AddAllowedValue(items[i]);
+    }
+  }
+
+
+  void LookupResource::AddDicomConstraint(const DicomTag& tag,
+                                          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)
+    {
+      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));
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/LookupResource.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,107 @@
+/**
+ * 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 "ListConstraint.h"
+#include "SetOfResources.h"
+
+#include <memory>
+
+namespace Orthanc
+{
+  class LookupResource : public boost::noncopyable
+  {
+  private:
+    typedef std::map<DicomTag, IFindConstraint*>  Constraints;
+    
+    class Level
+    {
+    private:
+      ResourceType        level_;
+      std::set<DicomTag>  identifiers_;
+      std::set<DicomTag>  mainTags_;
+      Constraints         identifiersConstraints_;
+      Constraints         mainTagsConstraints_;
+
+    public:
+      Level(ResourceType level);
+
+      ~Level();
+
+      bool Add(const DicomTag& tag,
+               std::auto_ptr<IFindConstraint>& constraint);
+
+      void Apply(SetOfResources& candidates,
+                 IDatabaseWrapper& database) const;
+    };
+
+    typedef std::map<ResourceType, Level*>  Levels;
+
+    ResourceType                    level_;
+    Levels                          levels_;
+    Constraints                     unoptimizedConstraints_; 
+    std::auto_ptr<ListConstraint>   modalitiesInStudy_;
+
+    bool AddInternal(ResourceType level,
+                     const DicomTag& tag,
+                     std::auto_ptr<IFindConstraint>& constraint);
+
+    void ApplyLevel(SetOfResources& candidates,
+                    ResourceType level,
+                    IDatabaseWrapper& database) const;
+
+  public:
+    LookupResource(ResourceType level);
+
+    ~LookupResource();
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    void SetModalitiesInStudy(const std::string& modalities); 
+
+    void Add(const DicomTag& tag,
+             IFindConstraint* constraint);   // Takes ownership
+
+    void AddDicomConstraint(const DicomTag& tag,
+                            const std::string& dicomQuery,
+                            bool caseSensitive);
+
+    void FindCandidates(std::list<int64_t>& result,
+                        IDatabaseWrapper& database) const;
+
+    bool IsMatch(const Json::Value& dicomAsJson) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/RangeConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,90 @@
+/**
+ * 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 "RangeConstraint.h"
+
+#include "../../Core/Toolbox.h"
+
+namespace Orthanc
+{
+  RangeConstraint::RangeConstraint(const std::string& lower,
+                                   const std::string& upper,
+                                   bool isCaseSensitive) : 
+    lower_(lower),
+    upper_(upper),
+    isCaseSensitive_(isCaseSensitive)
+  {
+    if (!isCaseSensitive_)
+    {
+      Toolbox::ToUpperCase(lower_);
+      Toolbox::ToUpperCase(upper_);
+    }
+  }
+
+
+  void RangeConstraint::Setup(LookupIdentifierQuery& lookup,
+                              const DicomTag& tag) const
+  {
+    lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_);
+    lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_);
+  }
+
+
+  bool RangeConstraint::Match(const std::string& value) const
+  {
+    std::string v = value;
+
+    if (!isCaseSensitive_)
+    {
+      Toolbox::ToUpperCase(v);
+    }
+
+    if (lower_.size() == 0 && 
+        upper_.size() == 0)
+    {
+      return false;
+    }
+
+    if (lower_.size() == 0)
+    {
+      return v <= upper_;
+    }
+
+    if (upper_.size() == 0)
+    {
+      return v >= lower_;
+    }
+    
+    return (v >= lower_ && v <= upper_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/RangeConstraint.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,68 @@
+/**
+ * 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 "IFindConstraint.h"
+
+namespace Orthanc
+{
+  class RangeConstraint : public IFindConstraint
+  {
+  private:
+    std::string  lower_;
+    std::string  upper_;
+    bool         isCaseSensitive_;
+
+    RangeConstraint(const RangeConstraint& other) : 
+      lower_(other.lower_),
+      upper_(other.upper_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
+  public:
+    RangeConstraint(const std::string& lower,
+                    const std::string& upper,
+                    bool isCaseSensitive);
+
+    virtual IFindConstraint* Clone() const
+    {
+      return new RangeConstraint(*this);
+    }
+
+    virtual void Setup(LookupIdentifierQuery& lookup,
+                       const DicomTag& tag) const;
+
+    virtual bool Match(const std::string& value) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/SetOfResources.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,156 @@
+/**
+ * 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 "SetOfResources.h"
+
+#include "../../Core/OrthancException.h"
+
+
+namespace Orthanc
+{
+  void SetOfResources::Intersect(const std::list<int64_t>& resources)
+  {
+    if (resources_.get() == NULL)
+    {
+      resources_.reset(new Resources);
+
+      for (std::list<int64_t>::const_iterator
+             it = resources.begin(); it != resources.end(); ++it)
+      {
+        resources_->insert(*it);
+      }
+    }
+    else
+    {
+      std::auto_ptr<Resources> filtered(new Resources);
+
+      for (std::list<int64_t>::const_iterator
+             it = resources.begin(); it != resources.end(); ++it)
+      {
+        if (resources_->find(*it) != resources_->end())
+        {
+          filtered->insert(*it);
+        }
+      }
+
+      resources_ = filtered;
+    }
+  }
+
+
+  void SetOfResources::GoDown()
+  {
+    if (level_ == ResourceType_Instance)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (resources_.get() != NULL)
+    {
+      std::auto_ptr<Resources> children(new Resources);
+
+      for (Resources::const_iterator it = resources_->begin(); 
+           it != resources_->end(); ++it)
+      {
+        std::list<int64_t> tmp;
+        database_.GetChildrenInternalId(tmp, *it);
+
+        for (std::list<int64_t>::const_iterator
+               child = tmp.begin(); child != tmp.end(); ++child)
+        {
+          children->insert(*child);
+        }
+      }
+
+      resources_ = children;
+    }
+
+    switch (level_)
+    {
+      case ResourceType_Patient:
+        level_ = ResourceType_Study;
+        break;
+
+      case ResourceType_Study:
+        level_ = ResourceType_Series;
+        break;
+
+      case ResourceType_Series:
+        level_ = ResourceType_Instance;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  void SetOfResources::Flatten(std::list<std::string>& result)
+  {
+    result.clear();
+      
+    if (resources_.get() == NULL)
+    {
+      // All the resources of this level are part of the filter
+      database_.GetAllPublicIds(result, level_);
+    }
+    else
+    {
+      for (Resources::const_iterator it = resources_->begin(); 
+           it != resources_->end(); ++it)
+      {
+        result.push_back(database_.GetPublicId(*it));
+      }
+    }
+  }
+
+
+  void SetOfResources::Flatten(std::list<int64_t>& result)
+  {
+    result.clear();
+      
+    if (resources_.get() == NULL)
+    {
+      // All the resources of this level are part of the filter
+      database_.GetAllInternalIds(result, level_);
+    }
+    else
+    {
+      for (Resources::const_iterator it = resources_->begin(); 
+           it != resources_->end(); ++it)
+      {
+        result.push_back(*it);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/SetOfResources.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,78 @@
+/**
+ * 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 "../IDatabaseWrapper.h"
+
+#include <set>
+#include <boost/noncopyable.hpp>
+#include <memory>
+
+namespace Orthanc
+{
+  class SetOfResources : public boost::noncopyable
+  {
+  private:
+    typedef std::set<int64_t>  Resources;
+
+    IDatabaseWrapper&         database_;
+    ResourceType              level_;
+    std::auto_ptr<Resources>  resources_;
+    
+  public:
+    SetOfResources(IDatabaseWrapper& database,
+                   ResourceType level) : 
+      database_(database),
+      level_(level)
+    {
+    }
+
+    ResourceType GetLevel() const
+    {
+      return level_;
+    }
+
+    void Intersect(const std::list<int64_t>& resources);
+
+    void GoDown();
+
+    void Flatten(std::list<int64_t>& result);
+
+    void Flatten(std::list<std::string>& result);
+
+    void Clear()
+    {
+      resources_.reset(NULL);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/ValueConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,73 @@
+/**
+ * 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 "ValueConstraint.h"
+
+#include "../../Core/Toolbox.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  ValueConstraint::ValueConstraint(const std::string& value,
+                                   bool isCaseSensitive) : 
+    value_(value),
+    isCaseSensitive_(isCaseSensitive)
+  {
+    if (!isCaseSensitive)
+    {
+      Toolbox::ToUpperCase(value_);
+    }
+  }
+
+
+  void ValueConstraint::Setup(LookupIdentifierQuery& lookup,
+                              const DicomTag& tag) const
+  {
+    lookup.AddConstraint(tag, IdentifierConstraintType_Equal, value_);
+  }
+
+  bool ValueConstraint::Match(const std::string& value) const
+  {
+    if (isCaseSensitive_)
+    {
+      return value_ == value;
+    }
+    else
+    {
+      std::string v;
+      Toolbox::ToUpperCase(v, value);
+      return value_ == v;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/ValueConstraint.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,65 @@
+/**
+ * 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 "IFindConstraint.h"
+
+namespace Orthanc
+{
+  class ValueConstraint : public IFindConstraint
+  {
+  private:
+    std::string  value_;
+    bool         isCaseSensitive_;
+
+    ValueConstraint(const ValueConstraint& other) : 
+      value_(other.value_),
+      isCaseSensitive_(other.isCaseSensitive_)
+    {
+    }
+
+  public:
+    ValueConstraint(const std::string& value,
+                    bool isCaseSensitive);
+
+    virtual IFindConstraint* Clone() const
+    {
+      return new ValueConstraint(*this);
+    }
+
+    virtual void Setup(LookupIdentifierQuery& lookup,
+                       const DicomTag& tag) const;
+
+    virtual bool Match(const std::string& value) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/WildcardConstraint.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,81 @@
+/**
+ * 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 "WildcardConstraint.h"
+
+#include <boost/regex.hpp>
+
+namespace Orthanc
+{
+  struct WildcardConstraint::PImpl
+  {
+    boost::regex  pattern_;
+    std::string   wildcard_;
+  };
+
+
+  WildcardConstraint::WildcardConstraint(const WildcardConstraint& other) :
+    pimpl_(new PImpl(*other.pimpl_))
+  {
+  }
+
+
+  WildcardConstraint::WildcardConstraint(const std::string& wildcard,
+                                         bool isCaseSensitive) :
+    pimpl_(new PImpl)
+  {
+    pimpl_->wildcard_ = wildcard;
+
+    std::string re = Toolbox::WildcardToRegularExpression(wildcard);
+
+    if (isCaseSensitive)
+    {
+      pimpl_->pattern_ = boost::regex(re);
+    }
+    else
+    {
+      pimpl_->pattern_ = boost::regex(re, boost::regex::icase /* case insensitive search */);
+    }
+  }
+
+  bool WildcardConstraint::Match(const std::string& value) const
+  {
+    return boost::regex_match(value, pimpl_->pattern_);
+  }
+
+  void WildcardConstraint::Setup(LookupIdentifierQuery& lookup,
+                                 const DicomTag& tag) const
+  {
+    lookup.AddConstraint(tag, IdentifierConstraintType_Wildcard, pimpl_->wildcard_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Search/WildcardConstraint.h	Wed Nov 18 10:16:21 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 "IFindConstraint.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class WildcardConstraint : public IFindConstraint
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl>  pimpl_;
+
+    WildcardConstraint(const WildcardConstraint& other);
+
+  public:
+    WildcardConstraint(const std::string& wildcard,
+                       bool isCaseSensitive);
+
+    virtual IFindConstraint* Clone() const
+    {
+      return new WildcardConstraint(*this);
+    }
+
+    virtual void Setup(LookupIdentifierQuery& lookup,
+                       const DicomTag& tag) const;
+
+    virtual bool Match(const std::string& value) const;
+  };
+}
--- a/OrthancServer/ServerContext.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -189,7 +189,7 @@
       resultPublicId = hasher.HashInstance();
 
       Json::Value simplifiedTags;
-      SimplifyTags(simplifiedTags, dicom.GetJson());
+      Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson());
 
       // Test if the instance must be filtered out
       bool accepted = true;
@@ -299,7 +299,7 @@
     {
       if (e.GetErrorCode() == ErrorCode_InexistentTag)
       {
-        LogMissingRequiredTag(dicom.GetSummary());
+        Toolbox::LogMissingRequiredTag(dicom.GetSummary());
       }
 
       throw;
@@ -307,19 +307,64 @@
   }
 
 
-
   void ServerContext::AnswerAttachment(RestApiOutput& output,
-                                       const std::string& instancePublicId,
+                                       const std::string& resourceId,
                                        FileContentType content)
   {
     FileInfo attachment;
-    if (!index_.LookupAttachment(attachment, instancePublicId, content))
+    if (!index_.LookupAttachment(attachment, resourceId, content))
     {
-      throw OrthancException(ErrorCode_InternalError);
+      throw OrthancException(ErrorCode_UnknownResource);
     }
 
     StorageAccessor accessor(area_);
-    accessor.AnswerFile(output, attachment);
+    accessor.AnswerFile(output, attachment, GetFileContentMime(content));
+  }
+
+
+  void ServerContext::ChangeAttachmentCompression(const std::string& resourceId,
+                                                  FileContentType attachmentType,
+                                                  CompressionType compression)
+  {
+    LOG(INFO) << "Changing compression type for attachment "
+              << EnumerationToString(attachmentType) 
+              << " of resource " << resourceId << " to " 
+              << compression; 
+
+    FileInfo attachment;
+    if (!index_.LookupAttachment(attachment, resourceId, attachmentType))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    if (attachment.GetCompressionType() == compression)
+    {
+      // Nothing to do
+      return;
+    }
+
+    std::string content;
+
+    StorageAccessor accessor(area_);
+    accessor.Read(content, attachment);
+
+    FileInfo modified = accessor.Write(content.empty() ? NULL : content.c_str(),
+                                       content.size(), attachmentType, compression, storeMD5_);
+
+    try
+    {
+      StoreStatus status = index_.AddAttachment(modified, resourceId);
+      if (status != StoreStatus_Success)
+      {
+        accessor.Remove(modified);
+        throw OrthancException(ErrorCode_Database);
+      }
+    }
+    catch (OrthancException&)
+    {
+      accessor.Remove(modified);
+      throw;
+    }    
   }
 
 
@@ -362,6 +407,14 @@
   }
 
 
+  void ServerContext::ReadFile(std::string& result,
+                               const FileInfo& file)
+  {
+    StorageAccessor accessor(area_);
+    accessor.Read(result, file);
+  }
+
+
   IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId)
   {
     std::string content;
@@ -475,6 +528,18 @@
     }
   }
 
+  OrthancPlugins& ServerContext::GetPlugins()
+  {
+    if (HasPlugins())
+    {
+      return *plugins_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
 #endif
 
 
@@ -486,4 +551,39 @@
     return false;
 #endif
   }
+
+
+  bool ServerContext::Apply(std::list<std::string>& result,
+                            const ::Orthanc::LookupResource& lookup,
+                            size_t maxResults)
+  {
+    result.clear();
+
+    std::vector<std::string> resources, instances;
+    GetIndex().FindCandidates(resources, instances, lookup);
+
+    assert(resources.size() == instances.size());
+
+    for (size_t i = 0; i < instances.size(); i++)
+    {
+      Json::Value dicom;
+      ReadJson(dicom, instances[i]);
+      
+      if (lookup.IsMatch(dicom))
+      {
+        if (maxResults != 0 &&
+            result.size() >= maxResults)
+        {
+          return false;  // too many results
+        }
+        else
+        {
+          result.push_back(resources[i]);
+        }
+      }
+    }
+
+    return true;  // finished
+  }
+
 }
--- a/OrthancServer/ServerContext.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerContext.h	Wed Nov 18 10:16:21 2015 +0100
@@ -47,6 +47,7 @@
 #include "Scheduler/ServerScheduler.h"
 #include "ServerIndex.h"
 #include "OrthancHttpHandler.h"
+#include "Search/LookupResource.h"
 
 #include <boost/filesystem.hpp>
 #include <boost/thread.hpp>
@@ -184,9 +185,13 @@
                       DicomInstanceToStore& dicom);
 
     void AnswerAttachment(RestApiOutput& output,
-                          const std::string& instancePublicId,
+                          const std::string& resourceId,
                           FileContentType content);
 
+    void ChangeAttachmentCompression(const std::string& resourceId,
+                                     FileContentType attachmentType,
+                                     CompressionType compression);
+
     void ReadJson(Json::Value& result,
                   const std::string& instancePublicId);
 
@@ -196,6 +201,9 @@
                   FileContentType content,
                   bool uncompressIfNeeded = true);
 
+    void ReadFile(std::string& result,
+                  const FileInfo& file);
+
     void SetStoreMD5ForAttachments(bool storeMD5);
 
     bool IsStoreMD5ForAttachments() const
@@ -241,6 +249,10 @@
 
     void Stop();
 
+    bool Apply(std::list<std::string>& result,
+               const ::Orthanc::LookupResource& lookup,
+               size_t maxResults);
+
 
     /**
      * Management of the plugins
@@ -252,9 +264,10 @@
     void ResetPlugins();
 
     const OrthancPlugins& GetPlugins() const;
+
+    OrthancPlugins& GetPlugins();
 #endif
 
     bool HasPlugins() const;
-
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -35,15 +35,19 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/EnumerationDictionary.h"
+#include "../Core/Logging.h"
 #include "../Core/Toolbox.h"
 
 #include <boost/thread.hpp>
 
 namespace Orthanc
 {
+  typedef std::map<FileContentType, std::string>  MimeTypes;
+
   static boost::mutex enumerationsMutex_;
   static Toolbox::EnumerationDictionary<MetadataType> dictMetadataType_;
   static Toolbox::EnumerationDictionary<FileContentType> dictContentType_;
+  static MimeTypes  mimeTypes_;
 
   void InitializeServerEnumerations()
   {
@@ -69,13 +73,29 @@
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
 
-    if (metadata < static_cast<int>(MetadataType_StartUser) ||
-        metadata > static_cast<int>(MetadataType_EndUser))
+    MetadataType type = static_cast<MetadataType>(metadata);
+
+    if (metadata < 0 || 
+        !IsUserMetadata(type))
     {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(MetadataType_StartUser) << " and "
+                 << static_cast<int>(MetadataType_EndUser) << ", but \""
+                 << name << "\" has index " << metadata;
+        
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    dictMetadataType_.Add(static_cast<MetadataType>(metadata), name);
+    if (dictMetadataType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << metadata 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictMetadataType_.Add(type, name);
   }
 
   std::string EnumerationToString(MetadataType type)
@@ -93,17 +113,35 @@
   }
 
   void RegisterUserContentType(int contentType,
-                               const std::string& name)
+                               const std::string& name,
+                               const std::string& mime)
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
 
-    if (contentType < static_cast<int>(FileContentType_StartUser) ||
-        contentType > static_cast<int>(FileContentType_EndUser))
+    FileContentType type = static_cast<FileContentType>(contentType);
+
+    if (contentType < 0 || 
+        !IsUserContentType(type))
     {
+      LOG(ERROR) << "A user content type must have index between "
+                 << static_cast<int>(FileContentType_StartUser) << " and "
+                 << static_cast<int>(FileContentType_EndUser) << ", but \""
+                 << name << "\" has index " << contentType;
+        
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    dictContentType_.Add(static_cast<FileContentType>(contentType), name);
+    if (dictContentType_.Contains(type))
+    {
+      LOG(ERROR) << "Cannot associate user content type \""
+                 << name << "\" with index " << contentType 
+                 << ", as this index is already used";
+        
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    dictContentType_.Add(type, name);
+    mimeTypes_[type] = mime;
   }
 
   std::string EnumerationToString(FileContentType type)
@@ -114,6 +152,33 @@
     return dictContentType_.Translate(type);
   }
 
+  std::string GetFileContentMime(FileContentType type)
+  {
+    if (type >= FileContentType_StartUser &&
+        type <= FileContentType_EndUser)
+    {
+      boost::mutex::scoped_lock lock(enumerationsMutex_);
+      
+      MimeTypes::const_iterator it = mimeTypes_.find(type);
+      if (it != mimeTypes_.end())
+      {
+        return it->second;
+      }
+    }
+
+    switch (type)
+    {
+      case FileContentType_Dicom:
+        return "application/dicom";
+
+      case FileContentType_DicomAsJson:
+        return "application/json";
+
+      default:
+        return "application/octet-stream";
+    }
+  }
+
   FileContentType StringToContentType(const std::string& str)
   {
     boost::mutex::scoped_lock lock(enumerationsMutex_);
@@ -237,6 +302,12 @@
       case ChangeType_NewChildInstance:
         return "NewChildInstance";
 
+      case ChangeType_UpdatedAttachment:
+        return "UpdatedAttachment";
+
+      case ChangeType_UpdatedMetadata:
+        return "UpdatedMetadata";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -365,4 +436,10 @@
     }
   }
 
+
+  bool IsUserMetadata(MetadataType metadata)
+  {
+    return (metadata >= MetadataType_StartUser &&
+            metadata <= MetadataType_EndUser);
+  }
 }
--- a/OrthancServer/ServerEnumerations.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerEnumerations.h	Wed Nov 18 10:16:21 2015 +0100
@@ -35,6 +35,7 @@
 #include <map>
 
 #include "../Core/Enumerations.h"
+#include "../Core/DicomFormat/DicomTag.h"
 
 namespace Orthanc
 {
@@ -100,6 +101,38 @@
     ValueRepresentation_Time
   };
 
+  enum DicomToJsonFormat
+  {
+    DicomToJsonFormat_Full,
+    DicomToJsonFormat_Short,
+    DicomToJsonFormat_Simple
+  };
+
+  enum DicomToJsonFlags
+  {
+    DicomToJsonFlags_IncludeBinary         = (1 << 0),
+    DicomToJsonFlags_IncludePrivateTags    = (1 << 1),
+    DicomToJsonFlags_IncludeUnknownTags    = (1 << 2),
+    DicomToJsonFlags_IncludePixelData      = (1 << 3),
+    DicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),
+    DicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),
+
+    // Some predefined combinations
+    DicomToJsonFlags_None     = 0,
+    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludePrivateTags | 
+                                 DicomToJsonFlags_IncludeUnknownTags | 
+                                 DicomToJsonFlags_IncludePixelData | 
+                                 DicomToJsonFlags_ConvertBinaryToNull)
+  };
+
+  enum IdentifierConstraintType
+  {
+    IdentifierConstraintType_Equal,
+    IdentifierConstraintType_SmallerOrEqual,
+    IdentifierConstraintType_GreaterOrEqual,
+    IdentifierConstraintType_Wildcard        /* Case sensitive, "*" or "?" are the only allowed wildcards */
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -109,7 +142,7 @@
 
   enum GlobalProperty
   {
-    GlobalProperty_DatabaseSchemaVersion = 1,
+    GlobalProperty_DatabaseSchemaVersion = 1,   // Unused in the Orthanc core as of Orthanc 0.9.5
     GlobalProperty_FlushSleep = 2,
     GlobalProperty_AnonymizationSequence = 3
   };
@@ -145,6 +178,8 @@
     ChangeType_StablePatient = 12,
     ChangeType_StableStudy = 13,
     ChangeType_StableSeries = 14,
+    ChangeType_UpdatedAttachment = 15,
+    ChangeType_UpdatedMetadata = 16,
 
     ChangeType_INTERNAL_LastLogged = 4095,
 
@@ -165,12 +200,15 @@
   std::string EnumerationToString(MetadataType type);
 
   void RegisterUserContentType(int contentType,
-                               const std::string& name);
+                               const std::string& name,
+                               const std::string& mime);
 
   FileContentType StringToContentType(const std::string& str);
 
   std::string EnumerationToString(FileContentType type);
 
+  std::string GetFileContentMime(FileContentType type);
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId);
 
@@ -187,4 +225,6 @@
   const char* EnumerationToString(TransferSyntax syntax);
 
   ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
+
+  bool IsUserMetadata(MetadataType type);
 }
--- a/OrthancServer/ServerIndex.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerIndex.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -40,10 +40,13 @@
 #include "ServerIndexChange.h"
 #include "EmbeddedResources.h"
 #include "OrthancInitialization.h"
+#include "ServerToolbox.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Logging.h"
 #include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomArray.h"
+#include "Search/LookupIdentifierQuery.h"
+#include "Search/LookupResource.h"
 
 #include "FromDcmtkBridge.h"
 #include "ServerContext.h"
@@ -393,8 +396,8 @@
           (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL)
       {
         // Patch for series with temporal positions thanks to Will Ryder
-        int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->AsString());
-        int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->AsString());
+        int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent());
+        int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent());
         std::string expected = boost::lexical_cast<std::string>(imagesInAcquisition * countTemporalPositions);
         db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected);
       }
@@ -403,18 +406,21 @@
                (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL)
       {
         // Support of Cardio-PET images
-        int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->AsString());
-        int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->AsString());
+        int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent());
+        int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent());
         std::string expected = boost::lexical_cast<std::string>(numberOfSlices * numberOfTimeSlices);
         db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, expected);
       }
 
       else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL)
       {
-        db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->AsString());
+        db.SetMetadata(series, MetadataType_Series_ExpectedNumberOfInstances, value->GetContent());
       }
     }
-    catch (boost::bad_lexical_cast)
+    catch (OrthancException&)
+    {
+    }
+    catch (boost::bad_lexical_cast&)
     {
     }
   }
@@ -490,18 +496,6 @@
 
 
 
-  void ServerIndex::SetMainDicomTags(int64_t resource,
-                                     const DicomMap& tags)
-  {
-    DicomArray flattened(tags);
-    for (size_t i = 0; i < flattened.GetSize(); i++)
-    {
-      const DicomElement& element = flattened.GetElement(i);
-      db_.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
-    }
-  }
-
-
   int64_t ServerIndex::CreateResource(const std::string& publicId,
                                       ResourceType type)
   {
@@ -638,10 +632,7 @@
 
       // Create the instance
       int64_t instance = CreateResource(hasher.HashInstance(), ResourceType_Instance);
-
-      DicomMap dicom;
-      dicomSummary.ExtractInstanceInformation(dicom);
-      SetMainDicomTags(instance, dicom);
+      Toolbox::SetMainDicomTags(db_, instance, ResourceType_Instance, dicomSummary);
 
       // Detect up to which level the patient/study/series/instance
       // hierarchy must be created
@@ -693,24 +684,21 @@
       if (isNewSeries)
       {
         series = CreateResource(hasher.HashSeries(), ResourceType_Series);
-        dicomSummary.ExtractSeriesInformation(dicom);
-        SetMainDicomTags(series, dicom);
+        Toolbox::SetMainDicomTags(db_, series, ResourceType_Series, dicomSummary);
       }
 
       // Create the study if needed
       if (isNewStudy)
       {
         study = CreateResource(hasher.HashStudy(), ResourceType_Study);
-        dicomSummary.ExtractStudyInformation(dicom);
-        SetMainDicomTags(study, dicom);
+        Toolbox::SetMainDicomTags(db_, study, ResourceType_Study, dicomSummary);
       }
 
       // Create the patient if needed
       if (isNewPatient)
       {
         patient = CreateResource(hasher.HashPatient(), ResourceType_Patient);
-        dicomSummary.ExtractPatientInformation(dicom);
-        SetMainDicomTags(patient, dicom);
+        Toolbox::SetMainDicomTags(db_, patient, ResourceType_Patient, dicomSummary);
       }
 
       // Create the parent-to-child links
@@ -785,8 +773,12 @@
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
           (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
-        db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
-        instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString();
+        if (!value->IsNull() && 
+            !value->IsBinary())
+        {
+          db_.SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->GetContent());
+          instanceMetadata[MetadataType_Instance_IndexInSeries] = value->GetContent();
+        }
       }
 
       // Check whether the series of this new instance is now completed
@@ -890,14 +882,30 @@
   }
 
 
-
   void ServerIndex::MainDicomTagsToJson(Json::Value& target,
-                                        int64_t resourceId)
+                                        int64_t resourceId,
+                                        ResourceType resourceType)
   {
     DicomMap tags;
     db_.GetMainDicomTags(tags, resourceId);
-    target["MainDicomTags"] = Json::objectValue;
-    FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true);
+
+    if (resourceType == ResourceType_Study)
+    {
+      DicomMap t1, t2;
+      tags.ExtractStudyInformation(t1);
+      tags.ExtractPatientInformation(t2);
+
+      target["MainDicomTags"] = Json::objectValue;
+      FromDcmtkBridge::ToJson(target["MainDicomTags"], t1, true);
+
+      target["PatientMainDicomTags"] = Json::objectValue;
+      FromDcmtkBridge::ToJson(target["PatientMainDicomTags"], t2, true);
+    }
+    else
+    {
+      target["MainDicomTags"] = Json::objectValue;
+      FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true);
+    }
   }
 
   bool ServerIndex::LookupResource(Json::Value& result,
@@ -1033,7 +1041,7 @@
 
     // Record the remaining information
     result["ID"] = publicId;
-    MainDicomTagsToJson(result, id);
+    MainDicomTagsToJson(result, id, type);
 
     std::string tmp;
 
@@ -1198,22 +1206,22 @@
       switch (currentType)
       {
         case ResourceType_Patient:
-          patientId = map.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+          patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent();
           done = true;
           break;
 
         case ResourceType_Study:
-          studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+          studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent();
           currentType = ResourceType_Patient;
           break;
 
         case ResourceType_Series:
-          seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
+          seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent();
           currentType = ResourceType_Study;
           break;
 
         case ResourceType_Instance:
-          sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
+          sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent();
           currentType = ResourceType_Series;
           break;
 
@@ -1517,6 +1525,7 @@
                                 const std::string& value)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction t(*this);
 
     ResourceType rtype;
     int64_t id;
@@ -1526,6 +1535,13 @@
     }
 
     db_.SetMetadata(id, type, value);
+
+    if (IsUserMetadata(type))
+    {
+      LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId);
+    }
+
+    t.Commit(0);
   }
 
 
@@ -1533,6 +1549,7 @@
                                    MetadataType type)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction t(*this);
 
     ResourceType rtype;
     int64_t id;
@@ -1542,6 +1559,13 @@
     }
 
     db_.DeleteMetadata(id, type);
+
+    if (IsUserMetadata(type))
+    {
+      LogChange(id, ChangeType_UpdatedMetadata, rtype, publicId);
+    }
+
+    t.Commit(0);
   }
 
 
@@ -1889,64 +1913,24 @@
 
 
 
-  void ServerIndex::LookupIdentifier(std::list<std::string>& result,
-                                     const DicomTag& tag,
-                                     const std::string& value,
-                                     ResourceType type)
+  void ServerIndex::LookupIdentifierExact(std::list<std::string>& result,
+                                          ResourceType level,
+                                          const DicomTag& tag,
+                                          const std::string& value)
   {
+    assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) ||
+           (level == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) ||
+           (level == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) ||
+           (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) ||
+           (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID));
+    
     result.clear();
 
     boost::mutex::scoped_lock lock(mutex_);
 
-    std::list<int64_t> id;
-    db_.LookupIdentifier(id, tag, value);
-
-    for (std::list<int64_t>::const_iterator 
-           it = id.begin(); it != id.end(); ++it)
-    {
-      if (db_.GetResourceType(*it) == type)
-      {
-        result.push_back(db_.GetPublicId(*it));
-      }
-    }
-  }
-
-
-  void ServerIndex::LookupIdentifier(std::list<std::string>& result,
-                                     const DicomTag& tag,
-                                     const std::string& value)
-  {
-    result.clear();
-
-    boost::mutex::scoped_lock lock(mutex_);
-
-    std::list<int64_t> id;
-    db_.LookupIdentifier(id, tag, value);
-
-    for (std::list<int64_t>::const_iterator 
-           it = id.begin(); it != id.end(); ++it)
-    {
-      result.push_back(db_.GetPublicId(*it));
-    }
-  }
-
-
-  void ServerIndex::LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result,
-                                     const std::string& value)
-  {
-    result.clear();
-
-    boost::mutex::scoped_lock lock(mutex_);
-
-    std::list<int64_t> id;
-    db_.LookupIdentifier(id, value);
-
-    for (std::list<int64_t>::const_iterator 
-           it = id.begin(); it != id.end(); ++it)
-    {
-      result.push_back(std::make_pair(db_.GetResourceType(*it),
-                                      db_.GetPublicId(*it)));
-    }
+    LookupIdentifierQuery query(level);
+    query.AddConstraint(tag, IdentifierConstraintType_Equal, value);
+    query.Apply(result, db_);
   }
 
 
@@ -1990,6 +1974,11 @@
 
     db_.AddAttachment(resourceId, attachment);
 
+    if (IsUserContentType(attachment.GetContentType()))
+    {
+      LogChange(resourceId, ChangeType_UpdatedAttachment, resourceType, publicId);
+    }
+
     t.Commit(attachment.GetCompressedSize());
 
     return StoreStatus_Success;
@@ -2011,6 +2000,11 @@
 
     db_.DeleteAttachment(id, type);
 
+    if (IsUserContentType(type))
+    {
+      LogChange(id, ChangeType_UpdatedAttachment, rtype, publicId);
+    }
+
     t.Commit(0);
   }
 
@@ -2077,8 +2071,19 @@
 
   bool ServerIndex::GetMainDicomTags(DicomMap& result,
                                      const std::string& publicId,
-                                     ResourceType expectedType)
+                                     ResourceType expectedType,
+                                     ResourceType levelOfInterest)
   {
+    // Yes, the following test could be shortened, but we wish to make it as clear as possible
+    if (!(expectedType == ResourceType_Patient  && levelOfInterest == ResourceType_Patient) &&
+        !(expectedType == ResourceType_Study    && levelOfInterest == ResourceType_Patient) &&
+        !(expectedType == ResourceType_Study    && levelOfInterest == ResourceType_Study)   &&
+        !(expectedType == ResourceType_Series   && levelOfInterest == ResourceType_Series)  &&
+        !(expectedType == ResourceType_Instance && levelOfInterest == ResourceType_Instance))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
     result.Clear();
 
     boost::mutex::scoped_lock lock(mutex_);
@@ -2091,6 +2096,26 @@
     {
       return false;
     }
+
+    if (type == ResourceType_Study)
+    {
+      DicomMap tmp;
+      db_.GetMainDicomTags(tmp, id);
+
+      switch (levelOfInterest)
+      {
+        case ResourceType_Patient:
+          tmp.ExtractPatientInformation(result);
+          return true;
+
+        case ResourceType_Study:
+          tmp.ExtractStudyInformation(result);
+          return true;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
     else
     {
       db_.GetMainDicomTags(result, id);
@@ -2108,4 +2133,40 @@
     return db_.LookupResource(id, type, publicId);
   }
 
+
+  unsigned int ServerIndex::GetDatabaseVersion()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return db_.GetDatabaseVersion();
+  }
+
+
+  void ServerIndex::FindCandidates(std::vector<std::string>& resources,
+                                   std::vector<std::string>& instances,
+                                   const ::Orthanc::LookupResource& lookup)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+   
+    std::list<int64_t> tmp;
+    lookup.FindCandidates(tmp, db_);
+
+    resources.resize(tmp.size());
+    instances.resize(tmp.size());
+
+    size_t pos = 0;
+    for (std::list<int64_t>::const_iterator
+           it = tmp.begin(); it != tmp.end(); ++it, pos++)
+    {
+      assert(db_.GetResourceType(*it) == lookup.GetLevel());
+      
+      int64_t instance;
+      if (!Toolbox::FindOneChildInstance(instance, db_, *it, lookup.GetLevel()))
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      resources[pos] = db_.GetPublicId(*it);
+      instances[pos] = db_.GetPublicId(instance);
+    }
+  }
 }
--- a/OrthancServer/ServerIndex.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerIndex.h	Wed Nov 18 10:16:21 2015 +0100
@@ -45,6 +45,7 @@
 
 namespace Orthanc
 {
+  class LookupResource;
   class ServerContext;
 
   class ServerIndex : public boost::noncopyable
@@ -76,7 +77,8 @@
     static void UnstableResourcesMonitorThread(ServerIndex* that);
 
     void MainDicomTagsToJson(Json::Value& result,
-                             int64_t resourceId);
+                             int64_t resourceId,
+                             ResourceType resourceType);
 
     SeriesStatus GetSeriesStatus(int64_t id);
 
@@ -110,9 +112,6 @@
 
     uint64_t IncrementGlobalSequenceInternal(GlobalProperty property);
 
-    void SetMainDicomTags(int64_t resource,
-                          const DicomMap& tags);
-
     int64_t CreateResource(const std::string& publicId,
                            ResourceType type);
 
@@ -237,17 +236,10 @@
                        /* out */ unsigned int& countInstances, 
                        const std::string& publicId);
 
-    void LookupIdentifier(std::list<std::string>& result,
-                          const DicomTag& tag,
-                          const std::string& value,
-                          ResourceType type);
-
-    void LookupIdentifier(std::list<std::string>& result,
-                          const DicomTag& tag,
-                          const std::string& value);
-
-    void LookupIdentifier(std::list< std::pair<ResourceType, std::string> >& result,
-                          const std::string& value);
+    void LookupIdentifierExact(std::list<std::string>& result,
+                               ResourceType level,
+                               const DicomTag& tag,
+                               const std::string& value);
 
     StoreStatus AddAttachment(const FileInfo& attachment,
                               const std::string& publicId);
@@ -263,9 +255,16 @@
 
     bool GetMainDicomTags(DicomMap& result,
                           const std::string& publicId,
-                          ResourceType expectedType);
+                          ResourceType expectedType,
+                          ResourceType levelOfInterest);
 
     bool LookupResourceType(ResourceType& type,
                             const std::string& publicId);
+
+    unsigned int GetDatabaseVersion();
+
+    void FindCandidates(std::vector<std::string>& resources,
+                        std::vector<std::string>& instances,
+                        const ::Orthanc::LookupResource& lookup);
   };
 }
--- a/OrthancServer/ServerIndexChange.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerIndexChange.h	Wed Nov 18 10:16:21 2015 +0100
@@ -33,6 +33,7 @@
 #pragma once
 
 #include "ServerEnumerations.h"
+#include "../Core/IDynamicObject.h"
 #include "../Core/Toolbox.h"
 
 #include <string>
--- a/OrthancServer/ServerToolbox.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -33,122 +33,305 @@
 #include "PrecompiledHeadersServer.h"
 #include "ServerToolbox.h"
 
+#include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/FileStorage/StorageAccessor.h"
 #include "../Core/Logging.h"
 #include "../Core/OrthancException.h"
+#include "ParsedDicomFile.h"
+#include "Search/LookupIdentifierQuery.h"
 
 #include <cassert>
 
 namespace Orthanc
 {
-  void SimplifyTags(Json::Value& target,
-                    const Json::Value& source)
+  namespace Toolbox
   {
-    assert(source.isObject());
+    void SimplifyTags(Json::Value& target,
+                      const Json::Value& source)
+    {
+      assert(source.isObject());
+
+      target = Json::objectValue;
+      Json::Value::Members members = source.getMemberNames();
+
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        const Json::Value& v = source[members[i]];
+        const std::string& name = v["Name"].asString();
+        const std::string& type = v["Type"].asString();
 
-    target = Json::objectValue;
-    Json::Value::Members members = source.getMemberNames();
+        if (type == "String")
+        {
+          target[name] = v["Value"].asString();
+        }
+        else if (type == "TooLong" ||
+                 type == "Null")
+        {
+          target[name] = Json::nullValue;
+        }
+        else if (type == "Sequence")
+        {
+          const Json::Value& array = v["Value"];
+          assert(array.isArray());
 
-    for (size_t i = 0; i < members.size(); i++)
+          Json::Value children = Json::arrayValue;
+          for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
+          {
+            Json::Value c;
+            SimplifyTags(c, array[i]);
+            children.append(c);
+          }
+
+          target[name] = children;
+        }
+        else
+        {
+          assert(0);
+        }
+      }
+    }
+
+
+    static std::string ValueAsString(const DicomMap& summary,
+                                     const DicomTag& tag)
     {
-      const Json::Value& v = source[members[i]];
-      const std::string& name = v["Name"].asString();
-      const std::string& type = v["Type"].asString();
-
-      if (type == "String")
+      const DicomValue& value = summary.GetValue(tag);
+      if (value.IsNull())
       {
-        target[name] = v["Value"].asString();
+        return "(null)";
+      }
+      else
+      {
+        return value.GetContent();
       }
-      else if (type == "TooLong" ||
-               type == "Null")
+    }
+
+
+    void LogMissingRequiredTag(const DicomMap& summary)
+    {
+      std::string s, t;
+
+      if (summary.HasTag(DICOM_TAG_PATIENT_ID))
       {
-        target[name] = Json::nullValue;
+        if (t.size() > 0)
+          t += ", ";
+        t += "PatientID=" + ValueAsString(summary, DICOM_TAG_PATIENT_ID);
       }
-      else if (type == "Sequence")
+      else
       {
-        const Json::Value& array = v["Value"];
-        assert(array.isArray());
+        if (s.size() > 0)
+          s += ", ";
+        s += "PatientID";
+      }
 
-        Json::Value children = Json::arrayValue;
-        for (Json::Value::ArrayIndex i = 0; i < array.size(); i++)
-        {
-          Json::Value c;
-          SimplifyTags(c, array[i]);
-          children.append(c);
-        }
+      if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "StudyInstanceUID=" + ValueAsString(summary, DICOM_TAG_STUDY_INSTANCE_UID);
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "StudyInstanceUID";
+      }
 
-        target[name] = children;
+      if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "SeriesInstanceUID=" + ValueAsString(summary, DICOM_TAG_SERIES_INSTANCE_UID);
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "SeriesInstanceUID";
+      }
+
+      if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
+      {
+        if (t.size() > 0)
+          t += ", ";
+        t += "SOPInstanceUID=" + ValueAsString(summary, DICOM_TAG_SOP_INSTANCE_UID);
+      }
+      else
+      {
+        if (s.size() > 0)
+          s += ", ";
+        s += "SOPInstanceUID";
+      }
+
+      if (t.size() == 0)
+      {
+        LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
       }
       else
       {
-        assert(0);
+        LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
+      }
+    }
+
+
+    static void SetMainDicomTagsInternal(IDatabaseWrapper& database,
+                                         int64_t resource,
+                                         const DicomMap& tags)
+    {
+      DicomArray flattened(tags);
+
+      for (size_t i = 0; i < flattened.GetSize(); i++)
+      {
+        const DicomElement& element = flattened.GetElement(i);
+        const DicomTag& tag = element.GetTag();
+        const DicomValue& value = element.GetValue();
+        if (!value.IsNull() && 
+            !value.IsBinary())
+        {
+          database.SetMainDicomTag(resource, tag, element.GetValue().GetContent());
+        }
+      }
+    }
+
+
+    void SetMainDicomTags(IDatabaseWrapper& database,
+                          int64_t resource,
+                          ResourceType level,
+                          const DicomMap& dicomSummary)
+    {
+      // WARNING: The database should be locked with a transaction!
+
+      LookupIdentifierQuery::StoreIdentifiers(database, resource, level, dicomSummary);
+
+      DicomMap tags;
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          dicomSummary.ExtractPatientInformation(tags);
+          break;
+
+        case ResourceType_Study:
+          // Duplicate the patient tags at the study level (new in Orthanc 0.9.5 - db v6)
+          dicomSummary.ExtractPatientInformation(tags);
+          SetMainDicomTagsInternal(database, resource, tags);
+
+          dicomSummary.ExtractStudyInformation(tags);
+          break;
+
+        case ResourceType_Series:
+          dicomSummary.ExtractSeriesInformation(tags);
+          break;
+
+        case ResourceType_Instance:
+          dicomSummary.ExtractInstanceInformation(tags);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      SetMainDicomTagsInternal(database, resource, tags);
+    }
+
+
+    bool FindOneChildInstance(int64_t& result,
+                              IDatabaseWrapper& database,
+                              int64_t resource,
+                              ResourceType type)
+    {
+      for (;;)
+      {
+        if (type == ResourceType_Instance)
+        {
+          result = resource;
+          return true;
+        }
+
+        std::list<int64_t> children;
+        database.GetChildrenInternalId(children, resource);
+        if (children.empty())
+        {
+          return false;
+        }
+
+        resource = children.front();
+        type = GetChildResourceType(type);    
+      }
+    }
+
+
+    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+                                  IStorageArea& storageArea,
+                                  ResourceType level)
+    {
+      // WARNING: The database should be locked with a transaction!
+
+      const char* plural = NULL;
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          plural = "patients";
+          break;
+
+        case ResourceType_Study:
+          plural = "studies";
+          break;
+
+        case ResourceType_Series:
+          plural = "series";
+          break;
+
+        case ResourceType_Instance:
+          plural = "instances";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "...";
+
+      std::list<std::string> resources;
+      database.GetAllPublicIds(resources, level);
+
+      for (std::list<std::string>::const_iterator
+             it = resources.begin(); it != resources.end(); it++)
+      {
+        // Locate the resource and one of its child instances
+        int64_t resource, instance;
+        ResourceType tmp;
+
+        if (!database.LookupResource(resource, tmp, *it) ||
+            tmp != level ||
+            !FindOneChildInstance(instance, database, resource, level))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Get the DICOM file attached to some instances in the resource
+        FileInfo attachment;
+        if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Read and parse the content of the DICOM file
+        StorageAccessor accessor(storageArea);
+
+        std::string content;
+        accessor.Read(content, attachment);
+
+        ParsedDicomFile dicom(content);
+
+        // Update the tags of this resource
+        DicomMap dicomSummary;
+        dicom.Convert(dicomSummary);
+
+        database.ClearMainDicomTags(resource);
+        Toolbox::SetMainDicomTags(database, resource, level, dicomSummary);
       }
     }
   }
-
-
-  void LogMissingRequiredTag(const DicomMap& summary)
-  {
-    std::string s, t;
-
-    if (summary.HasTag(DICOM_TAG_PATIENT_ID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "PatientID=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "PatientID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "StudyInstanceUID=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "StudyInstanceUID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "SeriesInstanceUID=" + summary.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "SeriesInstanceUID";
-    }
-
-    if (summary.HasTag(DICOM_TAG_SOP_INSTANCE_UID))
-    {
-      if (t.size() > 0)
-        t += ", ";
-      t += "SOPInstanceUID=" + summary.GetValue(DICOM_TAG_SOP_INSTANCE_UID).AsString();
-    }
-    else
-    {
-      if (s.size() > 0)
-        s += ", ";
-      s += "SOPInstanceUID";
-    }
-
-    if (t.size() == 0)
-    {
-      LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
-    }
-    else
-    {
-      LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
-    }
-  }
 }
--- a/OrthancServer/ServerToolbox.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ServerToolbox.h	Wed Nov 18 10:16:21 2015 +0100
@@ -33,13 +33,31 @@
 #pragma once
 
 #include "../Core/DicomFormat/DicomMap.h"
+#include "IDatabaseWrapper.h"
 
 #include <json/json.h>
 
 namespace Orthanc
 {
-  void SimplifyTags(Json::Value& target,
-                    const Json::Value& source);
+  namespace Toolbox
+  {
+    void SimplifyTags(Json::Value& target,
+                      const Json::Value& source);
+
+    void LogMissingRequiredTag(const DicomMap& summary);
 
-  void LogMissingRequiredTag(const DicomMap& summary);
+    void SetMainDicomTags(IDatabaseWrapper& database,
+                          int64_t resource,
+                          ResourceType level,
+                          const DicomMap& dicomSummary);
+
+    bool FindOneChildInstance(int64_t& result,
+                              IDatabaseWrapper& database,
+                              int64_t resource,
+                              ResourceType type);
+
+    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+                                  IStorageArea& storageArea,
+                                  ResourceType level);
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/SliceOrdering.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,411 @@
+/**
+ * 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 "SliceOrdering.h"
+
+#include "../Core/Logging.h"
+#include "../Core/Toolbox.h"
+#include "ServerEnumerations.h"
+
+#include <algorithm>
+#include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace Orthanc
+{
+  static bool TokenizeVector(std::vector<float>& result,
+                             const std::string& value,
+                             unsigned int expectedSize)
+  {
+    std::vector<std::string> tokens;
+    Toolbox::TokenizeString(tokens, value, '\\');
+
+    if (tokens.size() != expectedSize)
+    {
+      return false;
+    }
+
+    result.resize(tokens.size());
+
+    for (size_t i = 0; i < tokens.size(); i++)
+    {
+      try
+      {
+        result[i] = boost::lexical_cast<float>(tokens[i]);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  static bool TokenizeVector(std::vector<float>& result,
+                             const DicomMap& map,
+                             const DicomTag& tag,
+                             unsigned int expectedSize)
+  {
+    const DicomValue* value = map.TestAndGetValue(tag);
+
+    if (value == NULL ||
+        value->IsNull() ||
+        value->IsBinary())
+    {
+      return false;
+    }
+    else
+    {
+      return TokenizeVector(result, value->GetContent(), expectedSize);
+    }
+  }
+
+
+  struct SliceOrdering::Instance : public boost::noncopyable
+  {
+  private:
+    std::string   instanceId_;
+    bool          hasPosition_;
+    Vector        position_;   
+    bool          hasIndexInSeries_;
+    size_t        indexInSeries_;
+    unsigned int  framesCount_;
+
+  public:
+    Instance(ServerIndex& index,
+             const std::string& instanceId) :
+      instanceId_(instanceId),
+      framesCount_(1)
+    {
+      DicomMap instance;
+      if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance))
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+
+      const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES);
+      if (frames != NULL &&
+          !frames->IsNull() &&
+          !frames->IsBinary())
+      {
+        try
+        {
+          framesCount_ = boost::lexical_cast<unsigned int>(frames->GetContent());
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+      }
+      
+      std::vector<float> tmp;
+      hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3);
+
+      if (hasPosition_)
+      {
+        position_[0] = tmp[0];
+        position_[1] = tmp[1];
+        position_[2] = tmp[2];
+      }
+
+      std::string s;
+      hasIndexInSeries_ = false;
+
+      try
+      {
+        if (index.LookupMetadata(s, instanceId, MetadataType_Instance_IndexInSeries))
+        {
+          indexInSeries_ = boost::lexical_cast<size_t>(s);
+          hasIndexInSeries_ = true;
+        }
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+      }
+    }
+
+    const std::string& GetIdentifier() const
+    {
+      return instanceId_;
+    }
+
+    bool HasPosition() const
+    {
+      return hasPosition_;
+    }
+
+    float ComputeRelativePosition(const Vector& normal) const
+    {
+      assert(HasPosition());
+      return (normal[0] * position_[0] + 
+              normal[1] * position_[1] +
+              normal[2] * position_[2]);
+    }
+
+    bool HasIndexInSeries() const
+    {
+      return hasIndexInSeries_;
+    }
+    
+    size_t GetIndexInSeries() const
+    {
+      assert(HasIndexInSeries());
+      return indexInSeries_;
+    }
+
+    unsigned int GetFramesCount() const
+    {
+      return framesCount_;
+    }
+  };
+
+
+  class SliceOrdering::PositionComparator
+  {
+  private:
+    const Vector&  normal_;
+
+  public:
+    PositionComparator(const Vector& normal) : normal_(normal)
+    {
+    }
+    
+    int operator() (const Instance* a,
+                    const Instance* b) const
+    {
+      return a->ComputeRelativePosition(normal_) < b->ComputeRelativePosition(normal_);
+    }
+  };
+
+
+  bool SliceOrdering::IndexInSeriesComparator(const SliceOrdering::Instance* a,
+                                              const SliceOrdering::Instance* b)
+  {
+    return a->GetIndexInSeries() < b->GetIndexInSeries();
+  }  
+
+
+  void SliceOrdering::ComputeNormal()
+  {
+    DicomMap series;
+    if (!index_.GetMainDicomTags(series, seriesId_, ResourceType_Series, ResourceType_Series))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    std::vector<float> cosines;
+    hasNormal_ = TokenizeVector(cosines, series, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6);
+
+    if (hasNormal_)
+    {
+      normal_[0] = cosines[1] * cosines[5] - cosines[2] * cosines[4];
+      normal_[1] = cosines[2] * cosines[3] - cosines[0] * cosines[5];
+      normal_[2] = cosines[0] * cosines[4] - cosines[1] * cosines[3];
+    }
+  }
+
+
+  void SliceOrdering::CreateInstances()
+  {
+    std::list<std::string> instancesId;
+    index_.GetChildren(instancesId, seriesId_);
+
+    instances_.reserve(instancesId.size());
+    for (std::list<std::string>::const_iterator
+           it = instancesId.begin(); it != instancesId.end(); ++it)
+    {
+      instances_.push_back(new Instance(index_, *it));
+    }
+  }
+  
+
+  bool SliceOrdering::SortUsingPositions()
+  {
+    if (instances_.size() <= 1)
+    {
+      // One single instance: It is sorted by default
+      return true;
+    }
+
+    if (!hasNormal_)
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      assert(instances_[i] != NULL);
+      if (!instances_[i]->HasPosition())
+      {
+        return false;
+      }
+    }
+
+    PositionComparator comparator(normal_);
+    std::sort(instances_.begin(), instances_.end(), comparator);
+
+    float a = instances_.front()->ComputeRelativePosition(normal_);
+    float b = instances_.back()->ComputeRelativePosition(normal_);
+
+    if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon())
+    {
+      // Not enough difference between the minimum and maximum
+      // positions along the normal of the volume
+      return false;
+    }
+    else
+    {
+      // This is a 3D volume
+      isVolume_ = true;
+      return true;
+    }
+  }
+
+
+  bool SliceOrdering::SortUsingIndexInSeries()
+  {
+    if (instances_.size() <= 1)
+    {
+      // One single instance: It is sorted by default
+      return true;
+    }
+
+    for (size_t i = 0; i < instances_.size(); i++)
+    {
+      assert(instances_[i] != NULL);
+      if (!instances_[i]->HasIndexInSeries())
+      {
+        return false;
+      }
+    }
+
+    std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator);
+    
+    for (size_t i = 1; i < instances_.size(); i++)
+    {
+      if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries())
+      {
+        // The current "IndexInSeries" occurs 2 times: Not a proper ordering
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  SliceOrdering::SliceOrdering(ServerIndex& index,
+                               const std::string& seriesId) :
+    index_(index),
+    seriesId_(seriesId),
+    isVolume_(false)
+  {
+    ComputeNormal();
+    CreateInstances();
+
+    if (!SortUsingPositions() &&
+        !SortUsingIndexInSeries())
+    {
+      LOG(ERROR) << "Unable to order the slices of the series " << seriesId;
+      throw OrthancException(ErrorCode_CannotOrderSlices);
+    }
+  }
+
+
+  SliceOrdering::~SliceOrdering()
+  {
+    for (std::vector<Instance*>::iterator
+           it = instances_.begin(); it != instances_.end(); ++it)
+    {
+      if (*it != NULL)
+      {
+        delete *it;
+      }
+    }
+  }
+
+
+  const std::string& SliceOrdering::GetInstanceId(size_t index) const
+  {
+    if (index >= instances_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return instances_[index]->GetIdentifier();
+    }
+  }
+
+
+  unsigned int SliceOrdering::GetFramesCount(size_t index) const
+  {
+    if (index >= instances_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return instances_[index]->GetFramesCount();
+    }
+  }
+
+
+  void SliceOrdering::Format(Json::Value& result) const
+  {
+    result = Json::objectValue;
+    result["Type"] = (isVolume_ ? "Volume" : "Sequence");
+    
+    Json::Value tmp = Json::arrayValue;
+    for (size_t i = 0; i < GetInstancesCount(); i++)
+    {
+      tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file");
+    }
+
+    result["Dicom"] = tmp;
+
+    tmp.clear();
+    for (size_t i = 0; i < GetInstancesCount(); i++)
+    {
+      std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i));
+      for (size_t j = 0; j < GetFramesCount(i); j++)
+      {
+        tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j));
+      }
+    }
+
+    result["Slices"] = tmp;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/SliceOrdering.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,82 @@
+/**
+ * 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 "ServerIndex.h"
+
+namespace Orthanc
+{
+  class SliceOrdering
+  {
+  private:
+    typedef float Vector[3];
+
+    struct Instance;
+    struct PositionComparator;
+
+    ServerIndex&             index_;
+    std::string              seriesId_;
+    bool                     hasNormal_;
+    Vector                   normal_;
+    std::vector<Instance*>   instances_;
+    bool                     isVolume_;
+
+    static bool IndexInSeriesComparator(const SliceOrdering::Instance* a,
+                                        const SliceOrdering::Instance* b);
+
+    void ComputeNormal();
+
+    void CreateInstances();
+
+    bool SortUsingPositions();
+
+    bool SortUsingIndexInSeries();
+
+  public:
+    SliceOrdering(ServerIndex& index,
+                  const std::string& seriesId);
+
+    ~SliceOrdering();
+
+    size_t  GetInstancesCount() const
+    {
+      return instances_.size();
+    }
+
+    const std::string& GetInstanceId(size_t index) const;
+
+    unsigned int GetFramesCount(size_t index) const;
+
+    void Format(Json::Value& result) const;
+  };
+}
--- a/OrthancServer/ToDcmtkBridge.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/ToDcmtkBridge.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -52,8 +52,11 @@
     for (DicomMap::Map::const_iterator 
            it = map.map_.begin(); it != map.map_.end(); ++it)
     {
-      std::string s = it->second->AsString();
-      DU_putStringDOElement(result.get(), Convert(it->first), s.c_str());
+      if (!it->second->IsNull())
+      {
+        std::string s = it->second->GetContent();
+        DU_putStringDOElement(result.get(), Convert(it->first), s.c_str());
+      }
     }
 
     return result.release();
--- a/OrthancServer/main.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/OrthancServer/main.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -307,13 +307,95 @@
 };
 
 
-static void PrintHelp(char* path)
+
+class MyHttpExceptionFormatter : public IHttpExceptionFormatter
+{
+private:
+  bool             describeErrors_;
+  OrthancPlugins*  plugins_;
+
+public:
+  MyHttpExceptionFormatter(bool describeErrors,
+                           OrthancPlugins* plugins) :
+    describeErrors_(describeErrors),
+    plugins_(plugins)
+  {
+  }
+
+  virtual void Format(HttpOutput& output,
+                      const OrthancException& exception,
+                      HttpMethod method,
+                      const char* uri)
+  {
+    {
+      bool isPlugin = false;
+
+#if ORTHANC_PLUGINS_ENABLED == 1
+      if (plugins_ != NULL)
+      {
+        plugins_->GetErrorDictionary().LogError(exception.GetErrorCode(), true);
+        isPlugin = true;
+      }
+#endif
+
+      if (!isPlugin)
+      {
+        LOG(ERROR) << "Exception in the HTTP handler: " << exception.What();
+      }
+    }      
+
+    Json::Value message = Json::objectValue;
+    ErrorCode errorCode = exception.GetErrorCode();
+    HttpStatus httpStatus = exception.GetHttpStatus();
+
+    {
+      bool isPlugin = false;
+
+#if ORTHANC_PLUGINS_ENABLED == 1
+      if (plugins_ != NULL &&
+          plugins_->GetErrorDictionary().Format(message, httpStatus, exception))
+      {
+        errorCode = ErrorCode_Plugin;
+        isPlugin = true;
+      }
+#endif
+
+      if (!isPlugin)
+      {
+        message["Message"] = exception.What();
+      }
+    }
+
+    if (!describeErrors_)
+    {
+      output.SendStatus(httpStatus);
+    }
+    else
+    {
+      message["Method"] = EnumerationToString(method);
+      message["Uri"] = uri;
+      message["HttpError"] = EnumerationToString(httpStatus);
+      message["HttpStatus"] = httpStatus;
+      message["OrthancError"] = EnumerationToString(errorCode);
+      message["OrthancStatus"] = errorCode;
+
+      std::string info = message.toStyledString();
+      output.SendStatus(httpStatus, info);
+    }
+  }
+};
+
+
+
+static void PrintHelp(const char* path)
 {
   std::cout 
     << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
     << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." << std::endl
     << std::endl
-    << "If no configuration file is given on the command line, a set of default " << std::endl
+    << "The \"CONFIGURATION\" argument can be a single file or a directory. In the " << std::endl
+    << "case of a directory, all the JSON files it contains will be merged. " << std::endl
+    << "If no configuration path is given on the command line, a set of default " << std::endl
     << "parameters is used. Please refer to the Orthanc homepage for the full " << std::endl
     << "instructions about how to use Orthanc <http://www.orthanc-server.com/>." << std::endl
     << std::endl
@@ -322,6 +404,7 @@
     << "  --logdir=[dir]\tdirectory where to store the log files" << std::endl
     << "\t\t\t(if not used, the logs are dumped to stderr)" << std::endl
     << "  --config=[file]\tcreate a sample configuration file and exit" << std::endl
+    << "  --errors\t\tprint the supported error codes and exit" << std::endl
     << "  --verbose\t\tbe verbose in logs" << std::endl
     << "  --trace\t\thighest verbosity in logs (for debug)" << std::endl
     << "  --upgrade\t\tallow Orthanc to upgrade the version of the" << std::endl
@@ -331,12 +414,16 @@
     << std::endl
     << "Exit status:" << std::endl
     << "   0 if success," << std::endl
+#if defined(_WIN32)
+    << "!= 0 if error (use the --errors option to get the list of possible errors)." << std::endl
+#else
     << "  -1 if error (have a look at the logs)." << std::endl
+#endif
     << std::endl;
 }
 
 
-static void PrintVersion(char* path)
+static void PrintVersion(const char* path)
 {
   std::cout
     << path << " " << ORTHANC_VERSION << std::endl
@@ -349,6 +436,126 @@
 }
 
 
+static void PrintErrorCode(ErrorCode code, const char* description)
+{
+  std::cout 
+    << std::right << std::setw(16) 
+    << static_cast<int>(code)
+    << "   " << description << std::endl;
+}
+
+
+static void PrintErrors(const char* path)
+{
+  std::cout
+    << path << " " << ORTHANC_VERSION << std::endl
+    << "Orthanc, lightweight, RESTful DICOM server for healthcare and medical research." 
+    << std::endl << std::endl
+    << "List of error codes that could be returned by Orthanc:" 
+    << std::endl << std::endl;
+
+  // The content of the following brackets is automatically generated
+  // by the "GenerateErrorCodes.py" script
+  {
+    PrintErrorCode(ErrorCode_InternalError, "Internal error");
+    PrintErrorCode(ErrorCode_Success, "Success");
+    PrintErrorCode(ErrorCode_Plugin, "Error encountered within the plugin engine");
+    PrintErrorCode(ErrorCode_NotImplemented, "Not implemented yet");
+    PrintErrorCode(ErrorCode_ParameterOutOfRange, "Parameter out of range");
+    PrintErrorCode(ErrorCode_NotEnoughMemory, "Not enough memory");
+    PrintErrorCode(ErrorCode_BadParameterType, "Bad type for a parameter");
+    PrintErrorCode(ErrorCode_BadSequenceOfCalls, "Bad sequence of calls");
+    PrintErrorCode(ErrorCode_InexistentItem, "Accessing an inexistent item");
+    PrintErrorCode(ErrorCode_BadRequest, "Bad request");
+    PrintErrorCode(ErrorCode_NetworkProtocol, "Error in the network protocol");
+    PrintErrorCode(ErrorCode_SystemCommand, "Error while calling a system command");
+    PrintErrorCode(ErrorCode_Database, "Error with the database engine");
+    PrintErrorCode(ErrorCode_UriSyntax, "Badly formatted URI");
+    PrintErrorCode(ErrorCode_InexistentFile, "Inexistent file");
+    PrintErrorCode(ErrorCode_CannotWriteFile, "Cannot write to file");
+    PrintErrorCode(ErrorCode_BadFileFormat, "Bad file format");
+    PrintErrorCode(ErrorCode_Timeout, "Timeout");
+    PrintErrorCode(ErrorCode_UnknownResource, "Unknown resource");
+    PrintErrorCode(ErrorCode_IncompatibleDatabaseVersion, "Incompatible version of the database");
+    PrintErrorCode(ErrorCode_FullStorage, "The file storage is full");
+    PrintErrorCode(ErrorCode_CorruptedFile, "Corrupted file (e.g. inconsistent MD5 hash)");
+    PrintErrorCode(ErrorCode_InexistentTag, "Inexistent tag");
+    PrintErrorCode(ErrorCode_ReadOnly, "Cannot modify a read-only data structure");
+    PrintErrorCode(ErrorCode_IncompatibleImageFormat, "Incompatible format of the images");
+    PrintErrorCode(ErrorCode_IncompatibleImageSize, "Incompatible size of the images");
+    PrintErrorCode(ErrorCode_SharedLibrary, "Error while using a shared library (plugin)");
+    PrintErrorCode(ErrorCode_UnknownPluginService, "Plugin invoking an unknown service");
+    PrintErrorCode(ErrorCode_UnknownDicomTag, "Unknown DICOM tag");
+    PrintErrorCode(ErrorCode_BadJson, "Cannot parse a JSON document");
+    PrintErrorCode(ErrorCode_Unauthorized, "Bad credentials were provided to an HTTP request");
+    PrintErrorCode(ErrorCode_BadFont, "Badly formatted font file");
+    PrintErrorCode(ErrorCode_DatabasePlugin, "The plugin implementing a custom database back-end does not fulfill the proper interface");
+    PrintErrorCode(ErrorCode_StorageAreaPlugin, "Error in the plugin implementing a custom storage area");
+    PrintErrorCode(ErrorCode_EmptyRequest, "The request is empty");
+    PrintErrorCode(ErrorCode_NotAcceptable, "Cannot send a response which is acceptable according to the Accept HTTP header");
+    PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened");
+    PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open");
+    PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database");
+    PrintErrorCode(ErrorCode_SQLiteStatementAlreadyUsed, "SQLite: This cached statement is already being referred to");
+    PrintErrorCode(ErrorCode_SQLiteExecute, "SQLite: Cannot execute a command");
+    PrintErrorCode(ErrorCode_SQLiteRollbackWithoutTransaction, "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)");
+    PrintErrorCode(ErrorCode_SQLiteCommitWithoutTransaction, "SQLite: Committing a nonexistent transaction");
+    PrintErrorCode(ErrorCode_SQLiteRegisterFunction, "SQLite: Unable to register a function");
+    PrintErrorCode(ErrorCode_SQLiteFlush, "SQLite: Unable to flush the database");
+    PrintErrorCode(ErrorCode_SQLiteCannotRun, "SQLite: Cannot run a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteCannotStep, "SQLite: Cannot step over a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteBindOutOfRange, "SQLite: Bing a value while out of range (serious error)");
+    PrintErrorCode(ErrorCode_SQLitePrepareStatement, "SQLite: Cannot prepare a cached statement");
+    PrintErrorCode(ErrorCode_SQLiteTransactionAlreadyStarted, "SQLite: Beginning the same transaction twice");
+    PrintErrorCode(ErrorCode_SQLiteTransactionCommit, "SQLite: Failure when committing the transaction");
+    PrintErrorCode(ErrorCode_SQLiteTransactionBegin, "SQLite: Cannot start a transaction");
+    PrintErrorCode(ErrorCode_DirectoryOverFile, "The directory to be created is already occupied by a regular file");
+    PrintErrorCode(ErrorCode_FileStorageCannotWrite, "Unable to create a subdirectory or a file in the file storage");
+    PrintErrorCode(ErrorCode_DirectoryExpected, "The specified path does not point to a directory");
+    PrintErrorCode(ErrorCode_HttpPortInUse, "The TCP port of the HTTP server is already in use");
+    PrintErrorCode(ErrorCode_DicomPortInUse, "The TCP port of the DICOM server is already in use");
+    PrintErrorCode(ErrorCode_BadHttpStatusInRest, "This HTTP status is not allowed in a REST API");
+    PrintErrorCode(ErrorCode_RegularFileExpected, "The specified path does not point to a regular file");
+    PrintErrorCode(ErrorCode_PathToExecutable, "Unable to get the path to the executable");
+    PrintErrorCode(ErrorCode_MakeDirectory, "Cannot create a directory");
+    PrintErrorCode(ErrorCode_BadApplicationEntityTitle, "An application entity title (AET) cannot be empty or be longer than 16 characters");
+    PrintErrorCode(ErrorCode_NoCFindHandler, "No request handler factory for DICOM C-FIND SCP");
+    PrintErrorCode(ErrorCode_NoCMoveHandler, "No request handler factory for DICOM C-MOVE SCP");
+    PrintErrorCode(ErrorCode_NoCStoreHandler, "No request handler factory for DICOM C-STORE SCP");
+    PrintErrorCode(ErrorCode_NoApplicationEntityFilter, "No application entity filter");
+    PrintErrorCode(ErrorCode_NoSopClassOrInstance, "DicomUserConnection: Unable to find the SOP class and instance");
+    PrintErrorCode(ErrorCode_NoPresentationContext, "DicomUserConnection: No acceptable presentation context for modality");
+    PrintErrorCode(ErrorCode_DicomFindUnavailable, "DicomUserConnection: The C-FIND command is not supported by the remote SCP");
+    PrintErrorCode(ErrorCode_DicomMoveUnavailable, "DicomUserConnection: The C-MOVE command is not supported by the remote SCP");
+    PrintErrorCode(ErrorCode_CannotStoreInstance, "Cannot store an instance");
+    PrintErrorCode(ErrorCode_CreateDicomNotString, "Only string values are supported when creating DICOM instances");
+    PrintErrorCode(ErrorCode_CreateDicomOverrideTag, "Trying to override a value inherited from a parent module");
+    PrintErrorCode(ErrorCode_CreateDicomUseContent, "Use \"Content\" to inject an image into a new DICOM instance");
+    PrintErrorCode(ErrorCode_CreateDicomNoPayload, "No payload is present for one instance in the series");
+    PrintErrorCode(ErrorCode_CreateDicomUseDataUriScheme, "The payload of the DICOM instance must be specified according to Data URI scheme");
+    PrintErrorCode(ErrorCode_CreateDicomBadParent, "Trying to attach a new DICOM instance to an inexistent resource");
+    PrintErrorCode(ErrorCode_CreateDicomParentIsInstance, "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)");
+    PrintErrorCode(ErrorCode_CreateDicomParentEncoding, "Unable to get the encoding of the parent resource");
+    PrintErrorCode(ErrorCode_UnknownModality, "Unknown modality");
+    PrintErrorCode(ErrorCode_BadJobOrdering, "Bad ordering of filters in a job");
+    PrintErrorCode(ErrorCode_JsonToLuaTable, "Cannot convert the given JSON object to a Lua table");
+    PrintErrorCode(ErrorCode_CannotCreateLua, "Cannot create the Lua context");
+    PrintErrorCode(ErrorCode_CannotExecuteLua, "Cannot execute a Lua command");
+    PrintErrorCode(ErrorCode_LuaAlreadyExecuted, "Arguments cannot be pushed after the Lua function is executed");
+    PrintErrorCode(ErrorCode_LuaBadOutput, "The Lua function does not give the expected number of outputs");
+    PrintErrorCode(ErrorCode_NotLuaPredicate, "The Lua function is not a predicate (only true/false outputs allowed)");
+    PrintErrorCode(ErrorCode_LuaReturnsNoString, "The Lua function does not return a string");
+    PrintErrorCode(ErrorCode_StorageAreaAlreadyRegistered, "Another plugin has already registered a custom storage area");
+    PrintErrorCode(ErrorCode_DatabaseBackendAlreadyRegistered, "Another plugin has already registered a custom database back-end");
+    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");
+  }
+
+  std::cout << std::endl;
+}
+
+
 
 static void LoadLuaScripts(ServerContext& context)
 {
@@ -392,13 +599,27 @@
 {
   LOG(WARNING) << "Orthanc has started";
 
+#if ORTHANC_PLUGINS_ENABLED == 1
+  if (context.HasPlugins())
+  {
+    context.GetPlugins().SignalOrthancStarted();
+  }
+#endif
+
   context.GetLua().Execute("Initialize");
 
-  Toolbox::ServerBarrier(restApi.ResetRequestReceivedFlag());
-  bool restart = restApi.ResetRequestReceivedFlag();
+  Toolbox::ServerBarrier(restApi.LeaveBarrierFlag());
+  bool restart = restApi.IsResetRequestReceived();
 
   context.GetLua().Execute("Finalize");
 
+#if ORTHANC_PLUGINS_ENABLED == 1
+  if (context.HasPlugins())
+  {
+    context.GetPlugins().SignalOrthancStopped();
+  }
+#endif
+
   if (restart)
   {
     LOG(WARNING) << "Reset request received, restarting Orthanc";
@@ -413,7 +634,8 @@
 
 
 static bool StartHttpServer(ServerContext& context,
-                            OrthancRestApi& restApi)
+                            OrthancRestApi& restApi,
+                            OrthancPlugins* plugins)
 {
   if (!Configuration::GetGlobalBoolParameter("HttpServerEnabled", true))
   {
@@ -421,6 +643,9 @@
     return WaitForExit(context, restApi);
   }
 
+  MyHttpExceptionFormatter exceptionFormatter(Configuration::GetGlobalBoolParameter("HttpDescribeErrors", true), plugins);
+  
+
   // HTTP server
   MyIncomingHttpRequestFilter httpFilter(context);
   MongooseServer httpServer;
@@ -428,8 +653,8 @@
   httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false));
   httpServer.SetKeepAliveEnabled(Configuration::GetGlobalBoolParameter("KeepAlive", false));
   httpServer.SetHttpCompressionEnabled(Configuration::GetGlobalBoolParameter("HttpCompressionEnabled", true));
-  httpServer.SetDescribeErrorsEnabled(Configuration::GetGlobalBoolParameter("HttpDescribeErrors", true));
   httpServer.SetIncomingHttpRequestFilter(httpFilter);
+  httpServer.SetHttpExceptionFormatter(exceptionFormatter);
 
   httpServer.SetAuthenticationEnabled(Configuration::GetGlobalBoolParameter("AuthenticationEnabled", false));
   Configuration::SetupRegisteredUsers(httpServer);
@@ -461,12 +686,13 @@
 
 
 static bool StartDicomServer(ServerContext& context,
-                             OrthancRestApi& restApi)
+                             OrthancRestApi& restApi,
+                             OrthancPlugins* plugins)
 {
   if (!Configuration::GetGlobalBoolParameter("DicomServerEnabled", true))
   {
     LOG(WARNING) << "The DICOM server is disabled";
-    return StartHttpServer(context, restApi);
+    return StartHttpServer(context, restApi, plugins);
   }
 
   MyDicomServerFactory serverFactory(context);
@@ -485,13 +711,28 @@
   dicomServer.Start();
   LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
 
-  bool restart = StartHttpServer(context, restApi);
+  bool restart;
+  ErrorCode error = ErrorCode_Success;
+
+  try
+  {
+    restart = StartHttpServer(context, restApi, plugins);
+  }
+  catch (OrthancException& e)
+  {
+    error = e.GetErrorCode();
+  }
 
   dicomServer.Stop();
   LOG(WARNING) << "    DICOM server has stopped";
 
   serverFactory.Done();
 
+  if (error != ErrorCode_Success)
+  {
+    throw OrthancException(error);
+  }
+
   return restart;
 }
 
@@ -522,7 +763,7 @@
   OrthancRestApi restApi(context);
   context.GetHttpHandler().Register(restApi, true);
 
-  return StartDicomServer(context, restApi);
+  return StartDicomServer(context, restApi, plugins);
 }
 
 
@@ -530,22 +771,29 @@
                             IStorageArea& storageArea,
                             bool allowDatabaseUpgrade)
 {
-  // Upgrade the database, if needed
+  // Upgrade the schema of the database, if needed
   unsigned int currentVersion = database.GetDatabaseVersion();
   if (currentVersion == ORTHANC_DATABASE_VERSION)
   {
     return true;
   }
 
+  if (currentVersion > ORTHANC_DATABASE_VERSION)
+  {
+    LOG(ERROR) << "The version of the database schema (" << currentVersion
+               << ") is too recent for this version of Orthanc. Please upgrade Orthanc.";
+    return false;
+  }
+
   if (!allowDatabaseUpgrade)
   {
-    LOG(ERROR) << "The database must be upgraded from version "
+    LOG(ERROR) << "The database schema must be upgraded from version "
                << currentVersion << " to " << ORTHANC_DATABASE_VERSION 
                << ": Please run Orthanc with the \"--upgrade\" command-line option";
     return false;
   }
 
-  LOG(WARNING) << "Upgrading the database from version "
+  LOG(WARNING) << "Upgrading the database from schema version "
                << currentVersion << " to " << ORTHANC_DATABASE_VERSION;
   database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea);
     
@@ -553,7 +801,7 @@
   currentVersion = database.GetDatabaseVersion();
   if (ORTHANC_DATABASE_VERSION != currentVersion)
   {
-    LOG(ERROR) << "The database was not properly updated, it is still at version " << currentVersion;
+    LOG(ERROR) << "The database schema was not properly upgraded, it is still at version " << currentVersion;
     throw OrthancException(ErrorCode_InternalError);
   }
 
@@ -563,14 +811,8 @@
 
 static bool ConfigureServerContext(IDatabaseWrapper& database,
                                    IStorageArea& storageArea,
-                                   OrthancPlugins *plugins,
-                                   bool allowDatabaseUpgrade)
+                                   OrthancPlugins *plugins)
 {
-  if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade))
-  {
-    return false;
-  }
-
   ServerContext context(database, storageArea);
 
   HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0));
@@ -606,7 +848,18 @@
   }
 #endif
 
-  bool restart = ConfigureHttpHandler(context, plugins);
+  bool restart;
+  ErrorCode error = ErrorCode_Success;
+
+  try
+  {
+    restart = ConfigureHttpHandler(context, plugins);
+  }
+  catch (OrthancException& e)
+  {
+    error = e.GetErrorCode();
+  }
+
   context.Stop();
 
 #if ORTHANC_PLUGINS_ENABLED == 1
@@ -616,10 +869,35 @@
   }
 #endif
 
+  if (error != ErrorCode_Success)
+  {
+    throw OrthancException(error);
+  }
+
   return restart;
 }
 
 
+static bool ConfigureDatabase(IDatabaseWrapper& database,
+                              IStorageArea& storageArea,
+                              OrthancPlugins *plugins,
+                              bool allowDatabaseUpgrade)
+{
+  database.Open();
+  
+  if (!UpgradeDatabase(database, storageArea, allowDatabaseUpgrade))
+  {
+    return false;
+  }
+
+  bool success = ConfigureServerContext(database, storageArea, plugins);
+
+  database.Close();
+
+  return success;
+}
+
+
 static bool ConfigurePlugins(int argc, 
                              char* argv[],
                              bool allowDatabaseUpgrade)
@@ -657,14 +935,14 @@
   assert(database != NULL);
   assert(storage.get() != NULL);
 
-  return ConfigureServerContext(*database, *storage, &plugins, allowDatabaseUpgrade);
+  return ConfigureDatabase(*database, *storage, &plugins, allowDatabaseUpgrade);
 
 #elif ORTHANC_PLUGINS_ENABLED == 0
   // The plugins are disabled
   databasePtr.reset(Configuration::CreateDatabaseWrapper());
   storage.reset(Configuration::CreateStorageArea());
 
-  return ConfigureServerContext(*databasePtr, *storage, NULL, allowDatabaseUpgrade);
+  return ConfigureDatabase(*databasePtr, *storage, NULL, allowDatabaseUpgrade);
 
 #else
 #  error The macro ORTHANC_PLUGINS_ENABLED must be set to 0 or 1
@@ -685,34 +963,61 @@
   Logging::Initialize();
 
   bool allowDatabaseUpgrade = false;
+  const char* configurationFile = NULL;
+
+
+  /**
+   * Parse the command-line options.
+   **/ 
 
   for (int i = 1; i < argc; i++)
   {
-    if (std::string(argv[i]) == "--help")
+    std::string argument(argv[i]); 
+
+    if (argument.empty())
+    {
+      // Ignore empty arguments
+    }
+    else if (argument[0] != '-')
+    {
+      if (configurationFile != NULL)
+      {
+        LOG(ERROR) << "More than one configuration path were provided on the command line, aborting";
+        return -1;
+      }
+      else
+      {
+        // Use the first argument that does not start with a "-" as
+        // the configuration file
+        configurationFile = argv[i];
+      }
+    }
+    else if (argument == "--errors")
+    {
+      PrintErrors(argv[0]);
+      return 0;
+    }
+    else if (argument == "--help")
     {
       PrintHelp(argv[0]);
       return 0;
     }
-
-    if (std::string(argv[i]) == "--version")
+    else if (argument == "--version")
     {
       PrintVersion(argv[0]);
       return 0;
     }
-
-    if (std::string(argv[i]) == "--verbose")
+    else if (argument == "--verbose")
     {
       Logging::EnableInfoLevel(true);
     }
-
-    if (std::string(argv[i]) == "--trace")
+    else if (argument == "--trace")
     {
       Logging::EnableTraceLevel(true);
     }
-
-    if (boost::starts_with(argv[i], "--logdir="))
+    else if (boost::starts_with(argument, "--logdir="))
     {
-      std::string directory = std::string(argv[i]).substr(9);
+      std::string directory = argument.substr(9);
 
       try
       {
@@ -720,17 +1025,16 @@
       }
       catch (OrthancException&)
       {
-        fprintf(stderr, "The directory where to store the log files (%s) is inexistent, aborting.\n", directory.c_str());
+        LOG(ERROR) << "The directory where to store the log files (" 
+                   << directory << ") is inexistent, aborting.";
         return -1;
       }
     }
-
-    if (std::string(argv[i]) == "--upgrade")
+    else if (argument == "--upgrade")
     {
       allowDatabaseUpgrade = true;
     }
-
-    if (boost::starts_with(argv[i], "--config="))
+    else if (boost::starts_with(argument, "--config="))
     {
       std::string configurationSample;
       GetFileResource(configurationSample, EmbeddedResources::CONFIGURATION_SAMPLE);
@@ -740,24 +1044,20 @@
       boost::replace_all(configurationSample, "\n", "\r\n");
 #endif
 
-      std::string target = std::string(argv[i]).substr(9);
-      std::ofstream f(target.c_str());
-      f << configurationSample;
-      f.close();
+      std::string target = argument.substr(9);
+      Toolbox::WriteFile(configurationSample, target);
       return 0;
     }
+    else
+    {
+      LOG(WARNING) << "Option unsupported by the core of Orthanc: " << argument;
+    }
   }
 
-  const char* configurationFile = NULL;
-  for (int i = 1; i < argc; i++)
-  {
-    // Use the first argument that does not start with a "-" as
-    // the configuration file
-    if (argv[i][0] != '-')
-    {
-      configurationFile = argv[i];
-    }
-  }
+
+  /**
+   * Launch Orthanc.
+   **/
 
   LOG(WARNING) << "Orthanc version: " << ORTHANC_VERSION;
 
@@ -781,8 +1081,20 @@
   }
   catch (const OrthancException& e)
   {
-    LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "]";
+    LOG(ERROR) << "Uncaught exception, stopping now: [" << e.What() << "] (code " << e.GetErrorCode() << ")";
+#if defined(_WIN32)
+    if (e.GetErrorCode() >= ErrorCode_START_PLUGINS)
+    {
+      status = static_cast<int>(ErrorCode_Plugin);
+    }
+    else
+    {
+      status = static_cast<int>(e.GetErrorCode());
+    }
+
+#else
     status = -1;
+#endif
   }
   catch (const std::exception& e) 
   {
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -46,50 +46,6 @@
 
 namespace Orthanc
 {
-  static OrthancPluginResourceType Convert(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return OrthancPluginResourceType_Patient;
-
-      case ResourceType_Study:
-        return OrthancPluginResourceType_Study;
-
-      case ResourceType_Series:
-        return OrthancPluginResourceType_Series;
-
-      case ResourceType_Instance:
-        return OrthancPluginResourceType_Instance;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
-  static ResourceType Convert(OrthancPluginResourceType type)
-  {
-    switch (type)
-    {
-      case OrthancPluginResourceType_Patient:
-        return ResourceType_Patient;
-
-      case OrthancPluginResourceType_Study:
-        return ResourceType_Study;
-
-      case OrthancPluginResourceType_Series:
-        return ResourceType_Series;
-
-      case OrthancPluginResourceType_Instance:
-        return ResourceType_Instance;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-  }
-
-
   static FileInfo Convert(const OrthancPluginAttachment& attachment)
   {
     return FileInfo(attachment.uuid,
@@ -102,6 +58,16 @@
   }
 
 
+  void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      errorDictionary_.LogError(code, true);
+      throw OrthancException(static_cast<ErrorCode>(code));
+    }
+  }
+
+
   void OrthancPluginDatabase::ResetAnswers()
   {
     type_ = _OrthancPluginDatabaseAnswerType_None;
@@ -194,11 +160,13 @@
 
 
   OrthancPluginDatabase::OrthancPluginDatabase(SharedLibrary& library,
+                                               PluginsErrorDictionary&  errorDictionary,
                                                const OrthancPluginDatabaseBackend& backend,
                                                const OrthancPluginDatabaseExtensions* extensions,
                                                size_t extensionsSize,
                                                void *payload) : 
     library_(library),
+    errorDictionary_(errorDictionary),
     type_(_OrthancPluginDatabaseAnswerType_None),
     backend_(backend),
     payload_(payload),
@@ -232,46 +200,26 @@
     tmp.compressedSize = attachment.GetCompressedSize();
     tmp.compressedHash = attachment.GetCompressedMD5().c_str();
 
-    OrthancPluginErrorCode error = backend_.addAttachment(payload_, id, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.addAttachment(payload_, id, &tmp));
   }
 
 
   void OrthancPluginDatabase::AttachChild(int64_t parent,
                                           int64_t child)
   {
-    OrthancPluginErrorCode error = backend_.attachChild(payload_, parent, child);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.attachChild(payload_, parent, child));
   }
 
 
   void OrthancPluginDatabase::ClearChanges()
   {
-    OrthancPluginErrorCode error = backend_.clearChanges(payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.clearChanges(payload_));
   }
 
 
   void OrthancPluginDatabase::ClearExportedResources()
   {
-    OrthancPluginErrorCode error = backend_.clearExportedResources(payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.clearExportedResources(payload_));
   }
 
 
@@ -279,14 +227,7 @@
                                                 ResourceType type)
   {
     int64_t id;
-
-    OrthancPluginErrorCode error = backend_.createResource(&id, payload_, publicId.c_str(), Convert(type));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type)));
     return id;
   }
 
@@ -294,35 +235,20 @@
   void OrthancPluginDatabase::DeleteAttachment(int64_t id,
                                                FileContentType attachment)
   {
-    OrthancPluginErrorCode error = backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)));
   }
 
 
   void OrthancPluginDatabase::DeleteMetadata(int64_t id,
                                              MetadataType type)
   {
-    OrthancPluginErrorCode error = backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)));
   }
 
 
   void OrthancPluginDatabase::DeleteResource(int64_t id)
   {
-    OrthancPluginErrorCode error = backend_.deleteResource(payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.deleteResource(payload_, id));
   }
 
 
@@ -348,18 +274,26 @@
   }
 
 
+  void OrthancPluginDatabase::GetAllInternalIds(std::list<int64_t>& target,
+                                                ResourceType resourceType)
+  {
+    if (extensions_.getAllInternalIds == NULL)
+    {
+      LOG(ERROR) << "The database plugin does not implement the GetAllInternalIds primitive";
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
+
+    ResetAnswers();
+    CheckSuccess(extensions_.getAllInternalIds(GetContext(), payload_, Plugins::Convert(resourceType)));
+    ForwardAnswers(target);
+  }
+
+
   void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
                                               ResourceType resourceType)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType)));
     ForwardAnswers(target);
   }
 
@@ -373,15 +307,8 @@
     {
       // This extension is available since Orthanc 0.9.4
       ResetAnswers();
-
-      OrthancPluginErrorCode error = extensions_.getAllPublicIdsWithLimit
-        (GetContext(), payload_, Convert(resourceType), since, limit);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
-
+      CheckSuccess(extensions_.getAllPublicIdsWithLimit
+                   (GetContext(), payload_, Plugins::Convert(resourceType), since, limit));
       ForwardAnswers(target);
     }
     else
@@ -428,12 +355,7 @@
     answerDone_ = &done;
     done = false;
 
-    OrthancPluginErrorCode error = backend_.getChanges(GetContext(), payload_, since, maxResults);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults));
   }
 
 
@@ -441,14 +363,7 @@
                                                     int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getChildrenInternalId(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id));
     ForwardAnswers(target);
   }
 
@@ -457,14 +372,7 @@
                                                   int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getChildrenPublicId(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id));
     ForwardAnswers(target);
   }
 
@@ -479,12 +387,7 @@
     answerDone_ = &done;
     done = false;
 
-    OrthancPluginErrorCode error = backend_.getExportedResources(GetContext(), payload_, since, maxResults);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults));
   }
 
 
@@ -496,12 +399,7 @@
     answerChanges_ = &target;
     answerDone_ = &ignored;
 
-    OrthancPluginErrorCode error = backend_.getLastChange(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.getLastChange(GetContext(), payload_));
   }
 
 
@@ -513,12 +411,7 @@
     answerExportedResources_ = &target;
     answerDone_ = &ignored;
 
-    OrthancPluginErrorCode error = backend_.getLastExportedResource(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_));
   }
 
 
@@ -528,12 +421,7 @@
     ResetAnswers();
     answerDicomMap_ = &map;
 
-    OrthancPluginErrorCode error = backend_.getMainDicomTags(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id));
   }
 
 
@@ -542,13 +430,8 @@
     ResetAnswers();
     std::string s;
 
-    OrthancPluginErrorCode error = backend_.getPublicId(GetContext(), payload_, resourceId);
+    CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId));
 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-    
     if (!ForwardSingleAnswer(s))
     {
       throw OrthancException(ErrorCode_DatabasePlugin);
@@ -561,14 +444,7 @@
   uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
   {
     uint64_t count;
-
-    OrthancPluginErrorCode error = backend_.getResourceCount(&count, payload_, Convert(resourceType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType)));
     return count;
   }
 
@@ -576,29 +452,15 @@
   ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
   {
     OrthancPluginResourceType type;
-
-    OrthancPluginErrorCode error = backend_.getResourceType(&type, payload_, resourceId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
-    return Convert(type);
+    CheckSuccess(backend_.getResourceType(&type, payload_, resourceId));
+    return Plugins::Convert(type);
   }
 
 
   uint64_t OrthancPluginDatabase::GetTotalCompressedSize()
   {
     uint64_t size;
-
-    OrthancPluginErrorCode error = backend_.getTotalCompressedSize(&size, payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getTotalCompressedSize(&size, payload_));
     return size;
   }
 
@@ -606,14 +468,7 @@
   uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
   {
     uint64_t size;
-
-    OrthancPluginErrorCode error = backend_.getTotalUncompressedSize(&size, payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_));
     return size;
   }
 
@@ -621,14 +476,7 @@
   bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
   {
     int32_t existing;
-
-    OrthancPluginErrorCode error = backend_.isExistingResource(&existing, payload_, internalId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId));
     return (existing != 0);
   }
 
@@ -636,14 +484,7 @@
   bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
   {
     int32_t isProtected;
-
-    OrthancPluginErrorCode error = backend_.isProtectedPatient(&isProtected, payload_, internalId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId));
     return (isProtected != 0);
   }
 
@@ -652,13 +493,7 @@
                                                     int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.listAvailableMetadata(GetContext(), payload_, id);
- 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
 
     if (type_ != _OrthancPluginDatabaseAnswerType_None &&
         type_ != _OrthancPluginDatabaseAnswerType_Int32)
@@ -684,12 +519,7 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.listAvailableAttachments(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id));
 
     if (type_ != _OrthancPluginDatabaseAnswerType_None &&
         type_ != _OrthancPluginDatabaseAnswerType_Int32)
@@ -716,16 +546,11 @@
     OrthancPluginChange tmp;
     tmp.seq = change.GetSeq();
     tmp.changeType = static_cast<int32_t>(change.GetChangeType());
-    tmp.resourceType = Convert(change.GetResourceType());
+    tmp.resourceType = Plugins::Convert(change.GetResourceType());
     tmp.publicId = change.GetPublicId().c_str();
     tmp.date = change.GetDate().c_str();
 
-    OrthancPluginErrorCode error = backend_.logChange(payload_, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.logChange(payload_, &tmp));
   }
 
 
@@ -733,7 +558,7 @@
   {
     OrthancPluginExportedResource tmp;
     tmp.seq = resource.GetSeq();
-    tmp.resourceType = Convert(resource.GetResourceType());
+    tmp.resourceType = Plugins::Convert(resource.GetResourceType());
     tmp.publicId = resource.GetPublicId().c_str();
     tmp.modality = resource.GetModality().c_str();
     tmp.date = resource.GetDate().c_str();
@@ -742,12 +567,7 @@
     tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
     tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
 
-    OrthancPluginErrorCode error = backend_.logExportedResource(payload_, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.logExportedResource(payload_, &tmp));
   }
 
     
@@ -757,13 +577,8 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupAttachment
-      (GetContext(), payload_, id, static_cast<int32_t>(contentType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.lookupAttachment
+                 (GetContext(), payload_, id, static_cast<int32_t>(contentType)));
 
     if (type_ == _OrthancPluginDatabaseAnswerType_None)
     {
@@ -787,53 +602,34 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupGlobalProperty
-      (GetContext(), payload_, static_cast<int32_t>(property));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.lookupGlobalProperty
+                 (GetContext(), payload_, static_cast<int32_t>(property)));
 
     return ForwardSingleAnswer(target);
   }
 
 
-  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& target,
+  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& result,
+                                               ResourceType level,
                                                const DicomTag& tag,
+                                               IdentifierConstraintType type,
                                                const std::string& value)
   {
-    ResetAnswers();
+    if (extensions_.lookupIdentifier3 == NULL)
+    {
+      LOG(ERROR) << "The database plugin does not implement the LookupIdentifier3 primitive";
+      throw OrthancException(ErrorCode_DatabasePlugin);
+    }
 
     OrthancPluginDicomTag tmp;
     tmp.group = tag.GetGroup();
     tmp.element = tag.GetElement();
     tmp.value = value.c_str();
 
-    OrthancPluginErrorCode error = backend_.lookupIdentifier(GetContext(), payload_, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& target,
-                                               const std::string& value)
-  {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupIdentifier2(GetContext(), payload_, value.c_str());
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
-    ForwardAnswers(target);
+    CheckSuccess(extensions_.lookupIdentifier3(GetContext(), payload_, Plugins::Convert(level),
+                                               &tmp, Plugins::Convert(type)));
+    ForwardAnswers(result);
   }
 
 
@@ -842,14 +638,7 @@
                                              MetadataType type)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)));
     return ForwardSingleAnswer(target);
   }
 
@@ -858,14 +647,7 @@
                                            int64_t resourceId)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupParent(GetContext(), payload_, resourceId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId));
     return ForwardSingleAnswer(parentId);
   }
 
@@ -876,12 +658,7 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupResource(GetContext(), payload_, publicId.c_str());
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str()));
 
     if (type_ == _OrthancPluginDatabaseAnswerType_None)
     {
@@ -904,14 +681,7 @@
   bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.selectPatientToRecycle(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_));
     return ForwardSingleAnswer(internalId);
   }
 
@@ -920,14 +690,7 @@
                                                      int64_t patientIdToAvoid)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
-
+    CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid));
     return ForwardSingleAnswer(internalId);
   }
 
@@ -935,13 +698,20 @@
   void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property,
                                                 const std::string& value)
   {
-    OrthancPluginErrorCode error = backend_.setGlobalProperty
-      (payload_, static_cast<int32_t>(property), value.c_str());
+    CheckSuccess(backend_.setGlobalProperty
+                 (payload_, static_cast<int32_t>(property), value.c_str()));
+  }
+
 
-    if (error != OrthancPluginErrorCode_Success)
+  void OrthancPluginDatabase::ClearMainDicomTags(int64_t id)
+  {
+    if (extensions_.clearMainDicomTags == NULL)
     {
-      throw OrthancException(Plugins::Convert(error));
+      LOG(ERROR) << "Your custom index plugin does not implement the ClearMainDicomTags() extension";
+      throw OrthancException(ErrorCode_DatabasePlugin);
     }
+
+    CheckSuccess(extensions_.clearMainDicomTags(payload_, id));
   }
 
 
@@ -954,21 +724,20 @@
     tmp.element = tag.GetElement();
     tmp.value = value.c_str();
 
-    OrthancPluginErrorCode error;
+    CheckSuccess(backend_.setMainDicomTag(payload_, id, &tmp));
+  }
+
 
-    if (tag.IsIdentifier())
-    {
-      error = backend_.setIdentifierTag(payload_, id, &tmp);
-    }
-    else
-    {
-      error = backend_.setMainDicomTag(payload_, id, &tmp);
-    }
+  void OrthancPluginDatabase::SetIdentifierTag(int64_t id,
+                                               const DicomTag& tag,
+                                               const std::string& value)
+  {
+    OrthancPluginDicomTag tmp;
+    tmp.group = tag.GetGroup();
+    tmp.element = tag.GetElement();
+    tmp.value = value.c_str();
 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.setIdentifierTag(payload_, id, &tmp));
   }
 
 
@@ -976,25 +745,15 @@
                                           MetadataType type,
                                           const std::string& value)
   {
-    OrthancPluginErrorCode error = backend_.setMetadata
-      (payload_, id, static_cast<int32_t>(type), value.c_str());
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.setMetadata
+                 (payload_, id, static_cast<int32_t>(type), value.c_str()));
   }
 
 
   void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, 
                                                   bool isProtected)
   {
-    OrthancPluginErrorCode error = backend_.setProtectedPatient(payload_, internalId, isProtected);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      throw OrthancException(Plugins::Convert(error));
-    }
+    CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected));
   }
 
 
@@ -1003,50 +762,47 @@
   private:
     const OrthancPluginDatabaseBackend& backend_;
     void* payload_;
+    PluginsErrorDictionary&  errorDictionary_;
+
+    void CheckSuccess(OrthancPluginErrorCode code)
+    {
+      if (code != OrthancPluginErrorCode_Success)
+      {
+        errorDictionary_.LogError(code, true);
+        throw OrthancException(static_cast<ErrorCode>(code));
+      }
+    }
 
   public:
     Transaction(const OrthancPluginDatabaseBackend& backend,
-                void* payload) :
+                void* payload,
+                PluginsErrorDictionary&  errorDictionary) :
       backend_(backend),
-      payload_(payload)
+      payload_(payload),
+      errorDictionary_(errorDictionary)
     {
     }
 
     virtual void Begin()
     {
-      OrthancPluginErrorCode error = backend_.startTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
+      CheckSuccess(backend_.startTransaction(payload_));
     }
 
     virtual void Rollback()
     {
-      OrthancPluginErrorCode error = backend_.rollbackTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
+      CheckSuccess(backend_.rollbackTransaction(payload_));
     }
 
     virtual void Commit()
     {
-      OrthancPluginErrorCode error = backend_.commitTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
+      CheckSuccess(backend_.commitTransaction(payload_));
     }
   };
 
 
   SQLite::ITransaction* OrthancPluginDatabase::StartTransaction()
   {
-    return new Transaction(backend_, payload_);
+    return new Transaction(backend_, payload_, errorDictionary_);
   }
 
 
@@ -1065,14 +821,14 @@
         
       case _OrthancPluginDatabaseAnswerType_RemainingAncestor:
       {
-        ResourceType type = Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
+        ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
         listener.SignalRemainingAncestor(type, answer.valueString);
         break;
       }
       
       case _OrthancPluginDatabaseAnswerType_DeletedResource:
       {
-        ResourceType type = Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
+        ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
         ServerIndexChange change(ChangeType_Deleted, type, answer.valueString);
         listener.SignalChange(change);
         break;
@@ -1089,13 +845,7 @@
     if (extensions_.getDatabaseVersion != NULL)
     {
       uint32_t version;
-      OrthancPluginErrorCode error = extensions_.getDatabaseVersion(&version, payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
-
+      CheckSuccess(extensions_.getDatabaseVersion(&version, payload_));
       return version;
     }
     else
@@ -1113,13 +863,22 @@
   {
     if (extensions_.upgradeDatabase != NULL)
     {
-      OrthancPluginErrorCode error = extensions_.upgradeDatabase(
+      Transaction transaction(backend_, payload_, errorDictionary_);
+      transaction.Begin();
+
+      OrthancPluginErrorCode code = extensions_.upgradeDatabase(
         payload_, targetVersion, 
         reinterpret_cast<OrthancPluginStorageArea*>(&storageArea));
 
-      if (error != OrthancPluginErrorCode_Success)
+      if (code == OrthancPluginErrorCode_Success)
       {
-        throw OrthancException(Plugins::Convert(error));
+        transaction.Commit();
+      }
+      else
+      {
+        transaction.Rollback();
+        errorDictionary_.LogError(code, true);
+        throw OrthancException(static_cast<ErrorCode>(code));
       }
     }
   }
@@ -1210,7 +969,7 @@
       case _OrthancPluginDatabaseAnswerType_Resource:
       {
         OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(answer.valueInt32);
-        answerResources_.push_back(std::make_pair(answer.valueInt64, Convert(type)));
+        answerResources_.push_back(std::make_pair(answer.valueInt64, Plugins::Convert(type)));
         break;
       }
 
@@ -1270,7 +1029,7 @@
           answerChanges_->push_back
             (ServerIndexChange(change.seq,
                                static_cast<ChangeType>(change.changeType),
-                               Convert(change.resourceType),
+                               Plugins::Convert(change.resourceType),
                                change.publicId,
                                change.date));                                   
         }
@@ -1296,7 +1055,7 @@
           assert(answerExportedResources_ != NULL);
           answerExportedResources_->push_back
             (ExportedResource(exported.seq,
-                              Convert(exported.resourceType),
+                              Plugins::Convert(exported.resourceType),
                               exported.publicId,
                               exported.modality,
                               exported.date,
--- a/Plugins/Engine/OrthancPluginDatabase.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Wed Nov 18 10:16:21 2015 +0100
@@ -36,6 +36,7 @@
 
 #include "../../OrthancServer/IDatabaseWrapper.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
+#include "PluginsErrorDictionary.h"
 #include "SharedLibrary.h"
 
 namespace Orthanc
@@ -48,6 +49,7 @@
     typedef std::pair<int64_t, ResourceType>  AnswerResource;
 
     SharedLibrary&  library_;
+    PluginsErrorDictionary&  errorDictionary_;
     _OrthancPluginDatabaseAnswerType type_;
     OrthancPluginDatabaseBackend backend_;
     OrthancPluginDatabaseExtensions extensions_;
@@ -70,6 +72,8 @@
       return reinterpret_cast<OrthancPluginDatabaseContext*>(this);
     }
 
+    void CheckSuccess(OrthancPluginErrorCode code);
+
     void ResetAnswers();
 
     void ForwardAnswers(std::list<int64_t>& target);
@@ -82,11 +86,22 @@
 
   public:
     OrthancPluginDatabase(SharedLibrary& library,
+                          PluginsErrorDictionary&  errorDictionary,
                           const OrthancPluginDatabaseBackend& backend,
                           const OrthancPluginDatabaseExtensions* extensions,
                           size_t extensionsSize,
                           void *payload);
 
+    virtual void Open()
+    {
+      CheckSuccess(backend_.open(payload_));
+    }
+
+    virtual void Close()
+    {
+      CheckSuccess(backend_.close(payload_));
+    }
+
     const SharedLibrary& GetSharedLibrary() const
     {
       return library_;
@@ -125,6 +140,9 @@
     virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
                                 int64_t id);
 
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   ResourceType resourceType);
+
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  ResourceType resourceType);
 
@@ -188,11 +206,10 @@
     virtual bool LookupGlobalProperty(std::string& target,
                                       GlobalProperty property);
 
-    virtual void LookupIdentifier(std::list<int64_t>& target,
+    virtual void LookupIdentifier(std::list<int64_t>& result,
+                                  ResourceType level,
                                   const DicomTag& tag,
-                                  const std::string& value);
-
-    virtual void LookupIdentifier(std::list<int64_t>& target,
+                                  IdentifierConstraintType type,
                                   const std::string& value);
 
     virtual bool LookupMetadata(std::string& target,
@@ -214,10 +231,16 @@
     virtual void SetGlobalProperty(GlobalProperty property,
                                    const std::string& value);
 
+    virtual void ClearMainDicomTags(int64_t id);
+
     virtual void SetMainDicomTag(int64_t id,
                                  const DicomTag& tag,
                                  const std::string& value);
 
+    virtual void SetIdentifierTag(int64_t id,
+                                  const DicomTag& tag,
+                                  const std::string& value);
+
     virtual void SetMetadata(int64_t id,
                              MetadataType type,
                              const std::string& value);
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -43,6 +43,7 @@
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 #include "../../Core/Toolbox.h"
+#include "../../OrthancServer/FromDcmtkBridge.h"
 #include "../../OrthancServer/OrthancInitialization.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/ServerToolbox.h"
@@ -66,6 +67,7 @@
     {
     private:
       _OrthancPluginRegisterStorageArea callbacks_;
+      PluginsErrorDictionary&  errorDictionary_;
 
       void Free(void* buffer) const
       {
@@ -76,7 +78,10 @@
       }
 
     public:
-      PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks) : callbacks_(callbacks)
+      PluginStorageArea(const _OrthancPluginRegisterStorageArea& callbacks,
+                        PluginsErrorDictionary&  errorDictionary) : 
+        callbacks_(callbacks),
+        errorDictionary_(errorDictionary)
       {
       }
 
@@ -91,7 +96,8 @@
 
         if (error != OrthancPluginErrorCode_Success)
         {
-          throw OrthancException(Plugins::Convert(error));
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
         }
       }
 
@@ -108,7 +114,8 @@
 
         if (error != OrthancPluginErrorCode_Success)
         {
-          throw OrthancException(Plugins::Convert(error));
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
         }
 
         try
@@ -118,7 +125,7 @@
         catch (...)
         {
           Free(buffer);
-          throw;
+          throw OrthancException(ErrorCode_NotEnoughMemory);
         }
 
         if (size > 0)
@@ -138,7 +145,8 @@
 
         if (error != OrthancPluginErrorCode_Success)
         {
-          throw OrthancException(Plugins::Convert(error));
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
         }
       }
     };
@@ -149,12 +157,15 @@
     private:
       SharedLibrary&   sharedLibrary_;
       _OrthancPluginRegisterStorageArea  callbacks_;
+      PluginsErrorDictionary&  errorDictionary_;
 
     public:
       StorageAreaFactory(SharedLibrary& sharedLibrary,
-                         const _OrthancPluginRegisterStorageArea& callbacks) :
+                         const _OrthancPluginRegisterStorageArea& callbacks,
+                         PluginsErrorDictionary&  errorDictionary) :
         sharedLibrary_(sharedLibrary),
-        callbacks_(callbacks)
+        callbacks_(callbacks),
+        errorDictionary_(errorDictionary)
       {
       }
 
@@ -165,7 +176,7 @@
 
       IStorageArea* Create() const
       {
-        return new PluginStorageArea(callbacks_);
+        return new PluginStorageArea(callbacks_, errorDictionary_);
       }
     };
   }
@@ -242,6 +253,7 @@
     int argc_;
     char** argv_;
     std::auto_ptr<OrthancPluginDatabase>  database_;
+    PluginsErrorDictionary  dictionary_;
 
     PImpl() : 
       context_(NULL), 
@@ -286,7 +298,17 @@
         sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
         sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
         sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
-        sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType))
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginDatabaseAnswerType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePixelData) != static_cast<int>(DicomToJsonFlags_IncludePixelData) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToNull) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToNull) ||
+        static_cast<int>(OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii) != static_cast<int>(DicomToJsonFlags_ConvertBinaryToAscii))
     {
       /* Sanity check of the compiler */
       throw OrthancException(ErrorCode_Plugin);
@@ -463,7 +485,8 @@
     }
     else
     {
-      throw OrthancException(Plugins::Convert(error));
+      GetErrorDictionary().LogError(error, true);
+      throw OrthancException(static_cast<ErrorCode>(error));
     }
   }
 
@@ -484,7 +507,30 @@
 
       if (error != OrthancPluginErrorCode_Success)
       {
-        throw OrthancException(Plugins::Convert(error));
+        GetErrorDictionary().LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
+    }
+  }
+
+
+
+  void OrthancPlugins::SignalChangeInternal(OrthancPluginChangeType changeType,
+                                            OrthancPluginResourceType resourceType,
+                                            const char* resource)
+  {
+    boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
+
+    for (std::list<OrthancPluginOnChangeCallback>::const_iterator 
+           callback = pimpl_->onChangeCallbacks_.begin(); 
+         callback != pimpl_->onChangeCallbacks_.end(); ++callback)
+    {
+      OrthancPluginErrorCode error = (*callback) (changeType, resourceType, resource);
+
+      if (error != OrthancPluginErrorCode_Success)
+      {
+        GetErrorDictionary().LogError(error, true);
+        throw OrthancException(static_cast<ErrorCode>(error));
       }
     }
   }
@@ -493,22 +539,9 @@
 
   void OrthancPlugins::SignalChange(const ServerIndexChange& change)
   {
-    boost::recursive_mutex::scoped_lock lock(pimpl_->changeCallbackMutex_);
-
-    for (std::list<OrthancPluginOnChangeCallback>::const_iterator 
-           callback = pimpl_->onChangeCallbacks_.begin(); 
-         callback != pimpl_->onChangeCallbacks_.end(); ++callback)
-    {
-      OrthancPluginErrorCode error = (*callback)
-        (Plugins::Convert(change.GetChangeType()),
-         Plugins::Convert(change.GetResourceType()),
-         change.GetPublicId().c_str());
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        throw OrthancException(Plugins::Convert(error));
-      }
-    }
+    SignalChangeInternal(Plugins::Convert(change.GetChangeType()),
+                         Plugins::Convert(change.GetResourceType()),
+                         change.GetPublicId().c_str());
   }
 
 
@@ -743,8 +776,7 @@
   {
     if (!pimpl_->context_)
     {
-      LOG(ERROR) << "Plugin trying to call the database during its initialization";
-      throw OrthancException(ErrorCode_Plugin);
+      throw OrthancException(ErrorCode_DatabaseNotInitialized);
     }
   }
 
@@ -787,6 +819,36 @@
   }
 
 
+  void OrthancPlugins::RestApiGet2(const void* parameters)
+  {
+    const _OrthancPluginRestApiGet2& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiGet2*>(parameters);
+        
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri
+              << (p.afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    IHttpHandler::Arguments headers;
+
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      headers[p.headersKeys[i]] = p.headersValues[i];
+    }
+
+    CheckContextAvailable();
+    IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
+
+    std::string result;
+    if (HttpToolbox::SimpleGet(result, handler, RequestOrigin_Plugins, p.uri, headers))
+    {
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
   void OrthancPlugins::RestApiPostPut(bool isPost, 
                                       const void* parameters,
                                       bool afterPlugins)
@@ -880,7 +942,7 @@
     CheckContextAvailable();
 
     std::list<std::string> result;
-    pimpl_->context_->GetIndex().LookupIdentifier(result, tag, p.argument, level);
+    pimpl_->context_->GetIndex().LookupIdentifierExact(result, level, tag, p.argument);
 
     if (result.size() == 1)
     {
@@ -991,7 +1053,7 @@
         else
         {
           Json::Value simplified;
-          SimplifyTags(simplified, instance.GetJson());
+          Toolbox::SimplifyTags(simplified, instance.GetJson());
           s = writer.write(simplified);
         }
 
@@ -1214,15 +1276,78 @@
 
     font.Draw(target, p.utf8Text, p.x, p.y, p.r, p.g, p.b);
   }
+
+
+  void OrthancPlugins::ApplyDicomToJson(_OrthancPluginService service,
+                                        const void* parameters)
+  {
+    const _OrthancPluginDicomToJson& p =
+      *reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters);
+
+    std::auto_ptr<ParsedDicomFile> dicom;
+
+    if (service == _OrthancPluginService_DicomBufferToJson)
+    {
+      dicom.reset(new ParsedDicomFile(p.buffer, p.size));
+    }
+    else
+    {
+      if (p.instanceId == NULL)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      std::string content;
+      pimpl_->context_->ReadFile(content, p.instanceId, FileContentType_Dicom);
+      dicom.reset(new ParsedDicomFile(content));
+    }
+
+    Json::Value json;
+    dicom->ToJson(json, Plugins::Convert(p.format), 
+                  static_cast<DicomToJsonFlags>(p.flags), p.maxStringLength);
+
+    Json::FastWriter writer;
+    *p.result = CopyString(writer.write(json));
+  }
         
 
+  void OrthancPlugins::DatabaseAnswer(const void* parameters)
+  {
+    const _OrthancPluginDatabaseAnswer& p =
+      *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
+
+    if (pimpl_->database_.get() != NULL)
+    {
+      pimpl_->database_->AnswerReceived(p);
+    }
+    else
+    {
+      LOG(ERROR) << "Cannot invoke this service without a custom database back-end";
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
   bool OrthancPlugins::InvokeService(SharedLibrary& plugin,
                                      _OrthancPluginService service,
                                      const void* parameters)
   {
     VLOG(1) << "Calling service " << service << " from plugin " << plugin.GetPath();
 
-    boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
+    if (service == _OrthancPluginService_DatabaseAnswer)
+    {
+      // This case solves a deadlock at (*) reported by James Webster
+      // on 2015-10-27 that was present in versions of Orthanc <=
+      // 0.9.4 and related to database plugins implementing a custom
+      // index. The problem was that locking the database is already
+      // ensured by the "ServerIndex" class if the invoked service is
+      // "DatabaseAnswer".
+      DatabaseAnswer(parameters);
+      return true;
+    }
+
+
+    std::auto_ptr<boost::recursive_mutex::scoped_lock> lock;   // (*)
 
     switch (service)
     {
@@ -1300,6 +1425,10 @@
         RestApiGet(parameters, true);
         return true;
 
+      case _OrthancPluginService_RestApiGet2:
+        RestApiGet2(parameters);
+        return true;
+
       case _OrthancPluginService_RestApiPost:
         RestApiPostPut(true, parameters, false);
         return true;
@@ -1378,7 +1507,7 @@
         
         if (pimpl_->storageArea_.get() == NULL)
         {
-          pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p));
+          pimpl_->storageArea_.reset(new StorageAreaFactory(plugin, p, GetErrorDictionary()));
         }
         else
         {
@@ -1457,7 +1586,8 @@
 
         if (pimpl_->database_.get() == NULL)
         {
-          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, *p.backend, NULL, 0, p.payload));
+          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), 
+                                                            *p.backend, NULL, 0, p.payload));
         }
         else
         {
@@ -1478,7 +1608,8 @@
 
         if (pimpl_->database_.get() == NULL)
         {
-          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, *p.backend, p.extensions,
+          pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
+                                                            *p.backend, p.extensions,
                                                             p.extensionsSize, p.payload));
         }
         else
@@ -1492,21 +1623,7 @@
       }
 
       case _OrthancPluginService_DatabaseAnswer:
-      {
-        const _OrthancPluginDatabaseAnswer& p =
-          *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
-
-        if (pimpl_->database_.get() != NULL)
-        {
-          pimpl_->database_->AnswerReceived(p);
-          return true;
-        }
-        else
-        {
-          LOG(ERROR) << "Cannot invoke this service without a custom database back-end";
-          throw OrthancException(ErrorCode_BadRequest);
-        }
-      }
+        throw OrthancException(ErrorCode_InternalError);   // Implemented before locking (*)
 
       case _OrthancPluginService_GetExpectedDatabaseVersion:
       {
@@ -1674,6 +1791,46 @@
         return true;
       }
 
+      case _OrthancPluginService_RegisterErrorCode:
+      {
+        const _OrthancPluginRegisterErrorCode& p =
+          *reinterpret_cast<const _OrthancPluginRegisterErrorCode*>(parameters);
+        *(p.target) = pimpl_->dictionary_.Register(plugin, p.code, p.httpStatus, p.message);
+        return true;
+      }
+
+      case _OrthancPluginService_RegisterDictionaryTag:
+      {
+        const _OrthancPluginRegisterDictionaryTag& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDictionaryTag*>(parameters);
+        FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element),
+                                               Plugins::Convert(p.vr), p.name,
+                                               p.minMultiplicity, p.maxMultiplicity);
+        return true;
+      }
+
+      case _OrthancPluginService_ReconstructMainDicomTags:
+      {
+        const _OrthancPluginReconstructMainDicomTags& p =
+          *reinterpret_cast<const _OrthancPluginReconstructMainDicomTags*>(parameters);
+
+        if (pimpl_->database_.get() == NULL)
+        {
+          LOG(ERROR) << "The service ReconstructMainDicomTags can only be invoked by custom database plugins";
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+
+        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+        Toolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level));
+
+        return true;
+      }
+
+      case _OrthancPluginService_DicomBufferToJson:
+      case _OrthancPluginService_DicomInstanceToJson:
+        ApplyDicomToJson(service, parameters);
+        return true;
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
@@ -1785,4 +1942,10 @@
   {
     return pimpl_->manager_;
   }
+
+
+  PluginsErrorDictionary&  OrthancPlugins::GetErrorDictionary()
+  {
+    return pimpl_->dictionary_;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Wed Nov 18 10:16:21 2015 +0100
@@ -32,6 +32,8 @@
 
 #pragma once
 
+#include "PluginsErrorDictionary.h"
+
 #if ORTHANC_PLUGINS_ENABLED != 1
 
 #include <boost/noncopyable.hpp>
@@ -89,6 +91,8 @@
     void RestApiGet(const void* parameters,
                     bool afterPlugins);
 
+    void RestApiGet2(const void* parameters);
+
     void RestApiPostPut(bool isPost, 
                         const void* parameters,
                         bool afterPlugins);
@@ -125,6 +129,15 @@
 
     void DrawText(const void* parameters);
 
+    void DatabaseAnswer(const void* parameters);
+
+    void ApplyDicomToJson(_OrthancPluginService service,
+                          const void* parameters);
+
+    void SignalChangeInternal(OrthancPluginChangeType changeType,
+                              OrthancPluginResourceType resourceType,
+                              const char* resource);
+
   public:
     OrthancPlugins();
 
@@ -179,6 +192,18 @@
     PluginsManager& GetManager();
 
     const PluginsManager& GetManager() const;
+
+    PluginsErrorDictionary& GetErrorDictionary();
+
+    void SignalOrthancStarted()
+    {
+      SignalChangeInternal(OrthancPluginChangeType_OrthancStarted, OrthancPluginResourceType_None, NULL);
+    }
+
+    void SignalOrthancStopped()
+    {
+      SignalChangeInternal(OrthancPluginChangeType_OrthancStopped, OrthancPluginResourceType_None, NULL);
+    }
   };
 }
 
--- a/Plugins/Engine/PluginsEnumerations.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/PluginsEnumerations.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -44,277 +44,6 @@
 {
   namespace Plugins
   {
-    ErrorCode Convert(OrthancPluginErrorCode error)
-    {
-      switch (error)
-      {
-        case OrthancPluginErrorCode_InternalError:
-          return ErrorCode_InternalError;
-
-        case OrthancPluginErrorCode_Success:
-          return ErrorCode_Success;
-
-        case OrthancPluginErrorCode_Plugin:
-          return ErrorCode_Plugin;
-
-        case OrthancPluginErrorCode_NotImplemented:
-          return ErrorCode_NotImplemented;
-
-        case OrthancPluginErrorCode_ParameterOutOfRange:
-          return ErrorCode_ParameterOutOfRange;
-
-        case OrthancPluginErrorCode_NotEnoughMemory:
-          return ErrorCode_NotEnoughMemory;
-
-        case OrthancPluginErrorCode_BadParameterType:
-          return ErrorCode_BadParameterType;
-
-        case OrthancPluginErrorCode_BadSequenceOfCalls:
-          return ErrorCode_BadSequenceOfCalls;
-
-        case OrthancPluginErrorCode_InexistentItem:
-          return ErrorCode_InexistentItem;
-
-        case OrthancPluginErrorCode_BadRequest:
-          return ErrorCode_BadRequest;
-
-        case OrthancPluginErrorCode_NetworkProtocol:
-          return ErrorCode_NetworkProtocol;
-
-        case OrthancPluginErrorCode_SystemCommand:
-          return ErrorCode_SystemCommand;
-
-        case OrthancPluginErrorCode_Database:
-          return ErrorCode_Database;
-
-        case OrthancPluginErrorCode_UriSyntax:
-          return ErrorCode_UriSyntax;
-
-        case OrthancPluginErrorCode_InexistentFile:
-          return ErrorCode_InexistentFile;
-
-        case OrthancPluginErrorCode_CannotWriteFile:
-          return ErrorCode_CannotWriteFile;
-
-        case OrthancPluginErrorCode_BadFileFormat:
-          return ErrorCode_BadFileFormat;
-
-        case OrthancPluginErrorCode_Timeout:
-          return ErrorCode_Timeout;
-
-        case OrthancPluginErrorCode_UnknownResource:
-          return ErrorCode_UnknownResource;
-
-        case OrthancPluginErrorCode_IncompatibleDatabaseVersion:
-          return ErrorCode_IncompatibleDatabaseVersion;
-
-        case OrthancPluginErrorCode_FullStorage:
-          return ErrorCode_FullStorage;
-
-        case OrthancPluginErrorCode_CorruptedFile:
-          return ErrorCode_CorruptedFile;
-
-        case OrthancPluginErrorCode_InexistentTag:
-          return ErrorCode_InexistentTag;
-
-        case OrthancPluginErrorCode_ReadOnly:
-          return ErrorCode_ReadOnly;
-
-        case OrthancPluginErrorCode_IncompatibleImageFormat:
-          return ErrorCode_IncompatibleImageFormat;
-
-        case OrthancPluginErrorCode_IncompatibleImageSize:
-          return ErrorCode_IncompatibleImageSize;
-
-        case OrthancPluginErrorCode_SharedLibrary:
-          return ErrorCode_SharedLibrary;
-
-        case OrthancPluginErrorCode_UnknownPluginService:
-          return ErrorCode_UnknownPluginService;
-
-        case OrthancPluginErrorCode_UnknownDicomTag:
-          return ErrorCode_UnknownDicomTag;
-
-        case OrthancPluginErrorCode_BadJson:
-          return ErrorCode_BadJson;
-
-        case OrthancPluginErrorCode_Unauthorized:
-          return ErrorCode_Unauthorized;
-
-        case OrthancPluginErrorCode_BadFont:
-          return ErrorCode_BadFont;
-
-        case OrthancPluginErrorCode_SQLiteNotOpened:
-          return ErrorCode_SQLiteNotOpened;
-
-        case OrthancPluginErrorCode_SQLiteAlreadyOpened:
-          return ErrorCode_SQLiteAlreadyOpened;
-
-        case OrthancPluginErrorCode_SQLiteCannotOpen:
-          return ErrorCode_SQLiteCannotOpen;
-
-        case OrthancPluginErrorCode_SQLiteStatementAlreadyUsed:
-          return ErrorCode_SQLiteStatementAlreadyUsed;
-
-        case OrthancPluginErrorCode_SQLiteExecute:
-          return ErrorCode_SQLiteExecute;
-
-        case OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction:
-          return ErrorCode_SQLiteRollbackWithoutTransaction;
-
-        case OrthancPluginErrorCode_SQLiteCommitWithoutTransaction:
-          return ErrorCode_SQLiteCommitWithoutTransaction;
-
-        case OrthancPluginErrorCode_SQLiteRegisterFunction:
-          return ErrorCode_SQLiteRegisterFunction;
-
-        case OrthancPluginErrorCode_SQLiteFlush:
-          return ErrorCode_SQLiteFlush;
-
-        case OrthancPluginErrorCode_SQLiteCannotRun:
-          return ErrorCode_SQLiteCannotRun;
-
-        case OrthancPluginErrorCode_SQLiteCannotStep:
-          return ErrorCode_SQLiteCannotStep;
-
-        case OrthancPluginErrorCode_SQLiteBindOutOfRange:
-          return ErrorCode_SQLiteBindOutOfRange;
-
-        case OrthancPluginErrorCode_SQLitePrepareStatement:
-          return ErrorCode_SQLitePrepareStatement;
-
-        case OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted:
-          return ErrorCode_SQLiteTransactionAlreadyStarted;
-
-        case OrthancPluginErrorCode_SQLiteTransactionCommit:
-          return ErrorCode_SQLiteTransactionCommit;
-
-        case OrthancPluginErrorCode_SQLiteTransactionBegin:
-          return ErrorCode_SQLiteTransactionBegin;
-
-        case OrthancPluginErrorCode_DirectoryOverFile:
-          return ErrorCode_DirectoryOverFile;
-
-        case OrthancPluginErrorCode_FileStorageCannotWrite:
-          return ErrorCode_FileStorageCannotWrite;
-
-        case OrthancPluginErrorCode_DirectoryExpected:
-          return ErrorCode_DirectoryExpected;
-
-        case OrthancPluginErrorCode_HttpPortInUse:
-          return ErrorCode_HttpPortInUse;
-
-        case OrthancPluginErrorCode_DicomPortInUse:
-          return ErrorCode_DicomPortInUse;
-
-        case OrthancPluginErrorCode_BadHttpStatusInRest:
-          return ErrorCode_BadHttpStatusInRest;
-
-        case OrthancPluginErrorCode_RegularFileExpected:
-          return ErrorCode_RegularFileExpected;
-
-        case OrthancPluginErrorCode_PathToExecutable:
-          return ErrorCode_PathToExecutable;
-
-        case OrthancPluginErrorCode_MakeDirectory:
-          return ErrorCode_MakeDirectory;
-
-        case OrthancPluginErrorCode_BadApplicationEntityTitle:
-          return ErrorCode_BadApplicationEntityTitle;
-
-        case OrthancPluginErrorCode_NoCFindHandler:
-          return ErrorCode_NoCFindHandler;
-
-        case OrthancPluginErrorCode_NoCMoveHandler:
-          return ErrorCode_NoCMoveHandler;
-
-        case OrthancPluginErrorCode_NoCStoreHandler:
-          return ErrorCode_NoCStoreHandler;
-
-        case OrthancPluginErrorCode_NoApplicationEntityFilter:
-          return ErrorCode_NoApplicationEntityFilter;
-
-        case OrthancPluginErrorCode_NoSopClassOrInstance:
-          return ErrorCode_NoSopClassOrInstance;
-
-        case OrthancPluginErrorCode_NoPresentationContext:
-          return ErrorCode_NoPresentationContext;
-
-        case OrthancPluginErrorCode_DicomFindUnavailable:
-          return ErrorCode_DicomFindUnavailable;
-
-        case OrthancPluginErrorCode_DicomMoveUnavailable:
-          return ErrorCode_DicomMoveUnavailable;
-
-        case OrthancPluginErrorCode_CannotStoreInstance:
-          return ErrorCode_CannotStoreInstance;
-
-        case OrthancPluginErrorCode_CreateDicomNotString:
-          return ErrorCode_CreateDicomNotString;
-
-        case OrthancPluginErrorCode_CreateDicomOverrideTag:
-          return ErrorCode_CreateDicomOverrideTag;
-
-        case OrthancPluginErrorCode_CreateDicomUseContent:
-          return ErrorCode_CreateDicomUseContent;
-
-        case OrthancPluginErrorCode_CreateDicomNoPayload:
-          return ErrorCode_CreateDicomNoPayload;
-
-        case OrthancPluginErrorCode_CreateDicomUseDataUriScheme:
-          return ErrorCode_CreateDicomUseDataUriScheme;
-
-        case OrthancPluginErrorCode_CreateDicomBadParent:
-          return ErrorCode_CreateDicomBadParent;
-
-        case OrthancPluginErrorCode_CreateDicomParentIsInstance:
-          return ErrorCode_CreateDicomParentIsInstance;
-
-        case OrthancPluginErrorCode_CreateDicomParentEncoding:
-          return ErrorCode_CreateDicomParentEncoding;
-
-        case OrthancPluginErrorCode_UnknownModality:
-          return ErrorCode_UnknownModality;
-
-        case OrthancPluginErrorCode_BadJobOrdering:
-          return ErrorCode_BadJobOrdering;
-
-        case OrthancPluginErrorCode_JsonToLuaTable:
-          return ErrorCode_JsonToLuaTable;
-
-        case OrthancPluginErrorCode_CannotCreateLua:
-          return ErrorCode_CannotCreateLua;
-
-        case OrthancPluginErrorCode_CannotExecuteLua:
-          return ErrorCode_CannotExecuteLua;
-
-        case OrthancPluginErrorCode_LuaAlreadyExecuted:
-          return ErrorCode_LuaAlreadyExecuted;
-
-        case OrthancPluginErrorCode_LuaBadOutput:
-          return ErrorCode_LuaBadOutput;
-
-        case OrthancPluginErrorCode_NotLuaPredicate:
-          return ErrorCode_NotLuaPredicate;
-
-        case OrthancPluginErrorCode_LuaReturnsNoString:
-          return ErrorCode_LuaReturnsNoString;
-
-        case OrthancPluginErrorCode_StorageAreaAlreadyRegistered:
-          return ErrorCode_StorageAreaAlreadyRegistered;
-
-        case OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered:
-          return ErrorCode_DatabaseBackendAlreadyRegistered;
-
-        case OrthancPluginErrorCode_DatabasePlugin:
-          return ErrorCode_DatabasePlugin;
-
-        default:
-          return ErrorCode_Plugin;
-      }
-    }
-
-
     OrthancPluginResourceType Convert(ResourceType type)
     {
       switch (type)
@@ -337,6 +66,28 @@
     }
 
 
+    ResourceType Convert(OrthancPluginResourceType type)
+    {
+      switch (type)
+      {
+        case OrthancPluginResourceType_Patient:
+          return ResourceType_Patient;
+
+        case OrthancPluginResourceType_Study:
+          return ResourceType_Study;
+
+        case OrthancPluginResourceType_Series:
+          return ResourceType_Series;
+
+        case OrthancPluginResourceType_Instance:
+          return ResourceType_Instance;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
     OrthancPluginChangeType Convert(ChangeType type)
     {
       switch (type)
@@ -371,6 +122,12 @@
         case ChangeType_StableStudy:
           return OrthancPluginChangeType_StableStudy;
 
+        case ChangeType_UpdatedAttachment:
+          return OrthancPluginChangeType_UpdatedAttachment;
+
+        case ChangeType_UpdatedMetadata:
+          return OrthancPluginChangeType_UpdatedMetadata;
+
         default:
           throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
@@ -457,5 +214,161 @@
           return FileContentType_Unknown;
       }
     }
+
+
+    DicomToJsonFormat Convert(OrthancPluginDicomToJsonFormat format)
+    {
+      switch (format)
+      {
+        case OrthancPluginDicomToJsonFormat_Full:
+          return DicomToJsonFormat_Full;
+
+        case OrthancPluginDicomToJsonFormat_Short:
+          return DicomToJsonFormat_Short;
+
+        case OrthancPluginDicomToJsonFormat_Simple:
+          return DicomToJsonFormat_Simple;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
+    OrthancPluginIdentifierConstraint Convert(IdentifierConstraintType constraint)
+    {
+      switch (constraint)
+      {
+        case IdentifierConstraintType_Equal:
+          return OrthancPluginIdentifierConstraint_Equal;
+
+        case IdentifierConstraintType_GreaterOrEqual:
+          return OrthancPluginIdentifierConstraint_GreaterOrEqual;
+
+        case IdentifierConstraintType_SmallerOrEqual:
+          return OrthancPluginIdentifierConstraint_SmallerOrEqual;
+
+        case IdentifierConstraintType_Wildcard:
+          return OrthancPluginIdentifierConstraint_Wildcard;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
+    IdentifierConstraintType Convert(OrthancPluginIdentifierConstraint constraint)
+    {
+      switch (constraint)
+      {
+        case OrthancPluginIdentifierConstraint_Equal:
+          return IdentifierConstraintType_Equal;
+
+        case OrthancPluginIdentifierConstraint_GreaterOrEqual:
+          return IdentifierConstraintType_GreaterOrEqual;
+
+        case OrthancPluginIdentifierConstraint_SmallerOrEqual:
+          return IdentifierConstraintType_SmallerOrEqual;
+
+        case OrthancPluginIdentifierConstraint_Wildcard:
+          return IdentifierConstraintType_Wildcard;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+
+
+#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
+    DcmEVR Convert(OrthancPluginValueRepresentation vr)
+    {
+      switch (vr)
+      {
+        case OrthancPluginValueRepresentation_AE:
+          return EVR_AE;
+
+        case OrthancPluginValueRepresentation_AS:
+          return EVR_AS;
+
+        case OrthancPluginValueRepresentation_AT:
+          return EVR_AT;
+
+        case OrthancPluginValueRepresentation_CS:
+          return EVR_CS;
+
+        case OrthancPluginValueRepresentation_DA:
+          return EVR_DA;
+
+        case OrthancPluginValueRepresentation_DS:
+          return EVR_DS;
+
+        case OrthancPluginValueRepresentation_DT:
+          return EVR_DT;
+
+        case OrthancPluginValueRepresentation_FD:
+          return EVR_FD;
+
+        case OrthancPluginValueRepresentation_FL:
+          return EVR_FL;
+
+        case OrthancPluginValueRepresentation_IS:
+          return EVR_IS;
+
+        case OrthancPluginValueRepresentation_LO:
+          return EVR_LO;
+
+        case OrthancPluginValueRepresentation_LT:
+          return EVR_LT;
+
+        case OrthancPluginValueRepresentation_OB:
+          return EVR_OB;
+
+        case OrthancPluginValueRepresentation_OF:
+          return EVR_OF;
+
+        case OrthancPluginValueRepresentation_OW:
+          return EVR_OW;
+
+        case OrthancPluginValueRepresentation_PN:
+          return EVR_PN;
+
+        case OrthancPluginValueRepresentation_SH:
+          return EVR_SH;
+
+        case OrthancPluginValueRepresentation_SL:
+          return EVR_SL;
+
+        case OrthancPluginValueRepresentation_SQ:
+          return EVR_SQ;
+
+        case OrthancPluginValueRepresentation_SS:
+          return EVR_SS;
+
+        case OrthancPluginValueRepresentation_ST:
+          return EVR_ST;
+
+        case OrthancPluginValueRepresentation_TM:
+          return EVR_TM;
+
+        case OrthancPluginValueRepresentation_UI:
+          return EVR_UI;
+
+        case OrthancPluginValueRepresentation_UL:
+          return EVR_UL;
+
+        case OrthancPluginValueRepresentation_UN:
+          return EVR_UN;
+
+        case OrthancPluginValueRepresentation_US:
+          return EVR_US;
+
+        case OrthancPluginValueRepresentation_UT:
+          return EVR_UT;
+
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
+#endif
   }
 }
--- a/Plugins/Engine/PluginsEnumerations.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/PluginsEnumerations.h	Wed Nov 18 10:16:21 2015 +0100
@@ -37,13 +37,17 @@
 #include "../Include/orthanc/OrthancCPlugin.h"
 #include "../../OrthancServer/ServerEnumerations.h"
 
+#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
+#include <dcmtk/dcmdata/dcvr.h>
+#endif
+
 namespace Orthanc
 {
   namespace Plugins
   {
-    ErrorCode Convert(OrthancPluginErrorCode error);
+    OrthancPluginResourceType Convert(ResourceType type);
 
-    OrthancPluginResourceType Convert(ResourceType type);
+    ResourceType Convert(OrthancPluginResourceType type);
 
     OrthancPluginChangeType Convert(ChangeType type);
 
@@ -54,6 +58,16 @@
     OrthancPluginContentType Convert(FileContentType type);
 
     FileContentType Convert(OrthancPluginContentType type);
+
+    DicomToJsonFormat Convert(OrthancPluginDicomToJsonFormat format);
+
+    OrthancPluginIdentifierConstraint Convert(IdentifierConstraintType constraint);
+
+    IdentifierConstraintType Convert(OrthancPluginIdentifierConstraint constraint);
+
+#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
+    DcmEVR Convert(OrthancPluginValueRepresentation vr);
+#endif
   }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsErrorDictionary.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,138 @@
+/**
+ * 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 "../../OrthancServer/PrecompiledHeadersServer.h"
+#include "PluginsErrorDictionary.h"
+
+#if ORTHANC_PLUGINS_ENABLED != 1
+#error The plugin support is disabled
+#endif
+
+
+
+#include "PluginsEnumerations.h"
+#include "PluginsManager.h"
+#include "../../Core/Logging.h"
+
+#include <memory>
+
+
+namespace Orthanc
+{
+  PluginsErrorDictionary::PluginsErrorDictionary() : 
+    pos_(ErrorCode_START_PLUGINS)
+  {
+  }
+
+
+  PluginsErrorDictionary::~PluginsErrorDictionary()
+  {
+    for (Errors::iterator it = errors_.begin(); it != errors_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+
+  OrthancPluginErrorCode PluginsErrorDictionary::Register(SharedLibrary& library,
+                                                          int32_t  pluginCode,
+                                                          uint16_t httpStatus,
+                                                          const char* message)
+  {
+    std::auto_ptr<Error> error(new Error);
+
+    error->pluginName_ = PluginsManager::GetPluginName(library);
+    error->pluginCode_ = pluginCode;
+    error->message_ = message;
+    error->httpStatus_ = static_cast<HttpStatus>(httpStatus);
+
+    OrthancPluginErrorCode code;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      errors_[pos_] = error.release();
+      code = static_cast<OrthancPluginErrorCode>(pos_);
+      pos_ += 1;
+    }
+
+    return code;
+  }
+
+
+  void  PluginsErrorDictionary::LogError(ErrorCode code,
+                                         bool ignoreBuiltinErrors)
+  {
+    if (code >= ErrorCode_START_PLUGINS)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Errors::const_iterator error = errors_.find(static_cast<int32_t>(code));
+      
+      if (error != errors_.end())
+      {
+        LOG(ERROR) << "Error code " << error->second->pluginCode_
+                   << " inside plugin \"" << error->second->pluginName_
+                   << "\": " << error->second->message_;
+        return;
+      }
+    }
+
+    if (!ignoreBuiltinErrors)
+    {
+      LOG(ERROR) << "Exception inside the plugin engine: "
+                 << EnumerationToString(code);
+    }
+  }
+
+
+  bool  PluginsErrorDictionary::Format(Json::Value& message,    /* out */
+                                       HttpStatus& httpStatus,  /* out */
+                                       const OrthancException& exception)
+  {
+    if (exception.GetErrorCode() >= ErrorCode_START_PLUGINS)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Errors::const_iterator error = errors_.find(static_cast<int32_t>(exception.GetErrorCode()));
+      
+      if (error != errors_.end())
+      {
+        httpStatus = error->second->httpStatus_;
+        message["PluginName"] = error->second->pluginName_;
+        message["PluginCode"] = error->second->pluginCode_;
+        message["Message"] = error->second->message_;
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsErrorDictionary.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,92 @@
+/**
+ * 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
+
+#if ORTHANC_PLUGINS_ENABLED == 1
+
+#include "../Include/orthanc/OrthancCPlugin.h"
+#include "../../Core/OrthancException.h"
+#include "SharedLibrary.h"
+
+#include <map>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include <boost/thread/mutex.hpp>
+#include <json/value.h>
+
+
+namespace Orthanc
+{
+  class PluginsErrorDictionary : public boost::noncopyable
+  {
+  private:
+    struct Error
+    {
+      std::string  pluginName_;
+      int32_t      pluginCode_;
+      HttpStatus   httpStatus_;
+      std::string  message_;
+    };
+    
+    typedef std::map<int32_t, Error*>  Errors;
+
+    boost::mutex  mutex_;
+    int32_t  pos_;
+    Errors   errors_;
+
+  public:
+    PluginsErrorDictionary();
+
+    ~PluginsErrorDictionary();
+
+    OrthancPluginErrorCode  Register(SharedLibrary& library,
+                                     int32_t  pluginCode,
+                                     uint16_t httpStatus,
+                                     const char* message);
+
+    void  LogError(ErrorCode code,
+                   bool ignoreBuiltinErrors);
+
+    void  LogError(OrthancPluginErrorCode code,
+                   bool ignoreBuiltinErrors)
+    {
+      LogError(static_cast<ErrorCode>(code), ignoreBuiltinErrors);
+    }
+
+    bool  Format(Json::Value& message,    /* out */
+                 HttpStatus& httpStatus,  /* out */
+                 const OrthancException& exception);
+  };
+}
+
+#endif
--- a/Plugins/Engine/PluginsManager.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/PluginsManager.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -191,7 +191,7 @@
       catch (OrthancException& e)
       {
         // This service provider has failed
-        LOG(ERROR) << "Exception while invoking a plugin service: " << e.What();
+        LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What();
         return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
       }
     }
@@ -346,4 +346,10 @@
       return it->second->GetVersion();
     }
   }
+
+  
+  std::string PluginsManager::GetPluginName(SharedLibrary& library)
+  {
+    return CallGetName(library);
+  }
 }
--- a/Plugins/Engine/PluginsManager.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Engine/PluginsManager.h	Wed Nov 18 10:16:21 2015 +0100
@@ -112,6 +112,8 @@
     bool HasPlugin(const std::string& name) const;
 
     const std::string& GetPluginVersion(const std::string& name) const;
+
+    static std::string GetPluginName(SharedLibrary& library);
   };
 }
 
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Nov 18 10:16:21 2015 +0100
@@ -522,7 +522,9 @@
       void* payload,
       int32_t property);
 
-    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    /* Use "OrthancPluginDatabaseExtensions::lookupIdentifier3" 
+       instead of this function as of Orthanc 0.9.5 (db v6), can be set to NULL.
+       Output: Use OrthancPluginDatabaseAnswerInt64() */
     OrthancPluginErrorCode  (*lookupIdentifier) (
       /* outputs */
       OrthancPluginDatabaseContext* context,
@@ -530,7 +532,8 @@
       void* payload,
       const OrthancPluginDicomTag* tag);
 
-    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    /* Unused starting with Orthanc 0.9.5 (db v6), can be set to NULL.
+       Output: Use OrthancPluginDatabaseAnswerInt64() */
     OrthancPluginErrorCode  (*lookupIdentifier2) (
       /* outputs */
       OrthancPluginDatabaseContext* context,
@@ -655,7 +658,30 @@
       void* payload,
       uint32_t targetVersion,
       OrthancPluginStorageArea* storageArea);
-  } OrthancPluginDatabaseExtensions;
+ 
+    OrthancPluginErrorCode  (*clearMainDicomTags) (
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*getAllInternalIds) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    OrthancPluginErrorCode  (*lookupIdentifier3) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType,
+      const OrthancPluginDicomTag* tag,
+      OrthancPluginIdentifierConstraint constraint);
+   } OrthancPluginDatabaseExtensions;
 
 /*<! @endcond */
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Nov 18 10:16:21 2015 +0100
@@ -210,6 +210,10 @@
     OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
     OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
     OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
     OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
     OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
     OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
@@ -264,7 +268,9 @@
     OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
     OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
     OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
-    OrthancPluginErrorCode_DatabasePlugin = 2038    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    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_INTERNAL = 0x7fffffff
   } OrthancPluginErrorCode;
@@ -381,6 +387,10 @@
     _OrthancPluginService_WriteFile = 16,
     _OrthancPluginService_GetErrorDescription = 17,
     _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -418,6 +428,8 @@
     _OrthancPluginService_RestApiPostAfterPlugins = 3011,
     _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
     _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
 
     /* Access to DICOM instances */
     _OrthancPluginService_GetInstanceRemoteAet = 4000,
@@ -541,6 +553,7 @@
     OrthancPluginResourceType_Study = 1,       /*!< Study */
     OrthancPluginResourceType_Series = 2,      /*!< Series */
     OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
 
     _OrthancPluginResourceType_INTERNAL = 0x7fffffff
   } OrthancPluginResourceType;
@@ -563,6 +576,10 @@
     OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
     OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
     OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
 
     _OrthancPluginChangeType_INTERNAL = 0x7fffffff
   } OrthancPluginChangeType;
@@ -596,6 +613,91 @@
   } OrthancPluginImageFormat;
 
 
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Simple = 3,  /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
 
   /**
    * @brief A memory buffer allocated by the core system of Orthanc.
@@ -807,7 +909,11 @@
         sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
         sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
         sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginImageFormat))
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint))
     {
       /* Mismatch in the size of the enumerations */
       return 0;
@@ -2167,6 +2273,12 @@
    *
    * This function registers a callback function that is called
    * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * script.
    * 
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param callback The callback function.
@@ -3590,7 +3702,6 @@
     OrthancPluginContentType    type;
   } _OrthancPluginStorageAreaRemove;
 
-
   /**
    * @brief Remove a file from the storage area.
    *
@@ -3620,6 +3731,300 @@
 
 
 
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = &target;
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new tag in the dictionary of DICOM tags
+   * that are known to Orthanc. This function should be used in the
+   * OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end
+   * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A
+   * database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const char*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags The output flags.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const char*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags The output flags.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer.
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Wed Nov 18 10:16:21 2015 +0100
@@ -71,7 +71,7 @@
     OrthancPluginErrorCode  code_;
 
   public:
-    DatabaseException() : code_(OrthancPluginErrorCode_Plugin)
+    DatabaseException() : code_(OrthancPluginErrorCode_DatabasePlugin)
     {
     }
 
@@ -122,6 +122,11 @@
     {
     }
 
+    OrthancPluginContext* GetContext()
+    {
+      return context_;
+    }
+
     void LogError(const std::string& message)
     {
       OrthancPluginLogError(context_, message.c_str());
@@ -334,6 +339,9 @@
 
     virtual void DeleteResource(int64_t id) = 0;
 
+    virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                   OrthancPluginResourceType resourceType) = 0;
+
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  OrthancPluginResourceType resourceType) = 0;
 
@@ -398,18 +406,11 @@
     virtual bool LookupGlobalProperty(std::string& target /*out*/,
                                       int32_t property) = 0;
 
-    /**
-     * "Identifiers" are necessarily one of the following tags:
-     * PatientID (0x0010, 0x0020), StudyInstanceUID (0x0020, 0x000d),
-     * SeriesInstanceUID (0x0020, 0x000e), SOPInstanceUID (0x0008,
-     * 0x0018) or AccessionNumber (0x0008, 0x0050).
-     **/
     virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  OrthancPluginResourceType resourceType,
                                   uint16_t group,
                                   uint16_t element,
-                                  const char* value) = 0;
-
-    virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  OrthancPluginIdentifierConstraint constraint,
                                   const char* value) = 0;
 
     virtual bool LookupMetadata(std::string& target /*out*/,
@@ -456,8 +457,15 @@
 
     virtual uint32_t GetDatabaseVersion() = 0;
 
+    /**
+     * Upgrade the database to the specified version of the database
+     * schema.  The upgrade script is allowed to make calls to
+     * OrthancPluginReconstructMainDicomTags().
+     **/
     virtual void UpgradeDatabase(uint32_t  targetVersion,
                                  OrthancPluginStorageArea* storageArea) = 0;
+
+    virtual void ClearMainDicomTags(int64_t internalId) = 0;
   };
 
 
@@ -501,7 +509,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -525,7 +533,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -547,7 +555,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -569,7 +577,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -594,7 +602,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -618,7 +626,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -642,7 +650,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -665,7 +673,40 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
+      }
+      catch (DatabaseException& e)
+      {
+        return e.GetErrorCode();
+      }
+    }
+
+
+    static OrthancPluginErrorCode  GetAllInternalIds(OrthancPluginDatabaseContext* context,
+                                                     void* payload,
+                                                     OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->GetAllInternalIds(target, resourceType);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -699,7 +740,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -735,7 +776,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -768,7 +809,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -801,7 +842,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -835,7 +876,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -867,7 +908,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -890,7 +931,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -913,7 +954,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -937,7 +978,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -965,7 +1006,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -989,7 +1030,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1013,7 +1054,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1036,7 +1077,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1059,7 +1100,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1083,7 +1124,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1107,7 +1148,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1141,7 +1182,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1175,7 +1216,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1198,7 +1239,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1221,7 +1262,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1246,7 +1287,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1277,7 +1318,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1286,9 +1327,11 @@
     }
 
 
-    static OrthancPluginErrorCode  LookupIdentifier(OrthancPluginDatabaseContext* context,
-                                                    void* payload,
-                                                    const OrthancPluginDicomTag* tag)
+    static OrthancPluginErrorCode  LookupIdentifier3(OrthancPluginDatabaseContext* context,
+                                                     void* payload,
+                                                     OrthancPluginResourceType resourceType,
+                                                     const OrthancPluginDicomTag* tag,
+                                                     OrthancPluginIdentifierConstraint constraint)
     {
       IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
       backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
@@ -1296,7 +1339,7 @@
       try
       {
         std::list<int64_t> target;
-        backend->LookupIdentifier(target, tag->group, tag->element, tag->value);
+        backend->LookupIdentifier(target, resourceType, tag->group, tag->element, constraint, tag->value);
 
         for (std::list<int64_t>::const_iterator
                it = target.begin(); it != target.end(); ++it)
@@ -1310,40 +1353,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
-      }
-      catch (DatabaseException& e)
-      {
-        return e.GetErrorCode();
-      }
-    }
-
-
-    static OrthancPluginErrorCode  LookupIdentifier2(OrthancPluginDatabaseContext* context,
-                                                     void* payload,
-                                                     const char* value)
-    {
-      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
-      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
-
-      try
-      {
-        std::list<int64_t> target;
-        backend->LookupIdentifier(target, value);
-
-        for (std::list<int64_t>::const_iterator
-               it = target.begin(); it != target.end(); ++it)
-        {
-          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
-                                           backend->GetOutput().database_, *it);
-        }
-
-        return OrthancPluginErrorCode_Success;
-      }
-      catch (std::runtime_error& e)
-      {
-        LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1374,7 +1384,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1404,7 +1414,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1436,7 +1446,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1465,7 +1475,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1495,7 +1505,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1519,7 +1529,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1543,7 +1553,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1567,7 +1577,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1592,7 +1602,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1616,7 +1626,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1638,7 +1648,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1660,7 +1670,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1682,7 +1692,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1704,7 +1714,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1726,7 +1736,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1748,7 +1758,7 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1771,7 +1781,29 @@
       catch (std::runtime_error& e)
       {
         LogError(backend, e);
-        return OrthancPluginErrorCode_Plugin;
+        return OrthancPluginErrorCode_DatabasePlugin;
+      }
+      catch (DatabaseException& e)
+      {
+        return e.GetErrorCode();
+      }
+    }
+
+    
+    static OrthancPluginErrorCode ClearMainDicomTags(void* payload,
+                                                     int64_t internalId)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      
+      try
+      {
+        backend->ClearMainDicomTags(internalId);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return OrthancPluginErrorCode_DatabasePlugin;
       }
       catch (DatabaseException& e)
       {
@@ -1826,8 +1858,8 @@
       params.logExportedResource = LogExportedResource;
       params.lookupAttachment = LookupAttachment;
       params.lookupGlobalProperty = LookupGlobalProperty;
-      params.lookupIdentifier = LookupIdentifier;
-      params.lookupIdentifier2 = LookupIdentifier2;
+      params.lookupIdentifier = NULL;   // Unused starting with Orthanc 0.9.5 (db v6)
+      params.lookupIdentifier2 = NULL;   // Unused starting with Orthanc 0.9.5 (db v6)
       params.lookupMetadata = LookupMetadata;
       params.lookupParent = LookupParent;
       params.lookupResource = LookupResource;
@@ -1847,6 +1879,9 @@
       extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit;
       extensions.getDatabaseVersion = GetDatabaseVersion;
       extensions.upgradeDatabase = UpgradeDatabase;
+      extensions.clearMainDicomTags = ClearMainDicomTags;
+      extensions.getAllInternalIds = GetAllInternalIds;   // New in Orthanc 0.9.5 (db v6)
+      extensions.lookupIdentifier3 = LookupIdentifier3;   // New in Orthanc 0.9.5 (db v6)
 
       OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
       if (!context)
--- a/Plugins/Samples/Basic/Plugin.c	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/Basic/Plugin.c	Wed Nov 18 10:16:21 2015 +0100
@@ -25,6 +25,8 @@
 
 static OrthancPluginContext* context = NULL;
 
+static OrthancPluginErrorCode customError;
+
 
 ORTHANC_PLUGINS_API int32_t Callback1(OrthancPluginRestOutput* output,
                                       const char* url,
@@ -192,9 +194,7 @@
   }
   else
   {
-    printf("ICI1\n");
     error = OrthancPluginRestApiGetAfterPlugins(context, &tmp, request->groups[1]);
-    printf("ICI2\n");
   }
 
   if (error)
@@ -401,6 +401,11 @@
   sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
   OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
 
+  customError = OrthancPluginRegisterErrorCode(context, 4, 402, "Hello world");
+
+  OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA,
+                                     "ValidationExpiryDate", 1, 1);
+
   return 0;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/Common/ExportedSymbols.list	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- a/Plugins/Samples/Common/OrthancPlugins.cmake	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/Common/OrthancPlugins.cmake	Wed Nov 18 10:16:21 2015 +0100
@@ -1,28 +1,14 @@
+set(ORTHANC_ROOT ${SAMPLES_ROOT}/../..)
 include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
 include(CheckLibraryExists)
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
-  link_libraries(uuid)
-  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
-  link_libraries(rpcrt4 ws2_32 secur32)
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++")
-  endif()
-
-  CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
-  if (HAVE_WIN_PTHREAD)
-    # This line is necessary to compile with recent versions of MinGW,
-    # otherwise "libwinpthread-1.dll" is not statically linked.
-    SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
-  endif()
-endif ()
+include(FindPythonInterp)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
 
 
 if (CMAKE_COMPILER_IS_GNUCXX)
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${SAMPLES_ROOT}/Common/VersionScript.map -Wl,--no-undefined")
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")
 endif()
@@ -37,6 +23,7 @@
 
 include_directories(${SAMPLES_ROOT}/../Include/)
 
+
 if (MSVC)
   include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/)
 endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/DatabasePlugin/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(SampleDatabasePlugin)
+
+# Parameters of the build
+SET(SAMPLE_DATABASE_VERSION "0.0" CACHE STRING "Version of the plugin")
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+SET(STANDALONE_BUILD ON)
+
+# Advanced parameters to fine-tune linking against system libraries
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite")
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/SQLiteConfiguration.cmake)
+
+EmbedResources(
+  --system-exception  # Use "std::runtime_error" instead of "OrthancException" for embedded resources
+  PREPARE_DATABASE  ${ORTHANC_ROOT}/OrthancServer/PrepareDatabase.sql
+  )
+
+message("Setting the version of the plugin to ${SAMPLE_DATABASE_VERSION}")
+
+add_definitions(
+  -DORTHANC_SQLITE_STANDALONE=1
+  -DORTHANC_ENABLE_LOGGING=0
+  -DORTHANC_ENABLE_BASE64=0
+  -DORTHANC_ENABLE_MD5=0
+  -DORTHANC_ENABLE_DCMTK=0
+  -DORTHANC_PLUGINS_ENABLED=1
+  -DSAMPLE_DATABASE_VERSION="${SAMPLE_DATABASE_VERSION}"
+  )
+
+add_library(SampleDatabase SHARED 
+  ${BOOST_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${SQLITE_SOURCES}
+  ${AUTOGENERATED_SOURCES}
+
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp
+  ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/OrthancServer/DatabaseWrapperBase.cpp
+  ${ORTHANC_ROOT}/Plugins/Engine/PluginsEnumerations.cpp
+
+  Database.cpp
+  Plugin.cpp
+  )
+
+set_target_properties(SampleDatabase PROPERTIES 
+  VERSION ${SAMPLE_DATABASE_VERSION} 
+  SOVERSION ${SAMPLE_DATABASE_VERSION})
+
+install(
+  TARGETS SampleDatabase
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/DatabasePlugin/Database.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,562 @@
+/**
+ * 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 "Database.h"
+
+#include "../../../Core/DicomFormat/DicomArray.h"
+
+#include <EmbeddedResources.h>
+#include <boost/lexical_cast.hpp>
+
+
+namespace Internals
+{
+  class SignalFileDeleted : public Orthanc::SQLite::IScalarFunction
+  {
+  private:
+    OrthancPlugins::DatabaseBackendOutput&  output_;
+
+  public:
+    SignalFileDeleted(OrthancPlugins::DatabaseBackendOutput&  output) : output_(output)
+    {
+    }
+
+    virtual const char* GetName() const
+    {
+      return "SignalFileDeleted";
+    }
+
+    virtual unsigned int GetCardinality() const
+    {
+      return 7;
+    }
+
+    virtual void Compute(Orthanc::SQLite::FunctionContext& context)
+    {
+      std::string uncompressedMD5, compressedMD5;
+
+      if (!context.IsNullValue(5))
+      {
+        uncompressedMD5 = context.GetStringValue(5);
+      }
+
+      if (!context.IsNullValue(6))
+      {
+        compressedMD5 = context.GetStringValue(6);
+      }
+      
+      output_.SignalDeletedAttachment(context.GetStringValue(0),
+                                      context.GetIntValue(1),
+                                      context.GetInt64Value(2),
+                                      uncompressedMD5,
+                                      context.GetIntValue(3),
+                                      context.GetInt64Value(4),
+                                      compressedMD5);
+    }
+  };
+
+
+  class SignalResourceDeleted : public Orthanc::SQLite::IScalarFunction
+  {
+  private:
+    OrthancPlugins::DatabaseBackendOutput&  output_;
+
+  public:
+    SignalResourceDeleted(OrthancPlugins::DatabaseBackendOutput&  output) : output_(output)
+    {
+    }
+
+    virtual const char* GetName() const
+    {
+      return "SignalResourceDeleted";
+    }
+
+    virtual unsigned int GetCardinality() const
+    {
+      return 2;
+    }
+
+    virtual void Compute(Orthanc::SQLite::FunctionContext& context)
+    {
+      output_.SignalDeletedResource(context.GetStringValue(0),
+                                    Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1))));
+    }
+  };
+}
+
+
+class Database::SignalRemainingAncestor : public Orthanc::SQLite::IScalarFunction
+{
+private:
+  bool hasRemainingAncestor_;
+  std::string remainingPublicId_;
+  OrthancPluginResourceType remainingType_;
+
+public:
+  SignalRemainingAncestor() : 
+    hasRemainingAncestor_(false)
+  {
+  }
+
+  void Reset()
+  {
+    hasRemainingAncestor_ = false;
+  }
+
+  virtual const char* GetName() const
+  {
+    return "SignalRemainingAncestor";
+  }
+
+  virtual unsigned int GetCardinality() const
+  {
+    return 2;
+  }
+
+  virtual void Compute(Orthanc::SQLite::FunctionContext& context)
+  {
+    if (!hasRemainingAncestor_ ||
+        remainingType_ >= context.GetIntValue(1))
+    {
+      hasRemainingAncestor_ = true;
+      remainingPublicId_ = context.GetStringValue(0);
+      remainingType_ = Orthanc::Plugins::Convert(static_cast<Orthanc::ResourceType>(context.GetIntValue(1)));
+    }
+  }
+
+  bool HasRemainingAncestor() const
+  {
+    return hasRemainingAncestor_;
+  }
+
+  const std::string& GetRemainingAncestorId() const
+  {
+    assert(hasRemainingAncestor_);
+    return remainingPublicId_;
+  }
+
+  OrthancPluginResourceType GetRemainingAncestorType() const
+  {
+    assert(hasRemainingAncestor_);
+    return remainingType_;
+  }
+};
+
+
+
+Database::Database(const std::string& path) : 
+  path_(path),
+  base_(db_)
+{
+}
+
+
+void Database::Open()
+{
+  db_.Open(path_);
+
+  db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
+
+  // http://www.sqlite.org/pragma.html
+  db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
+  db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
+  db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
+  db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
+  //db_.Execute("PRAGMA TEMP_STORE=memory");
+
+  if (!db_.DoesTableExist("GlobalProperties"))
+  {
+    std::string query;
+    Orthanc::EmbeddedResources::GetFileResource(query, Orthanc::EmbeddedResources::PREPARE_DATABASE);
+    db_.Execute(query);
+  }
+
+  signalRemainingAncestor_ = new SignalRemainingAncestor;
+  db_.Register(signalRemainingAncestor_);
+  db_.Register(new Internals::SignalFileDeleted(GetOutput()));
+  db_.Register(new Internals::SignalResourceDeleted(GetOutput()));
+}
+
+
+void Database::Close()
+{
+  db_.Close();
+}
+
+
+void Database::AddAttachment(int64_t id,
+                             const OrthancPluginAttachment& attachment)
+{
+  Orthanc::FileInfo info(attachment.uuid,
+                         static_cast<Orthanc::FileContentType>(attachment.contentType),
+                         attachment.uncompressedSize,
+                         attachment.uncompressedHash,
+                         static_cast<Orthanc::CompressionType>(attachment.compressionType),
+                         attachment.compressedSize,
+                         attachment.compressedHash);
+  base_.AddAttachment(id, info);
+}
+
+
+void Database::DeleteResource(int64_t id)
+{
+  signalRemainingAncestor_->Reset();
+
+  Orthanc::SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
+  s.BindInt64(0, id);
+  s.Run();
+
+  if (signalRemainingAncestor_->HasRemainingAncestor())
+  {
+    GetOutput().SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorId(),
+                                        signalRemainingAncestor_->GetRemainingAncestorType());
+  }
+}
+
+
+static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
+                   const Orthanc::ServerIndexChange& change)
+{
+  output.AnswerChange(change.GetSeq(), 
+                      change.GetChangeType(),
+                      Orthanc::Plugins::Convert(change.GetResourceType()),
+                      change.GetPublicId(),
+                      change.GetDate());
+}
+
+
+static void Answer(OrthancPlugins::DatabaseBackendOutput& output,
+                   const Orthanc::ExportedResource& resource)
+{
+  output.AnswerExportedResource(resource.GetSeq(),
+                                Orthanc::Plugins::Convert(resource.GetResourceType()),
+                                resource.GetPublicId(),
+                                resource.GetModality(),
+                                resource.GetDate(),
+                                resource.GetPatientId(),
+                                resource.GetStudyInstanceUid(),
+                                resource.GetSeriesInstanceUid(),
+                                resource.GetSopInstanceUid());
+}
+
+
+void Database::GetChanges(bool& done /*out*/,
+                          int64_t since,
+                          uint32_t maxResults)
+{
+  typedef std::list<Orthanc::ServerIndexChange> Changes;
+
+  Changes changes;
+  base_.GetChanges(changes, done, since, maxResults);
+
+  for (Changes::const_iterator it = changes.begin(); it != changes.end(); ++it)
+  {
+    Answer(GetOutput(), *it);
+  }
+}
+
+
+void Database::GetExportedResources(bool& done /*out*/,
+                                    int64_t since,
+                                    uint32_t maxResults)
+{
+  typedef std::list<Orthanc::ExportedResource> Resources;
+
+  Resources resources;
+  base_.GetExportedResources(resources, done, since, maxResults);
+
+  for (Resources::const_iterator it = resources.begin(); it != resources.end(); ++it)
+  {
+    Answer(GetOutput(), *it);
+  }
+}
+
+
+void Database::GetLastChange()
+{
+  std::list<Orthanc::ServerIndexChange> change;
+  Orthanc::ErrorCode code = base_.GetLastChange(change);
+  
+  if (code != Orthanc::ErrorCode_Success)
+  {
+    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
+  }
+
+  if (!change.empty())
+  {
+    Answer(GetOutput(), change.front());
+  }
+}
+
+
+void Database::GetLastExportedResource()
+{
+  std::list<Orthanc::ExportedResource> resource;
+  base_.GetLastExportedResource(resource);
+  
+  if (!resource.empty())
+  {
+    Answer(GetOutput(), resource.front());
+  }
+}
+
+
+void Database::GetMainDicomTags(int64_t id)
+{
+  Orthanc::DicomMap tags;
+  base_.GetMainDicomTags(tags, id);
+
+  Orthanc::DicomArray arr(tags);
+  for (size_t i = 0; i < arr.GetSize(); i++)
+  {
+    GetOutput().AnswerDicomTag(arr.GetElement(i).GetTag().GetGroup(),
+                               arr.GetElement(i).GetTag().GetElement(),
+                               arr.GetElement(i).GetValue().GetContent());
+  }
+}
+
+
+std::string Database::GetPublicId(int64_t resourceId)
+{
+  std::string id;
+  if (base_.GetPublicId(id, resourceId))
+  {
+    return id;
+  }
+  else
+  {
+    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_UnknownResource);
+  }
+}
+
+
+OrthancPluginResourceType Database::GetResourceType(int64_t resourceId)
+{
+  Orthanc::ResourceType  result;
+  Orthanc::ErrorCode  code = base_.GetResourceType(result, resourceId);
+
+  if (code == Orthanc::ErrorCode_Success)
+  {
+    return Orthanc::Plugins::Convert(result);
+  }
+  else
+  {
+    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
+  }
+}
+
+
+
+template <typename I>
+static void ConvertList(std::list<int32_t>& target,
+                        const std::list<I>& source)
+{
+  for (typename std::list<I>::const_iterator 
+         it = source.begin(); it != source.end(); it++)
+  {
+    target.push_back(*it);
+  }
+}
+
+
+void Database::ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                     int64_t id)
+{
+  std::list<Orthanc::MetadataType> tmp;
+  base_.ListAvailableMetadata(tmp, id);
+  ConvertList(target, tmp);
+}
+
+
+void Database::ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                        int64_t id)
+{
+  std::list<Orthanc::FileContentType> tmp;
+  base_.ListAvailableAttachments(tmp, id);
+  ConvertList(target, tmp);
+}
+
+
+void Database::LogChange(const OrthancPluginChange& change)
+{
+  int64_t id;
+  OrthancPluginResourceType type;
+  if (!LookupResource(id, type, change.publicId) ||
+      type != change.resourceType)
+  {
+    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_DatabasePlugin);
+  }
+
+  Orthanc::ServerIndexChange tmp(change.seq,
+                                 static_cast<Orthanc::ChangeType>(change.changeType),
+                                 Orthanc::Plugins::Convert(change.resourceType),
+                                 change.publicId,
+                                 change.date);
+
+  base_.LogChange(id, tmp);
+}
+
+
+void Database::LogExportedResource(const OrthancPluginExportedResource& resource) 
+{
+  Orthanc::ExportedResource tmp(resource.seq,
+                                Orthanc::Plugins::Convert(resource.resourceType),
+                                resource.publicId,
+                                resource.modality,
+                                resource.date,
+                                resource.patientId,
+                                resource.studyInstanceUid,
+                                resource.seriesInstanceUid,
+                                resource.sopInstanceUid);
+
+  base_.LogExportedResource(tmp);
+}
+
+    
+bool Database::LookupAttachment(int64_t id,
+                                int32_t contentType)
+{
+  Orthanc::FileInfo attachment;
+  if (base_.LookupAttachment(attachment, id, static_cast<Orthanc::FileContentType>(contentType)))
+  {
+    GetOutput().AnswerAttachment(attachment.GetUuid(),
+                                 attachment.GetContentType(),
+                                 attachment.GetUncompressedSize(),
+                                 attachment.GetUncompressedMD5(),
+                                 attachment.GetCompressionType(),
+                                 attachment.GetCompressedSize(),
+                                 attachment.GetCompressedMD5());
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+bool Database::LookupParent(int64_t& parentId /*out*/,
+                            int64_t resourceId)
+{
+  bool found;
+  Orthanc::ErrorCode code = base_.LookupParent(found, parentId, resourceId);
+
+  if (code == Orthanc::ErrorCode_Success)
+  {
+    return found;
+  }
+  else
+  {
+    throw OrthancPlugins::DatabaseException(static_cast<OrthancPluginErrorCode>(code));
+  }
+}
+
+
+bool Database::LookupResource(int64_t& id /*out*/,
+                              OrthancPluginResourceType& type /*out*/,
+                              const char* publicId)
+{
+  Orthanc::ResourceType tmp;
+  if (base_.LookupResource(id, tmp, publicId))
+  {
+    type = Orthanc::Plugins::Convert(tmp);
+    return true;
+  }
+  else
+  {
+    return false;
+  }
+}
+
+
+void Database::StartTransaction()
+{
+  transaction_.reset(new Orthanc::SQLite::Transaction(db_));
+  transaction_->Begin();
+}
+
+
+void Database::RollbackTransaction()
+{
+  transaction_->Rollback();
+  transaction_.reset(NULL);
+}
+
+
+void Database::CommitTransaction()
+{
+  transaction_->Commit();
+  transaction_.reset(NULL);
+}
+
+
+uint32_t Database::GetDatabaseVersion()
+{
+  std::string version;
+
+  if (!LookupGlobalProperty(version, Orthanc::GlobalProperty_DatabaseSchemaVersion))
+  {
+    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
+  }
+
+  try
+  {
+    return boost::lexical_cast<uint32_t>(version);
+  }
+  catch (boost::bad_lexical_cast&)
+  {
+    throw OrthancPlugins::DatabaseException(OrthancPluginErrorCode_InternalError);
+  }
+}
+
+
+void Database::UpgradeDatabase(uint32_t  targetVersion,
+                               OrthancPluginStorageArea* storageArea)
+{
+  if (targetVersion == 6)
+  {
+    OrthancPluginErrorCode code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, 
+                                                                        OrthancPluginResourceType_Study);
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      code = OrthancPluginReconstructMainDicomTags(GetOutput().GetContext(), storageArea, 
+                                                   OrthancPluginResourceType_Series);
+    }
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      throw OrthancPlugins::DatabaseException(code);
+    }
+
+    base_.SetGlobalProperty(Orthanc::GlobalProperty_DatabaseSchemaVersion, "6");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/DatabasePlugin/Database.h	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,284 @@
+/**
+ * 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 <orthanc/OrthancCppDatabasePlugin.h>
+
+#include "../../../Core/SQLite/Connection.h"
+#include "../../../Core/SQLite/Transaction.h"
+#include "../../../OrthancServer/DatabaseWrapperBase.h"
+#include "../../Engine/PluginsEnumerations.h"
+
+#include <memory>
+
+class Database : public OrthancPlugins::IDatabaseBackend
+{
+private:
+  class SignalRemainingAncestor;
+
+  std::string                   path_;
+  Orthanc::SQLite::Connection   db_;
+  Orthanc::DatabaseWrapperBase  base_;
+  SignalRemainingAncestor*      signalRemainingAncestor_;
+
+  std::auto_ptr<Orthanc::SQLite::Transaction>  transaction_;
+
+public:
+  Database(const std::string& path);
+
+  virtual void Open();
+
+  virtual void Close();
+
+  virtual void AddAttachment(int64_t id,
+                             const OrthancPluginAttachment& attachment);
+
+  virtual void AttachChild(int64_t parent,
+                           int64_t child)
+  {
+    base_.AttachChild(parent, child);
+  }
+
+  virtual void ClearChanges()
+  {
+    db_.Execute("DELETE FROM Changes");    
+  }
+
+  virtual void ClearExportedResources()
+  {
+    db_.Execute("DELETE FROM ExportedResources");    
+  }
+
+  virtual int64_t CreateResource(const char* publicId,
+                                 OrthancPluginResourceType type)
+  {
+    return base_.CreateResource(publicId, Orthanc::Plugins::Convert(type));
+  }
+
+  virtual void DeleteAttachment(int64_t id,
+                                int32_t attachment)
+  {
+    base_.DeleteAttachment(id, static_cast<Orthanc::FileContentType>(attachment));
+  }
+
+  virtual void DeleteMetadata(int64_t id,
+                              int32_t metadataType)
+  {
+    base_.DeleteMetadata(id, static_cast<Orthanc::MetadataType>(metadataType));
+  }
+
+  virtual void DeleteResource(int64_t id);
+
+  virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                 OrthancPluginResourceType resourceType)
+  {
+    base_.GetAllInternalIds(target, Orthanc::Plugins::Convert(resourceType));
+  }
+
+  virtual void GetAllPublicIds(std::list<std::string>& target,
+                               OrthancPluginResourceType resourceType)
+  {
+    base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType));
+  }
+
+  virtual void GetAllPublicIds(std::list<std::string>& target,
+                               OrthancPluginResourceType resourceType,
+                               uint64_t since,
+                               uint64_t limit)
+  {
+    base_.GetAllPublicIds(target, Orthanc::Plugins::Convert(resourceType), since, limit);
+  }
+
+  virtual void GetChanges(bool& done /*out*/,
+                          int64_t since,
+                          uint32_t maxResults);
+
+  virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
+                                     int64_t id)
+  {
+    base_.GetChildrenInternalId(target, id);
+  }
+
+  virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/,
+                                   int64_t id)
+  {
+    base_.GetChildrenPublicId(target, id);
+  }
+
+  virtual void GetExportedResources(bool& done /*out*/,
+                                    int64_t since,
+                                    uint32_t maxResults);
+
+  virtual void GetLastChange();
+
+  virtual void GetLastExportedResource();
+
+  virtual void GetMainDicomTags(int64_t id);
+
+  virtual std::string GetPublicId(int64_t resourceId);
+
+  virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType)
+  {
+    return base_.GetResourceCount(Orthanc::Plugins::Convert(resourceType));
+  }
+
+  virtual OrthancPluginResourceType GetResourceType(int64_t resourceId);
+
+  virtual uint64_t GetTotalCompressedSize()
+  {
+    return base_.GetTotalCompressedSize();
+  }
+    
+  virtual uint64_t GetTotalUncompressedSize()
+  {
+    return base_.GetTotalUncompressedSize();
+  }
+
+  virtual bool IsExistingResource(int64_t internalId)
+  {
+    return base_.IsExistingResource(internalId);
+  }
+
+  virtual bool IsProtectedPatient(int64_t internalId)
+  {
+    return base_.IsProtectedPatient(internalId);
+  }
+
+  virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                     int64_t id);
+
+  virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                        int64_t id);
+
+  virtual void LogChange(const OrthancPluginChange& change);
+
+  virtual void LogExportedResource(const OrthancPluginExportedResource& resource);
+    
+  virtual bool LookupAttachment(int64_t id,
+                                int32_t contentType);
+
+  virtual bool LookupGlobalProperty(std::string& target /*out*/,
+                                    int32_t property)
+  {
+    return base_.LookupGlobalProperty(target, static_cast<Orthanc::GlobalProperty>(property));
+  }
+
+  virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                OrthancPluginResourceType level,
+                                uint16_t group,
+                                uint16_t element,
+                                OrthancPluginIdentifierConstraint constraint,
+                                const char* value)
+  {
+    base_.LookupIdentifier(target, Orthanc::Plugins::Convert(level),
+                           Orthanc::DicomTag(group, element), 
+                           Orthanc::Plugins::Convert(constraint), value);
+  }
+
+  virtual bool LookupMetadata(std::string& target /*out*/,
+                              int64_t id,
+                              int32_t metadataType)
+  {
+    return base_.LookupMetadata(target, id, static_cast<Orthanc::MetadataType>(metadataType));
+  }
+
+  virtual bool LookupParent(int64_t& parentId /*out*/,
+                            int64_t resourceId);
+
+  virtual bool LookupResource(int64_t& id /*out*/,
+                              OrthancPluginResourceType& type /*out*/,
+                              const char* publicId);
+
+  virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/)
+  {
+    return base_.SelectPatientToRecycle(internalId);
+  }
+
+  virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/,
+                                      int64_t patientIdToAvoid)
+  {
+    return base_.SelectPatientToRecycle(internalId, patientIdToAvoid);
+  }
+
+
+  virtual void SetGlobalProperty(int32_t property,
+                                 const char* value)
+  {
+    base_.SetGlobalProperty(static_cast<Orthanc::GlobalProperty>(property), value);
+  }
+
+  virtual void SetMainDicomTag(int64_t id,
+                               uint16_t group,
+                               uint16_t element,
+                               const char* value)
+  {
+    base_.SetMainDicomTag(id, Orthanc::DicomTag(group, element), value);
+  }
+
+  virtual void SetIdentifierTag(int64_t id,
+                                uint16_t group,
+                                uint16_t element,
+                                const char* value)
+  {
+    base_.SetIdentifierTag(id, Orthanc::DicomTag(group, element), value);
+  }
+
+  virtual void SetMetadata(int64_t id,
+                           int32_t metadataType,
+                           const char* value)
+  {
+    base_.SetMetadata(id, static_cast<Orthanc::MetadataType>(metadataType), value);
+  }
+
+  virtual void SetProtectedPatient(int64_t internalId, 
+                                   bool isProtected)
+  {
+    base_.SetProtectedPatient(internalId, isProtected);
+  }
+
+  virtual void StartTransaction();
+
+  virtual void RollbackTransaction();
+
+  virtual void CommitTransaction();
+
+  virtual uint32_t GetDatabaseVersion();
+
+  virtual void UpgradeDatabase(uint32_t  targetVersion,
+                               OrthancPluginStorageArea* storageArea);
+
+  virtual void ClearMainDicomTags(int64_t internalId)
+  {
+    base_.ClearMainDicomTags(internalId);
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/DatabasePlugin/Plugin.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,101 @@
+/**
+ * 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 "Database.h"
+
+#include <memory>
+#include <iostream>
+#include <boost/algorithm/string/predicate.hpp>
+
+static OrthancPluginContext*  context_ = NULL;
+static std::auto_ptr<OrthancPlugins::IDatabaseBackend>  backend_;
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+    OrthancPluginLogWarning(context_, "Sample plugin is initializing");
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 0)
+    {
+      char info[256];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              c->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    std::string path = "SampleDatabase.sqlite";
+    uint32_t argCount = OrthancPluginGetCommandLineArgumentsCount(context_);
+    for (uint32_t i = 0; i < argCount; i++)
+    {
+      char* tmp = OrthancPluginGetCommandLineArgument(context_, i);
+      std::string argument(tmp);
+      OrthancPluginFreeString(context_, tmp);
+
+      if (boost::starts_with(argument, "--database="))
+      {
+        path = argument.substr(11);
+      }
+    }
+
+    std::string s = "Using the following SQLite database: " + path;
+    OrthancPluginLogWarning(context_, s.c_str());
+
+    backend_.reset(new Database(path));
+    OrthancPlugins::DatabaseBackendAdapter::Register(context_, *backend_);
+
+    return 0;
+  }
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    backend_.reset(NULL);
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "sample-database";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "1.0";
+  }
+}
--- a/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
@@ -3,18 +3,15 @@
 project(GdcmDecoding)
 
 SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
-SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
-SET(USE_SYSTEM_GOOGLE_LOG ON CACHE BOOL "Use the system version of Google Log")
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
 
-set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..)
-set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
 
-include(CheckIncludeFiles)
-include(CheckIncludeFileCXX)
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
 include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+
 include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake)
 include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
 
 find_package(GDCM REQUIRED)
--- a/Plugins/Samples/ServeFolders/CMakeLists.txt	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/ServeFolders/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
@@ -5,16 +5,12 @@
 SET(SERVE_FOLDERS_VERSION "0.0" CACHE STRING "Version of the plugin")
 SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
 SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
 SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
 SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
 
-set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../../)
 set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
-
-include(CheckIncludeFiles)
-include(CheckIncludeFileCXX)
 include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake)
-include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
 include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
 include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
 
@@ -24,7 +20,6 @@
   ${BOOST_SOURCES}
   )
 
-
 message("Setting the version of the plugin to ${SERVE_FOLDERS_VERSION}")
 add_definitions(-DSERVE_FOLDERS_VERSION="${SERVE_FOLDERS_VERSION}")
 
--- a/Plugins/Samples/StorageArea/Plugin.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/StorageArea/Plugin.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -43,13 +43,13 @@
   FILE* fp = fopen(path.c_str(), "wb");
   if (!fp)
   {
-    return OrthancPluginErrorCode_Plugin;
+    return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 
   bool ok = fwrite(content, size, 1, fp) == 1;
   fclose(fp);
 
-  return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_Plugin;
+  return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_StorageAreaPlugin;
 }
 
 
@@ -63,13 +63,13 @@
   FILE* fp = fopen(path.c_str(), "rb");
   if (!fp)
   {
-    return OrthancPluginErrorCode_Plugin;
+    return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 
   if (fseek(fp, 0, SEEK_END) < 0)
   {
     fclose(fp);
-    return OrthancPluginErrorCode_Plugin;
+    return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 
   *size = ftell(fp);
@@ -77,7 +77,7 @@
   if (fseek(fp, 0, SEEK_SET) < 0)
   {
     fclose(fp);
-    return OrthancPluginErrorCode_Plugin;
+    return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 
   bool ok = true;
@@ -98,7 +98,7 @@
 
   fclose(fp);
 
-  return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_Plugin;
+  return ok ? OrthancPluginErrorCode_Success : OrthancPluginErrorCode_StorageAreaPlugin;
 }
 
 
@@ -113,7 +113,7 @@
   }
   else
   {
-    return OrthancPluginErrorCode_Plugin;
+    return OrthancPluginErrorCode_StorageAreaPlugin;
   }
 }
 
--- a/Plugins/Samples/WebSkeleton/CMakeLists.txt	Wed Sep 23 10:29:06 2015 +0200
+++ b/Plugins/Samples/WebSkeleton/CMakeLists.txt	Wed Nov 18 10:16:21 2015 +0100
@@ -5,11 +5,11 @@
 SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
 SET(RESOURCES_ROOT ${CMAKE_SOURCE_DIR}/StaticResources)
 
-include(Framework/Framework.cmake)
-
 set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
 include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
 
+include(Framework/Framework.cmake)
+
 add_library(WebSkeleton SHARED 
   ${AUTOGENERATED_SOURCES}
   )
--- a/Resources/CMake/Compiler.cmake	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/CMake/Compiler.cmake	Wed Nov 18 10:16:21 2015 +0100
@@ -49,7 +49,7 @@
     ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
     ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
   set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
-  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
 
   # Remove the "-rdynamic" option
   # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
@@ -109,6 +109,8 @@
   endif()
 
 elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
+
   add_definitions(
     -D_XOPEN_SOURCE=1
     )
--- a/Resources/CMake/DcmtkConfiguration.cmake	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Wed Nov 18 10:16:21 2015 +0100
@@ -1,18 +1,3 @@
-# Lookup for DICOM dictionaries, if none is specified by the user
-if (DCMTK_DICTIONARY_DIR STREQUAL "")
-  find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic
-    /usr/share/dcmtk
-    /usr/share/libdcmtk2
-    /usr/local/share/dcmtk
-    )
-
-  message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}")
-  add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}")
-else()
-  add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
-endif()
-
-
 if (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK)
   SET(DCMTK_VERSION_NUMBER 361)
   set(DCMTK_PACKAGE_VERSION "3.6.1")
@@ -166,17 +151,16 @@
   set(DCMTK_BUNDLES_LOG4CPLUS 1)
 
   if (STANDALONE_BUILD)
-    add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=1)
+    set(DCMTK_USE_EMBEDDED_DICTIONARIES 1)
+    set(DCMTK_DICTIONARIES
+      DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic
+      DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic
+      DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic
+      )
   else()
-    add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0)
+    set(DCMTK_USE_EMBEDDED_DICTIONARIES 0)
   endif()
 
-  set(DCMTK_DICTIONARIES
-    DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic
-    DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic
-    DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic
-    )
-
 else()
   # The following line allows to manually add libraries at the
   # command-line, which is necessary for Ubuntu/Debian packages
@@ -210,9 +194,32 @@
     DCMTK_VERSION_NUMBER 
     ${DCMTK_VERSION_NUMBER1})
 
-  add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=0)
+  set(DCMTK_USE_EMBEDDED_DICTIONARIES 0)
+endif()
 
-endif()
 
 add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})
 message("DCMTK version: ${DCMTK_VERSION_NUMBER}")
+
+
+add_definitions(-DDCMTK_USE_EMBEDDED_DICTIONARIES=${DCMTK_USE_EMBEDDED_DICTIONARIES})
+if (NOT DCMTK_USE_EMBEDDED_DICTIONARIES)
+  # Lookup for DICOM dictionaries, if none is specified by the user
+  if (DCMTK_DICTIONARY_DIR STREQUAL "")
+    find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic
+      /usr/share/dcmtk
+      /usr/share/libdcmtk2
+      /usr/share/libdcmtk4
+      /usr/local/share/dcmtk
+      )
+
+    if (${DCMTK_DICTIONARY_DIR_AUTO} MATCHES "DCMTK_DICTIONARY_DIR_AUTO-NOTFOUND")
+      message(FATAL_ERROR "Cannot locate the DICOM dictionary on this system")
+    endif()
+
+    message("Autodetected path to the DICOM dictionaries: ${DCMTK_DICTIONARY_DIR_AUTO}")
+    add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR_AUTO}")
+  else()
+    add_definitions(-DDCMTK_DICTIONARY_DIR="${DCMTK_DICTIONARY_DIR}")
+  endif()
+endif()
--- a/Resources/Configuration.json	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/Configuration.json	Wed Nov 18 10:16:21 2015 +0100
@@ -190,17 +190,19 @@
    **/
 
   // Dictionary of symbolic names for the user-defined metadata. Each
-  // entry must map a number between 1024 and 65535 to an unique
-  // string.
+  // entry must map an unique string to an unique number between 1024
+  // and 65535.
   "UserMetadata" : {
     // "Sample" : 1024
   },
 
   // Dictionary of symbolic names for the user-defined types of
-  // attached files. Each entry must map a number between 1024 and
-  // 65535 to an unique string.
+  // attached files. Each entry must map an unique string to an unique
+  // number between 1024 and 65535. Optionally, a second argument can
+  // provided to specify a MIME content type for the attachment.
   "UserContentType" : {
     // "sample" : 1024
+    // "sample2" : [ 1025, "application/pdf" ]
   },
 
   // Number of seconds without receiving any instance before a
@@ -243,7 +245,8 @@
   "KeepAlive" : false,
 
   // If this option is set to "false", Orthanc will run in index-only
-  // mode. The DICOM files will not be stored on the drive.
+  // mode. The DICOM files will not be stored on the drive. Note that
+  // this option might prevent the upgrade to newer versions of Orthanc.
   "StoreDicom" : true,
 
   // DICOM associations are kept open as long as new DICOM commands
@@ -257,8 +260,19 @@
   // deleted as new requests are issued.
   "QueryRetrieveSize" : 10,
 
-  // When handling a C-Find SCP request, setting this flag to "false"
-  // will enable case-insensitive match for PN value representation
-  // (such as PatientName). By default, the search is case-insensitive.
-  "CaseSensitivePN" : false
+  // When handling a C-Find SCP request, setting this flag to "true"
+  // will enable case-sensitive match for PN value representation
+  // (such as PatientName). By default, the search is
+  // case-insensitive, which does not follow the DICOM standard.
+  "CaseSensitivePN" : false,
+  
+  // Register a new tag in the dictionary of DICOM tags that are known
+  // to Orthanc. Each line must contain the tag (formatted as 2
+  // hexadecimal numbers), the value representation (2 upcase
+  // characters), a nickname for the tag, possibly the minimum
+  // multiplicity (> 0 with defaults to 1), and possibly the maximum
+  // multiplicity (0 means arbitrary multiplicity, defaults to 1).
+  "Dictionary" : {
+    // "0014,1020" : [ "DA", "ValidationExpiryDate", 1, 1 ]
+  }
 }
--- a/Resources/ErrorCodes.json	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/ErrorCodes.json	Wed Nov 18 10:16:21 2015 +0100
@@ -175,7 +175,27 @@
     "Name": "BadFont", 
     "Description": "Badly formatted font file"
   },
-
+  {
+    "Code": 31, 
+    "Name": "DatabasePlugin", 
+    "Description": "The plugin implementing a custom database back-end does not fulfill the proper interface"
+  }, 
+  {
+    "Code": 32, 
+    "Name": "StorageAreaPlugin", 
+    "Description": "Error in the plugin implementing a custom storage area"
+  },
+  {
+    "Code": 33,
+    "Name": "EmptyRequest",
+    "Description": "The request is empty"
+  }, 
+  {
+    "Code": 34, 
+    "HttpStatus": 406, 
+    "Name": "NotAcceptable", 
+    "Description": "Cannot send a response which is acceptable according to the Accept HTTP header"
+  },
 
 
 
@@ -478,10 +498,20 @@
     "Code": 2037,
     "Name": "DatabaseBackendAlreadyRegistered",
     "Description": "Another plugin has already registered a custom database back-end"
-  }, 
+  },
   {
-    "Code": 2038, 
-    "Name": "DatabasePlugin", 
-    "Description": "The plugin implementing a custom database back-end does not fulfill the proper interface"
+    "Code": 2038,
+    "Name": "DatabaseNotInitialized",
+    "Description": "Plugin trying to call the database during its initialization"
+  },
+  { 
+    "Code": 2039,
+    "Name": "SslDisabled",
+    "Description": "Orthanc has been built without SSL support"
+  },
+  {
+    "Code": 2040,
+    "Name": "CannotOrderSlices",
+    "Description": "Unable to order the slices of the series"
   }
 ]
--- a/Resources/GenerateErrorCodes.py	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/GenerateErrorCodes.py	Wed Nov 18 10:16:21 2015 +0100
@@ -33,7 +33,9 @@
 import json
 import os
 import re
+import sys
 
+START_PLUGINS = 1000000
 BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
 
 
@@ -45,6 +47,11 @@
 with open(os.path.join(BASE, 'Resources', 'ErrorCodes.json'), 'r') as f:
     ERRORS = json.loads(re.sub('/\*.*?\*/', '', f.read()))
 
+for error in ERRORS:
+    if error['Code'] >= START_PLUGINS:
+        print('ERROR: Error code must be below %d, but "%s" is set to %d' % (START_PLUGINS, error['Name'], error['Code']))
+        sys.exit(-1)
+
 with open(os.path.join(BASE, 'Core', 'Enumerations.h'), 'r') as f:
     a = f.read()
 
@@ -63,6 +70,8 @@
     a = f.read()
 
 s = ',\n'.join(map(lambda x: '    ErrorCode_%s = %d    /*!< %s */' % (x['Name'], int(x['Code']), x['Description']), ERRORS))
+
+s += ',\n    ErrorCode_START_PLUGINS = %d' % START_PLUGINS
 a = re.sub('(enum ErrorCode\s*{)[^}]*?(\s*};)', r'\1\n%s\2' % s, a, re.DOTALL)
 
 with open(path, 'w') as f:
@@ -136,17 +145,15 @@
 
 
 ##
-## Generate the "Plugins::Convert(OrthancPluginErrorCode)" in
-## "PluginsEnumerations.cpp"
+## Generate the "PrintErrors" function in "main.cpp"
 ##
 
-path = os.path.join(BASE, 'Plugins', 'Engine', 'PluginsEnumerations.cpp')
+path = os.path.join(BASE, 'OrthancServer', 'main.cpp')
 with open(path, 'r') as f:
     a = f.read()
 
-s = '\n\n'.join(map(lambda x: '        case OrthancPluginErrorCode_%s:\n          return ErrorCode_%s;' % (x['Name'], x['Name']), ERRORS))
-a = re.sub('(Convert\(OrthancPluginErrorCode.*?\)\s*{\s*switch \([^)]*?\)\s*{)[^}]*?(\s*default:)',
-           r'\1\n%s\2' % s, a, re.DOTALL)
+s = '\n'.join(map(lambda x: '    PrintErrorCode(ErrorCode_%s, "%s");' % (x['Name'], x['Description']), ERRORS))
+a = re.sub('(static void PrintErrors[^{}]*?{[^{}]*?{)([^}]*?)}', r'\1\n%s\n  }' % s, a, re.DOTALL)
 
 with open(path, 'w') as f:
     f.write(a)
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Sep 23 10:29:06 2015 +0200
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Nov 18 10:16:21 2015 +0100
@@ -81,7 +81,7 @@
             sys.stdout.write(" => success\n")
             success_count += 1
         else:
-            sys.stdout.write(" => failure (Is it a DICOM file?)\n")
+            sys.stdout.write(" => failure (Is it a DICOM file? Is there a password?)\n")
 
     except:
         sys.stdout.write(" => unable to connect (Is Orthanc running? Is there a password?)\n")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/ModifyInstanceWithSequence.lua	Wed Nov 18 10:16:21 2015 +0100
@@ -0,0 +1,28 @@
+-- Answer to:
+-- https://groups.google.com/d/msg/orthanc-users/0ymHe1cDBCQ/YfD0NoOTn0wJ
+-- Applicable starting with Orthanc 0.9.5
+
+function OnStoredInstance(instanceId, tags, metadata, origin)
+   -- Do not modify twice the same file
+   if origin['RequestOrigin'] ~= 'Lua' then
+      local replace = {}
+      replace['0010,1002'] = {}
+      replace['0010,1002'][1] = {}
+      replace['0010,1002'][1]['PatientID'] = 'Hello'
+      replace['0010,1002'][2] = {}
+      replace['0010,1002'][2]['PatientID'] = 'World'
+
+      local request = {}
+      request['Replace'] = replace
+
+      -- Create the modified instance
+      local modified = RestApiPost('/instances/' .. instanceId .. '/modify',
+                                   DumpJson(request))
+
+      -- Upload the modified instance to the Orthanc store
+      RestApiPost('/instances/', modified)
+
+      -- Delete the original instance
+      RestApiDelete('/instances/' .. instanceId)
+   end
+end
--- a/THANKS	Wed Sep 23 10:29:06 2015 +0200
+++ b/THANKS	Wed Nov 18 10:16:21 2015 +0100
@@ -28,7 +28,6 @@
 * Vincent Kersten <vincent1234567@gmail.com>, for DICOMDIR in the GUI.
 * Emsy Chan <emlscs@yahoo.com>, for various contributions
   and sample DICOM files.
-* Mikhail <mp39590@gmail.com>, for FreeBSD support.
 
 
 Thanks also to all the contributors active in our Google Group:
@@ -49,6 +48,12 @@
 * Mario Ceresa <mrceresa@gmail.com>, for help about packaging.
 
 
+FreeBSD
+-------
+
+* Mikhail <mp39590@gmail.com>, for FreeBSD packaging.
+
+
 Artwork
 -------
 
--- a/UnitTestsSources/DicomMapTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/DicomMapTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -36,7 +36,6 @@
 #include "../Core/Uuid.h"
 #include "../Core/OrthancException.h"
 #include "../Core/DicomFormat/DicomMap.h"
-#include "../Core/DicomFormat/DicomNullValue.h"
 #include "../OrthancServer/FromDcmtkBridge.h"
 
 #include <memory>
@@ -103,7 +102,7 @@
   m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID");
   ASSERT_TRUE(m.HasTag(0x0010, 0x0020));
   m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2");
-  ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString());
+  ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).GetContent());
 
   m.GetTags(s);
   ASSERT_EQ(2u, s.size());
@@ -116,14 +115,14 @@
   ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin());
 
   std::auto_ptr<DicomMap> mm(m.Clone());
-  ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString());  
+  ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).GetContent());  
 
   m.SetValue(DICOM_TAG_PATIENT_ID, "Hello");
   ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException);
   mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID);
-  ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString());  
+  ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).GetContent());  
 
-  DicomNullValue v;
+  DicomValue v;
   ASSERT_TRUE(v.IsNull());
 }
 
@@ -170,18 +169,12 @@
       }*/
 
     // Exceptions for the Instance level
-    if ((/* Accession number, from Image module */
-          *it == DicomTag(0x0020, 0x0012) && 
-          level == ResourceType_Instance) ||
-        (/* Image Index, from PET Image module */
-          *it == DicomTag(0x0054, 0x1330) && 
-          level == ResourceType_Instance) ||
-        (/* Temporal Position Identifier, from MR Image module */
-          *it == DicomTag(0x0020, 0x0100) && 
-          level == ResourceType_Instance) ||
-        (/* Number of Frames, from Multi-frame module attributes, related to Image IOD */
-          *it == DicomTag(0x0028, 0x0008) && 
-          level == ResourceType_Instance ))
+    if (level == ResourceType_Instance &&
+        (*it == DicomTag(0x0020, 0x0012) ||  /* Accession number, from Image module */
+         *it == DicomTag(0x0054, 0x1330) ||  /* Image Index, from PET Image module */
+         *it == DicomTag(0x0020, 0x0100) ||  /* Temporal Position Identifier, from MR Image module */
+         *it == DicomTag(0x0028, 0x0008) ||  /* Number of Frames, from Multi-frame module attributes, related to Image IOD */
+         *it == DICOM_TAG_IMAGE_POSITION_PATIENT))
     {
       ok = true;
     }
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -36,6 +36,7 @@
 #include "../OrthancServer/FromDcmtkBridge.h"
 #include "../OrthancServer/OrthancInitialization.h"
 #include "../OrthancServer/DicomModification.h"
+#include "../OrthancServer/ServerToolbox.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Images/ImageBuffer.h"
 #include "../Core/Images/PngReader.h"
@@ -43,6 +44,8 @@
 #include "../Core/Uuid.h"
 #include "../Resources/EncodingTests.h"
 
+#include <dcmtk/dcmdata/dcelem.h>
+
 using namespace Orthanc;
 
 TEST(DicomFormat, Tag)
@@ -102,7 +105,7 @@
   ParsedDicomFile o;
   o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
-  o.Insert(privateTag, "private tag");
+  o.Insert(privateTag, "private tag", false);
   ASSERT_TRUE(o.GetTagValue(s, privateTag));
   ASSERT_STREQ("private tag", s.c_str());
 
@@ -145,14 +148,11 @@
   // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
   std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
 
-  std::string m, c;
-  Toolbox::DecodeDataUriScheme(m, c, s);
+  std::string m, cc;
+  Toolbox::DecodeDataUriScheme(m, cc, s);
 
   ASSERT_EQ("image/png", m);
-  ASSERT_EQ(116u, c.size());
 
-  std::string cc;
-  Toolbox::DecodeBase64(cc, c);
   PngReader reader;
   reader.ReadFromMemory(cc);
 
@@ -184,7 +184,7 @@
     img.SetHeight(256);
     img.SetFormat(PixelFormat_Grayscale16);
 
-    int v = 0;
+    uint16_t v = 0;
     for (unsigned int y = 0; y < img.GetHeight(); y++)
     {
       uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y));
@@ -207,7 +207,7 @@
     std::string source(testEncodingsEncoded[i]);
     std::string expected(testEncodingsExpected[i]);
     std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i]);
-    std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+    //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
     EXPECT_EQ(expected, s);
   }
 }
@@ -262,13 +262,15 @@
 {
   for (unsigned int i = 0; i < testEncodingsCount; i++)
   {
-    std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+    //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
     std::string dicom;
 
     {
       ParsedDicomFile f;
       f.SetEncoding(testEncodings[i]);
-      f.Insert(DICOM_TAG_PATIENT_NAME, testEncodingsEncoded[i]);
+
+      std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
+      f.Insert(DICOM_TAG_PATIENT_NAME, s, false);
       f.SaveToMemoryBuffer(dicom);
     }
 
@@ -302,3 +304,305 @@
   ASSERT_EQ(ValueRepresentation_Other, 
             FromDcmtkBridge::GetValueRepresentation(DICOM_TAG_PATIENT_ID));
 }
+
+
+
+static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110);
+static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120);
+
+static void CreateSampleJson(Json::Value& a)
+{
+  {
+    Json::Value b = Json::objectValue;
+    b["PatientName"] = "Hello";
+    b["PatientID"] = "World";
+    b["StudyDescription"] = "Toto";
+    a.append(b);
+  }
+
+  {
+    Json::Value b = Json::objectValue;
+    b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y";  // echo -n "Hello2" | base64
+    b["PatientID"] = "World2";
+    a.append(b);
+  }
+}
+
+
+TEST(FromDcmtkBridge, FromJson)
+{
+  std::auto_ptr<DcmElement> element;
+
+  {
+    Json::Value a;
+    a = "Hello";
+    element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8));
+
+    Json::Value b;
+    FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii);
+    ASSERT_EQ("Hello", b["0010,0010"].asString());
+  }
+
+  {
+    Json::Value a;
+    a = "Hello";
+    // Cannot assign a string to a sequence
+    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8)), OrthancException);
+  }
+
+  {
+    Json::Value a = Json::arrayValue;
+    a.append("Hello");
+    // Cannot assign an array to a string
+    ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)), OrthancException);
+  }
+
+  {
+    Json::Value a;
+    a = "data:application/octet-stream;base64,SGVsbG8=";  // echo -n "Hello" | base64
+    element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8));
+
+    Json::Value b;
+    FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii);
+    ASSERT_EQ("Hello", b["0010,0010"].asString());
+  }
+
+  {
+    Json::Value a = Json::arrayValue;
+    CreateSampleJson(a);
+    element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8));
+
+    {
+      Json::Value b;
+      FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0, Encoding_Ascii);
+      ASSERT_EQ(Json::arrayValue, b["0008,1110"].type());
+      ASSERT_EQ(2, b["0008,1110"].size());
+      
+      Json::Value::ArrayIndex i = (b["0008,1110"][0]["0010,0010"].asString() == "Hello") ? 0 : 1;
+
+      ASSERT_EQ(3, b["0008,1110"][i].size());
+      ASSERT_EQ(2, b["0008,1110"][1 - i].size());
+      ASSERT_EQ(b["0008,1110"][i]["0010,0010"].asString(), "Hello");
+      ASSERT_EQ(b["0008,1110"][i]["0010,0020"].asString(), "World");
+      ASSERT_EQ(b["0008,1110"][i]["0008,1030"].asString(), "Toto");
+      ASSERT_EQ(b["0008,1110"][1 - i]["0010,0010"].asString(), "Hello2");
+      ASSERT_EQ(b["0008,1110"][1 - i]["0010,0020"].asString(), "World2");
+    }
+
+    {
+      Json::Value b;
+      FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0, Encoding_Ascii);
+
+      Json::Value c;
+      Toolbox::SimplifyTags(c, b);
+
+      a[1]["PatientName"] = "Hello2";  // To remove the Data URI Scheme encoding
+      ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a));
+    }
+  }
+}
+
+
+
+TEST(ParsedDicomFile, InsertReplaceStrings)
+{
+  ParsedDicomFile f;
+
+  f.Insert(DICOM_TAG_PATIENT_NAME, "World", false);
+  ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException);  // Already existing tag
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, "Toto");  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, "Tata");  // (**)
+
+  std::string s;
+
+  ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession");
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession2", DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession2");
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession3", DicomReplaceMode_ThrowIfAbsent);
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_EQ(s, "Accession3");
+
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
+  ASSERT_EQ(s, "World");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
+  ASSERT_EQ(s, "Toto");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
+  ASSERT_EQ(s, "Toto");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
+  ASSERT_EQ(s, "Tata");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
+  ASSERT_EQ(s, "Tata");
+}
+
+
+
+
+TEST(ParsedDicomFile, InsertReplaceJson)
+{
+  ParsedDicomFile f;
+
+  Json::Value a;
+  CreateSampleJson(a);
+
+  ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  f.Remove(REFERENCED_STUDY_SEQUENCE);  // No effect
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException);
+  f.Remove(REFERENCED_STUDY_SEQUENCE);
+  ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
+
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent);
+  ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent);
+  ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
+
+  {
+    Json::Value b;
+    f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0);
+
+    Json::Value c;
+    Toolbox::SimplifyTags(c, b);
+
+    ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a));
+    ASSERT_NE(0, c["ReferencedStudySequence"].compare(a));  // Because Data URI Scheme decoding was enabled
+  }
+
+  a = "data:application/octet-stream;base64,VGF0YQ==";   // echo -n "Tata" | base64 
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false);  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true);  // (**)
+
+  std::string s;
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
+  ASSERT_EQ(s, a.asString());
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
+  ASSERT_EQ(s, a.asString());
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
+  ASSERT_EQ(s, "Tata");
+  ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
+  ASSERT_EQ(s, "Tata");
+}
+
+
+TEST(ParsedDicomFile, JsonEncoding)
+{
+  ParsedDicomFile f;
+
+  for (unsigned int i = 0; i < testEncodingsCount; i++)
+  {
+    if (testEncodings[i] != Encoding_Windows1251)
+    {
+      //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
+      f.SetEncoding(testEncodings[i]);
+
+      if (testEncodings[i] != Encoding_Ascii)
+      {
+        ASSERT_EQ(testEncodings[i], f.GetEncoding());
+      }
+
+      Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
+      f.Replace(DICOM_TAG_PATIENT_NAME, s, false);
+
+      Json::Value v;
+      f.ToJson(v, DicomToJsonFormat_Simple, DicomToJsonFlags_Default, 0);
+      ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i]));
+    }
+  }
+}
+
+
+TEST(ParsedDicomFile, ToJsonFlags1)
+{
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1);
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1);
+
+  ParsedDicomFile f;
+  f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false);  // Even group => public tag
+  f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false);  // Even group => public, unknown tag
+  f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false);  // Odd group => private tag
+
+  Json::Value v;
+  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(6, v.getMemberNames().size());
+  ASSERT_FALSE(v.isMember("7052,1000"));
+  ASSERT_FALSE(v.isMember("7053,1000"));
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_EQ(Json::stringValue, v["7050,1000"].type());
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+
+  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(7, v.getMemberNames().size());
+  ASSERT_FALSE(v.isMember("7052,1000"));
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_TRUE(v.isMember("7053,1000"));
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());  // TODO SHOULD BE STRING
+
+  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludeUnknownTags, 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(7, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_TRUE(v.isMember("7052,1000"));
+  ASSERT_FALSE(v.isMember("7053,1000"));
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());  // TODO SHOULD BE STRING
+
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(8, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_TRUE(v.isMember("7052,1000"));
+  ASSERT_TRUE(v.isMember("7053,1000"));
+  ASSERT_EQ("Some public tag", v["7050,1000"].asString());
+  ASSERT_EQ(Json::nullValue, v["7052,1000"].type());  // TODO SHOULD BE STRING
+  ASSERT_EQ(Json::nullValue, v["7053,1000"].type());  // TODO SHOULD BE STRING
+}
+
+
+TEST(ParsedDicomFile, ToJsonFlags2)
+{
+  ParsedDicomFile f;
+  f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false);
+
+  Json::Value v;
+  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(5, v.getMemberNames().size());
+  ASSERT_FALSE(v.isMember("7fe0,0010"));  
+
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(6, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7fe0,0010"));  
+  ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type());  
+
+  f.ToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(6, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7fe0,0010"));  
+  ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());  
+  ASSERT_EQ("Pixels", v["7fe0,0010"].asString());  
+
+  f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(6, v.getMemberNames().size());
+  ASSERT_TRUE(v.isMember("7fe0,0010"));  
+  ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());
+  std::string mime, content;
+  Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString());
+  ASSERT_EQ("application/octet-stream", mime);
+  ASSERT_EQ("Pixels", content);
+}
--- a/UnitTestsSources/ImageTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/ImageTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -50,15 +50,15 @@
 TEST(PngWriter, ColorPattern)
 {
   Orthanc::PngWriter w;
-  int width = 17;
-  int height = 61;
-  int pitch = width * 3;
+  unsigned int width = 17;
+  unsigned int height = 61;
+  unsigned int pitch = width * 3;
 
   std::vector<uint8_t> image(height * pitch);
-  for (int y = 0; y < height; y++)
+  for (unsigned int y = 0; y < height; y++)
   {
     uint8_t *p = &image[0] + y * pitch;
-    for (int x = 0; x < width; x++, p += 3)
+    for (unsigned int x = 0; x < width; x++, p += 3)
     {
       p[0] = (y % 3 == 0) ? 255 : 0;
       p[1] = (y % 3 == 1) ? 255 : 0;
--- a/UnitTestsSources/LuaTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/LuaTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -171,7 +171,7 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson("hello");
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ("hello", v.asString());
   }
 
@@ -179,7 +179,7 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson(42.25);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_FLOAT_EQ(42.25f, v.asFloat());
   }
 
@@ -187,7 +187,7 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson(-42);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ(-42, v.asInt());
   }
 
@@ -196,7 +196,7 @@
     Json::Value vv = Json::arrayValue;
     f.PushJson(vv);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ(Json::arrayValue, v.type());
   }
 
@@ -205,7 +205,7 @@
     Json::Value vv = Json::objectValue;
     f.PushJson(vv);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     // Lua does not make the distinction between empty lists and empty objects
     ASSERT_EQ(Json::arrayValue, v.type());
   }
@@ -214,7 +214,7 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson(b);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ(Json::objectValue, v.type());
     ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat());
     ASSERT_FLOAT_EQ(44.37f, v["b"].asFloat());
@@ -225,7 +225,7 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson(c);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ(Json::arrayValue, v.type());
     ASSERT_EQ("test3", v[0].asString());
     ASSERT_EQ("test1", v[1].asString());
@@ -236,9 +236,13 @@
     Orthanc::LuaFunctionCall f(lua, "identity");
     f.PushJson(a);
     Json::Value v;
-    f.ExecuteToJson(v);
+    f.ExecuteToJson(v, false);
     ASSERT_EQ("World", v["Hello"].asString());
+    ASSERT_EQ(Json::intValue, v["List"][0]["a"].type());
+    ASSERT_EQ(Json::realValue, v["List"][0]["b"].type());
+    ASSERT_EQ(Json::intValue, v["List"][0]["c"].type());
     ASSERT_EQ(42, v["List"][0]["a"].asInt());
+    ASSERT_FLOAT_EQ(44.37f, v["List"][0]["b"].asFloat());
     ASSERT_EQ(44, v["List"][0]["b"].asInt());
     ASSERT_EQ(-43, v["List"][0]["c"].asInt());
     ASSERT_EQ("test3", v["List"][1][0].asString());
@@ -247,6 +251,23 @@
   }
 
   {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(a);
+    Json::Value v;
+    f.ExecuteToJson(v, true);
+    ASSERT_EQ("World", v["Hello"].asString());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["a"].type());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["b"].type());
+    ASSERT_EQ(Json::stringValue, v["List"][0]["c"].type());
+    ASSERT_EQ("42", v["List"][0]["a"].asString());
+    ASSERT_EQ("44.37", v["List"][0]["b"].asString());
+    ASSERT_EQ("-43", v["List"][0]["c"].asString());
+    ASSERT_EQ("test3", v["List"][1][0].asString());
+    ASSERT_EQ("test1", v["List"][1][1].asString());
+    ASSERT_EQ("test2", v["List"][1][2].asString());
+  }
+
+  {
     Orthanc::LuaFunctionCall f(lua, "DumpJson");
     f.PushJson(a);
     std::string s;
--- a/UnitTestsSources/MemoryCacheTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/MemoryCacheTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -190,11 +190,6 @@
       LOG(INFO) << "Removing cache entry for " << value_;
       log_ += boost::lexical_cast<std::string>(value_) + " ";
     }
-
-    int GetValue() const 
-    {
-      return value_;
-    }
   };
 
   class IntegerProvider : public Orthanc::ICachePageProvider
@@ -235,8 +230,6 @@
 
 
 
-
-
 namespace
 {
   class S : public Orthanc::IDynamicObject
@@ -253,11 +246,6 @@
     {
       return value_;
     }
-
-    static const std::string& Access(const Orthanc::IDynamicObject& obj)
-    {
-      return dynamic_cast<const S&>(obj).GetValue();
-    }
   };
 }
 
--- a/UnitTestsSources/MultiThreadingTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -183,7 +183,7 @@
       outputs.push_back(boost::lexical_cast<std::string>(b));
     }
 
-    Toolbox::USleep(100000);
+    Toolbox::USleep(30000);
 
     return true;
   }
@@ -202,7 +202,7 @@
     {
       printf(">> %s: %0.1f\n", it->c_str(), 100.0f * s->GetProgress(*it));
     }
-    Toolbox::USleep(10000);
+    Toolbox::USleep(3000);
   }
 }
 
--- a/UnitTestsSources/RestApiTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/RestApiTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -34,6 +34,8 @@
 #include "gtest/gtest.h"
 
 #include <ctype.h>
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
 
 #include "../Core/ChunkedBuffer.h"
 #include "../Core/HttpClient.h"
@@ -43,6 +45,7 @@
 #include "../Core/OrthancException.h"
 #include "../Core/Compression/ZlibCompressor.h"
 #include "../Core/RestApi/RestApiHierarchy.h"
+#include "../Core/HttpServer/HttpContentNegociation.h"
 
 using namespace Orthanc;
 
@@ -335,3 +338,126 @@
   ASSERT_TRUE(HandleGet(root, "/hello2/a/b"));
   ASSERT_EQ(testValue, 4);
 }
+
+
+
+
+
+namespace
+{
+  class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler
+  {
+  private:
+    std::string type_;
+    std::string subtype_;
+
+  public:
+    AcceptHandler()
+    {
+      Reset();
+    }
+
+    void Reset()
+    {
+      Handle("nope", "nope");
+    }
+
+    const std::string& GetType() const
+    {
+      return type_;
+    }
+
+    const std::string& GetSubType() const
+    {
+      return subtype_;
+    }
+
+    virtual void Handle(const std::string& type,
+                        const std::string& subtype)
+    {
+      type_ = type;
+      subtype_ = subtype;
+    }
+  };
+}
+
+
+TEST(RestApi, HttpContentNegociation)
+{
+  // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+
+  AcceptHandler h;
+
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("audio/mp3", h);
+    d.Register("audio/basic", h);
+
+    ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
+    ASSERT_EQ("audio", h.GetType());
+    ASSERT_EQ("basic", h.GetSubType());
+
+    ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope"));
+    ASSERT_EQ("audio", h.GetType());
+    ASSERT_EQ("mp3", h.GetSubType());
+    
+    ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf"));
+    
+    ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf"));
+    ASSERT_EQ("audio", h.GetType());
+  }
+
+  // "This would be interpreted as "text/html and text/x-c are the
+  // preferred media types, but if they do not exist, then send the
+  // text/x-dvi entity, and if that does not exist, send the
+  // text/plain entity.""
+  const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
+  
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("text/plain", h);
+    d.Register("text/html", h);
+    d.Register("text/x-dvi", h);
+    ASSERT_TRUE(d.Apply(T1));
+    ASSERT_EQ("text", h.GetType());
+    ASSERT_EQ("html", h.GetSubType());
+  }
+  
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("text/plain", h);
+    d.Register("text/x-dvi", h);
+    d.Register("text/x-c", h);
+    ASSERT_TRUE(d.Apply(T1));
+    ASSERT_EQ("text", h.GetType());
+    ASSERT_EQ("x-c", h.GetSubType());
+  }
+  
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("text/plain", h);
+    d.Register("text/x-dvi", h);
+    d.Register("text/x-c", h);
+    d.Register("text/html", h);
+    ASSERT_TRUE(d.Apply(T1));
+    ASSERT_EQ("text", h.GetType());
+    ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
+  }
+  
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("text/plain", h);
+    d.Register("text/x-dvi", h);
+    ASSERT_TRUE(d.Apply(T1));
+    ASSERT_EQ("text", h.GetType());
+    ASSERT_EQ("x-dvi", h.GetSubType());
+  }
+  
+  {
+    Orthanc::HttpContentNegociation d;
+    d.Register("text/plain", h);
+    ASSERT_TRUE(d.Apply(T1));
+    ASSERT_EQ("text", h.GetType());
+    ASSERT_EQ("plain", h.GetSubType());
+  }
+}
--- a/UnitTestsSources/ServerIndexTests.cpp	Wed Sep 23 10:29:06 2015 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Wed Nov 18 10:16:21 2015 +0100
@@ -33,13 +33,13 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
-#include "../Core/DicomFormat/DicomNullValue.h"
 #include "../Core/FileStorage/FilesystemStorage.h"
 #include "../Core/Logging.h"
 #include "../Core/Uuid.h"
 #include "../OrthancServer/DatabaseWrapper.h"
 #include "../OrthancServer/ServerContext.h"
 #include "../OrthancServer/ServerIndex.h"
+#include "../OrthancServer/Search/LookupIdentifierQuery.h"
 
 #include <ctype.h>
 #include <algorithm>
@@ -122,10 +122,12 @@
       }
 
       index_->SetListener(*listener_);
+      index_->Open();
     }
 
     virtual void TearDown()
     {
+      index_->Close();
       index_.reset(NULL);
       listener_.reset(NULL);
     }
@@ -244,6 +246,18 @@
           throw OrthancException(ErrorCode_InternalError);
       }
     }
+
+
+    void DoLookup(std::list<std::string>& result,
+                  ResourceType level,
+                  const DicomTag& tag,
+                  const std::string& value)
+    {
+      LookupIdentifierQuery query(level);
+      query.AddConstraint(tag, IdentifierConstraintType_Equal, value);
+      query.Apply(result, *index_);
+    }
+
   };
 }
 
@@ -660,6 +674,7 @@
   Toolbox::RemoveFile(path + "/index");
   FilesystemStorage storage(path);
   DatabaseWrapper db;   // The SQLite DB is in memory
+  db.Open();
   ServerContext context(db, storage);
   ServerIndex& index = context.GetIndex();
 
@@ -669,6 +684,7 @@
   ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
 
   context.Stop();
+  db.Close();
 }
 
 
@@ -682,41 +698,69 @@
     index_->CreateResource("d", ResourceType_Series)   // 3
   };
 
-  index_->SetMainDicomTag(a[0], DICOM_TAG_STUDY_INSTANCE_UID, "0");
-  index_->SetMainDicomTag(a[1], DICOM_TAG_STUDY_INSTANCE_UID, "1");
-  index_->SetMainDicomTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
-  index_->SetMainDicomTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
+  index_->SetIdentifierTag(a[0], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  index_->SetIdentifierTag(a[1], DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  index_->SetIdentifierTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  index_->SetIdentifierTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
 
-  std::list<int64_t> s;
+  std::list<std::string> s;
 
-  index_->LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "0");
   ASSERT_EQ(2u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), "a") != s.end());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), "c") != s.end());
+
+  DoLookup(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, "0");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), "d") != s.end());
 
-  index_->LookupIdentifier(s, "0");
-  ASSERT_EQ(3u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[0]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[2]) != s.end());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[3]) != s.end());
+  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), "b") != s.end());
+
+  DoLookup(s, ResourceType_Study, DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  ASSERT_EQ(1u, s.size());
+  ASSERT_TRUE(std::find(s.begin(), s.end(), "b") != s.end());
+
+  DoLookup(s, ResourceType_Series, DICOM_TAG_SERIES_INSTANCE_UID, "1");
+  ASSERT_EQ(0u, s.size());
 
-  index_->LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "1");
-  ASSERT_EQ(1u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
+  {
+    LookupIdentifierQuery query(ResourceType_Study);
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "0");
+    query.Apply(s, *index_);
+    ASSERT_EQ(3u, s.size());
+  }
 
-  index_->LookupIdentifier(s, "1");
-  ASSERT_EQ(1u, s.size());
-  ASSERT_TRUE(std::find(s.begin(), s.end(), a[1]) != s.end());
-
+  {
+    LookupIdentifierQuery query(ResourceType_Study);
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "0");
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_SmallerOrEqual, "0");
+    query.Apply(s, *index_);
+    ASSERT_EQ(2u, s.size());
+  }
 
-  /*{
-    std::list<std::string> s;
-    context.GetIndex().LookupIdentifier(s, DICOM_TAG_STUDY_INSTANCE_UID, "1.2.250.1.74.20130819132500.29000036381059");
-    for (std::list<std::string>::iterator i = s.begin(); i != s.end(); i++)
-    {
-    std::cout << "*** " << *i << std::endl;;
-    }      
-    }*/
+  {
+    LookupIdentifierQuery query(ResourceType_Study);
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "1");
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_SmallerOrEqual, "1");
+    query.Apply(s, *index_);
+    ASSERT_EQ(1u, s.size());
+  }
+
+  {
+    LookupIdentifierQuery query(ResourceType_Study);
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "1");
+    query.Apply(s, *index_);
+    ASSERT_EQ(1u, s.size());
+  }
+
+  {
+    LookupIdentifierQuery query(ResourceType_Study);
+    query.AddConstraint(DICOM_TAG_STUDY_INSTANCE_UID, IdentifierConstraintType_GreaterOrEqual, "2");
+    query.Apply(s, *index_);
+    ASSERT_EQ(0u, s.size());
+  }
 }
 
 
@@ -728,6 +772,7 @@
   Toolbox::RemoveFile(path + "/index");
   FilesystemStorage storage(path);
   DatabaseWrapper db;   // The SQLite DB is in memory
+  db.Open();
   ServerContext context(db, storage);
   ServerIndex& index = context.GetIndex();
 
@@ -781,4 +826,12 @@
   ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException);  
 
   context.Stop();
+  db.Close();
 }
+
+
+TEST(LookupIdentifierQuery, NormalizeIdentifier)
+{
+  ASSERT_EQ("H^L.LO", LookupIdentifierQuery::NormalizeIdentifier("   Hé^l.LO  %_  "));
+  ASSERT_EQ("1.2.840.113619.2.176.2025", LookupIdentifierQuery::NormalizeIdentifier("   1.2.840.113619.2.176.2025  "));
+}