changeset 1701:4aaaecae5803 db-changes

integration mainline->db-changes
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 12 Oct 2015 14:47:58 +0200
parents 21d31da73374 (diff) f5ddbd9239dd (current diff)
children 9980875edc7c
files OrthancServer/Internals/StoreScp.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.h UnitTestsSources/FromDcmtkTests.cpp
diffstat 44 files changed, 2947 insertions(+), 1319 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Oct 09 17:20:26 2015 +0200
+++ b/CMakeLists.txt	Mon Oct 12 14:47:58 2015 +0200
@@ -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)
 
 
 #####################################################################
@@ -171,6 +172,7 @@
   OrthancServer/ServerIndex.cpp
   OrthancServer/ToDcmtkBridge.cpp
   OrthancServer/DatabaseWrapper.cpp
+  OrthancServer/DatabaseWrapperBase.cpp
   OrthancServer/ServerContext.cpp
   OrthancServer/ServerEnumerations.cpp
   OrthancServer/ServerToolbox.cpp
--- a/Core/Enumerations.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Core/Enumerations.h	Mon Oct 12 14:47:58 2015 +0200
@@ -134,6 +134,7 @@
     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_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
     ErrorCode_START_PLUGINS = 1000000
   };
 
--- a/Core/HttpServer/MongooseServer.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -931,7 +931,7 @@
 #if ORTHANC_SSL_ENABLED == 0
     if (enabled)
     {
-      throw OrthancException("Orthanc has been built without SSL support");
+      throw OrthancException(ErrorCode_SslDisabled);
     }
     else
     {
--- a/Core/SQLite/Connection.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Core/SQLite/Connection.h	Mon Oct 12 14:47:58 2015 +0200
@@ -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/OrthancServer/DatabaseWrapper.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -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,68 +256,14 @@
   }
 
     
-  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()
@@ -839,15 +327,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 +355,18 @@
       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.
+      db_.BeginTransaction();
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study);
+      Toolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series);
+      db_.CommitTransaction();
+      version_ = 6;
+    }    
   }
 
 
@@ -875,90 +377,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,54 +384,89 @@
   }
 
 
-  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;
+    }
+  }
+
+
+  ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId)
+  {
+    ResourceType result;
+    ErrorCode code = base_.GetResourceType(result, resourceId);
+
+    if (code == ErrorCode_Success)
+    {
+      return result;
+    }
+    else
+    {
+      throw OrthancException(code);
+    }
   }
 
 
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
-                                          const DicomTag& tag,
-                                          const std::string& value)
+  std::string DatabaseWrapper::GetPublicId(int64_t resourceId)
+  {
+    std::string id;
+
+    if (base_.GetPublicId(id, resourceId))
+    {
+      return id;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+  }
+
+
+  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);
+
+    if (code != ErrorCode_Success)
+    {
+      throw OrthancException(code);
+    }
+  }
+
+
+  void DatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
+  {
+    ErrorCode code = base_.GetLastChange(target);
+
+    if (code != ErrorCode_Success)
+    {
+      throw OrthancException(code);
+    }
+  }
+
+
+  void DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
+                                         const DicomTag& tag,
+                                         const std::string& value)
   {
     if (!tag.IsIdentifier())
     {
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    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())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
-  }
-
-
-  void  DatabaseWrapper::LookupIdentifier(std::list<int64_t>& target,
-                                          const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT id FROM DicomIdentifiers WHERE value=?");
-
-    s.BindString(0, value);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
+    base_.LookupIdentifier(target, tag, value);
   }
 
 
--- a/OrthancServer/DatabaseWrapper.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Mon Oct 12 14:47:58 2015 +0200
@@ -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,99 @@
     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 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 +209,74 @@
 
     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 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 +303,20 @@
       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,
                                   const DicomTag& tag,
                                   const std::string& value);
 
     virtual void LookupIdentifier(std::list<int64_t>& target,
-                                  const std::string& value);
+                                  const std::string& value)
+    {
+      base_.LookupIdentifier(target, 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	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,715 @@
+/**
+ * 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>
+
+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();
+    }
+  }
+
+
+  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 DatabaseWrapperBase::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 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));
+    }
+
+    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));
+    }
+  }
+
+
+
+  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::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,
+                                             const DicomTag& tag,
+                                             const std::string& value)
+  {
+    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())
+    {
+      target.push_back(s.ColumnInt64(0));
+    }
+  }
+
+
+  void DatabaseWrapperBase::LookupIdentifier(std::list<int64_t>& target,
+                                             const std::string& value)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT id FROM DicomIdentifiers WHERE value=?");
+
+    s.BindString(0, 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	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,197 @@
+/**
+ * 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 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 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>& target,
+                          const DicomTag& tag,
+                          const std::string& value);
+
+    void LookupIdentifier(std::list<int64_t>& target,
+                          const std::string& value);
+  };
+}
+
--- a/OrthancServer/DicomProtocol/DicomServer.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -281,9 +281,8 @@
       throw OrthancException(ErrorCode_DicomPortInUse);
     }
 
+    continue_ = true;
     pimpl_->workers_.reset(new RunnableWorkersPool(4));   // Use 4 workers - TODO as a parameter?
-
-    continue_ = true;
     pimpl_->thread_ = boost::thread(ServerThread, this);
   }
 
--- a/OrthancServer/DicomProtocol/DicomServer.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/DicomProtocol/DicomServer.h	Mon Oct 12 14:47:58 2015 +0200
@@ -40,6 +40,7 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 
+
 namespace Orthanc
 {
   class DicomServer : public boost::noncopyable
--- a/OrthancServer/IDatabaseWrapper.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/IDatabaseWrapper.h	Mon Oct 12 14:47:58 2015 +0200
@@ -51,6 +51,10 @@
     {
     }
 
+    virtual void Open() = 0;
+
+    virtual void Close() = 0;
+
     virtual void AddAttachment(int64_t id,
                                const FileInfo& attachment) = 0;
 
@@ -168,6 +172,8 @@
     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;
--- a/OrthancServer/Internals/StoreScp.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -212,7 +212,7 @@
 
                 if (e.GetErrorCode() == ErrorCode_InexistentTag)
                 {
-                  LogMissingRequiredTag(summary);
+                  Toolbox::LogMissingRequiredTag(summary);
                 }
                 else
                 {
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -208,7 +208,7 @@
       context.ReadJson(full, publicId);
 
       Json::Value simplified;
-      SimplifyTags(simplified, full);
+      Toolbox::SimplifyTags(simplified, full);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -792,7 +792,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, sharedTags);
+        Toolbox::SimplifyTags(simplified, sharedTags);
         call.GetOutput().AnswerJson(simplified);
       }
       else
@@ -859,7 +859,7 @@
     if (simplify)
     {
       Json::Value simplified;
-      SimplifyTags(simplified, result);
+      Toolbox::SimplifyTags(simplified, result);
       call.GetOutput().AnswerJson(simplified);
     }
     else
@@ -1030,7 +1030,7 @@
       if (simplify)
       {
         Json::Value simplified;
-        SimplifyTags(simplified, full);
+        Toolbox::SimplifyTags(simplified, full);
         result[*it] = simplified;
       }
       else
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -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/PrepareDatabase.sql	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Mon Oct 12 14:47:58 2015 +0200
@@ -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	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ResourceFinder.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -37,8 +37,6 @@
 #include "FromDcmtkBridge.h"
 #include "ServerContext.h"
 
-#include <boost/algorithm/string/predicate.hpp>
-
 namespace Orthanc
 {
   class ResourceFinder::CandidateResources
@@ -190,9 +188,21 @@
     }
 
 
-    void RestrictMainDicomTags(const IQuery& query)
+    void RestrictMainDicomTags(const IQuery& query,
+                               bool filterPatientTagsAtStudyLevel)
     {
-      if (!query.HasMainDicomTagsFilter(level_))
+      if (filterPatientTagsAtStudyLevel &&
+          level_ == ResourceType_Patient)
+      {
+        return;
+      }
+
+      bool hasTagsAtThisLevel = query.HasMainDicomTagsFilter(level_);
+      bool hasTagsAtPatientLevel = (filterPatientTagsAtStudyLevel &&
+                                    level_ == ResourceType_Study &&
+                                    query.HasMainDicomTagsFilter(ResourceType_Patient));
+
+      if (!hasTagsAtThisLevel && !hasTagsAtPatientLevel)        
       {
         return;
       }
@@ -207,13 +217,22 @@
              it = resources.begin(); it != resources.end(); ++it)
       {
         DicomMap mainTags;
-        if (index_.GetMainDicomTags(mainTags, *it, level_))
+
+        if (hasTagsAtThisLevel &&
+            (!index_.GetMainDicomTags(mainTags, *it, level_, level_) ||
+             !query.FilterMainDicomTags(*it, level_, mainTags)))
         {
-          if (query.FilterMainDicomTags(*it, level_, mainTags))
-          {
-            filtered_.insert(*it);
-          }
+          continue;
         }
+
+        if (hasTagsAtPatientLevel &&
+            (!index_.GetMainDicomTags(mainTags, *it, ResourceType_Study, ResourceType_Patient) ||
+             !query.FilterMainDicomTags(*it, ResourceType_Patient, mainTags)))
+        {
+          continue;
+        }
+
+        filtered_.insert(*it);
       }
     }
   };
@@ -266,7 +285,14 @@
         throw OrthancException(ErrorCode_InternalError);
     }
 
-    candidates.RestrictMainDicomTags(query);
+    if (query.GetLevel() == ResourceType_Patient)
+    {
+      candidates.RestrictMainDicomTags(query, false);
+    }
+    else
+    {
+      candidates.RestrictMainDicomTags(query, true);
+    }
   }
 
 
--- a/OrthancServer/ServerContext.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -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;
--- a/OrthancServer/ServerEnumerations.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerEnumerations.h	Mon Oct 12 14:47:58 2015 +0200
@@ -116,7 +116,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
   };
--- a/OrthancServer/ServerIndex.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerIndex.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -40,6 +40,7 @@
 #include "ServerIndexChange.h"
 #include "EmbeddedResources.h"
 #include "OrthancInitialization.h"
+#include "ServerToolbox.h"
 #include "../Core/Toolbox.h"
 #include "../Core/Logging.h"
 #include "../Core/Uuid.h"
@@ -490,18 +491,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 +627,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, true);
 
       // Detect up to which level the patient/study/series/instance
       // hierarchy must be created
@@ -693,24 +679,22 @@
       if (isNewSeries)
       {
         series = CreateResource(hasher.HashSeries(), ResourceType_Series);
-        dicomSummary.ExtractSeriesInformation(dicom);
-        SetMainDicomTags(series, dicom);
+        Toolbox::SetMainDicomTags(db_, series, ResourceType_Series, dicomSummary, true);
       }
 
       // 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, true);
+        Toolbox::SetMainDicomTags(db_, study, ResourceType_Patient, dicomSummary, false);  // New in version 0.9.5 (db v6)
       }
 
       // 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, true);
       }
 
       // Create the parent-to-child links
@@ -890,14 +874,55 @@
   }
 
 
+  static std::string GetPatientIdOfStudy(IDatabaseWrapper& db,
+                                         int64_t resourceId)
+  {
+    int64_t patient;
+    if (!db.LookupParent(patient, resourceId))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    DicomMap tags;
+    db.GetMainDicomTags(tags, patient);
+
+    if (tags.HasTag(DICOM_TAG_PATIENT_ID))
+    {
+      return tags.GetValue(DICOM_TAG_PATIENT_ID).AsString();
+    }
+    else
+    {
+      return "";
+    }
+  }
+
 
   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);
+
+      target["PatientMainDicomTags"]["PatientID"] = GetPatientIdOfStudy(db_, resourceId);
+    }
+    else
+    {
+      target["MainDicomTags"] = Json::objectValue;
+      FromDcmtkBridge::ToJson(target["MainDicomTags"], tags, true);
+    }
   }
 
   bool ServerIndex::LookupResource(Json::Value& result,
@@ -1033,7 +1058,7 @@
 
     // Record the remaining information
     result["ID"] = publicId;
-    MainDicomTagsToJson(result, id);
+    MainDicomTagsToJson(result, id, type);
 
     std::string tmp;
 
@@ -2077,8 +2102,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 +2127,27 @@
     {
       return false;
     }
+
+    if (type == ResourceType_Study)
+    {
+      DicomMap tmp;
+      db_.GetMainDicomTags(tmp, id);
+
+      switch (levelOfInterest)
+      {
+        case ResourceType_Patient:
+          tmp.ExtractPatientInformation(result);
+          result.SetValue(DICOM_TAG_PATIENT_ID, GetPatientIdOfStudy(db_, id));
+          return true;
+
+        case ResourceType_Study:
+          tmp.ExtractStudyInformation(result);
+          return true;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
     else
     {
       db_.GetMainDicomTags(result, id);
@@ -2108,4 +2165,10 @@
     return db_.LookupResource(id, type, publicId);
   }
 
+
+  unsigned int ServerIndex::GetDatabaseVersion()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return db_.GetDatabaseVersion();
+  }
 }
--- a/OrthancServer/ServerIndex.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerIndex.h	Mon Oct 12 14:47:58 2015 +0200
@@ -76,7 +76,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 +111,6 @@
 
     uint64_t IncrementGlobalSequenceInternal(GlobalProperty property);
 
-    void SetMainDicomTags(int64_t resource,
-                          const DicomMap& tags);
-
     int64_t CreateResource(const std::string& publicId,
                            ResourceType type);
 
@@ -263,9 +261,12 @@
 
     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();
   };
 }
--- a/OrthancServer/ServerToolbox.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerToolbox.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -33,122 +33,298 @@
 #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 <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();
+      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();
 
-    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();
+        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());
 
-      if (type == "String")
-      {
-        target[name] = v["Value"].asString();
+          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);
+        }
       }
-      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=" + summary.GetValue(DICOM_TAG_PATIENT_ID).AsString();
       }
-      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=" + summary.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString();
+      }
+      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=" + 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
       {
-        assert(0);
+        LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
+      }
+    }
+
+
+    void SetMainDicomTags(IDatabaseWrapper& database,
+                          int64_t resource,
+                          ResourceType level,
+                          const DicomMap& dicomSummary,
+                          bool includeIdentifiers)
+    {
+      // WARNING: The database should be locked with a transaction!
+
+      DicomMap tags;
+
+      switch (level)
+      {
+        case ResourceType_Patient:
+          dicomSummary.ExtractPatientInformation(tags);
+          break;
+
+        case ResourceType_Study:
+          dicomSummary.ExtractStudyInformation(tags);
+          break;
+
+        case ResourceType_Series:
+          dicomSummary.ExtractSeriesInformation(tags);
+          break;
+
+        case ResourceType_Instance:
+          dicomSummary.ExtractInstanceInformation(tags);
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      DicomArray flattened(tags);
+      for (size_t i = 0; i < flattened.GetSize(); i++)
+      {
+        const DicomElement& element = flattened.GetElement(i);
+
+        if (includeIdentifiers ||
+            !element.GetTag().IsIdentifier())
+        {
+          database.SetMainDicomTag(resource, element.GetTag(), element.GetValue().AsString());
+        }
+      }
+    }
+
+
+    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);
+
+        switch (level)
+        {
+          case ResourceType_Patient:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, true);
+            break;
+
+          case ResourceType_Study:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Study, dicomSummary, true);
+
+            // Duplicate the patient tags at the study level (new in Orthanc 0.9.5 - db v6)
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Patient, dicomSummary, false);
+            break;
+
+          case ResourceType_Series:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Series, dicomSummary, true);
+            break;
+
+          case ResourceType_Instance:
+            Toolbox::SetMainDicomTags(database, resource, ResourceType_Instance, dicomSummary, true);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
       }
     }
   }
-
-
-  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	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/ServerToolbox.h	Mon Oct 12 14:47:58 2015 +0200
@@ -33,13 +33,32 @@
 #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 includeIdentifiers);
+
+    bool FindOneChildInstance(int64_t& result,
+                              IDatabaseWrapper& database,
+                              int64_t resource,
+                              ResourceType type);
+
+    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+                                  IStorageArea& storageArea,
+                                  ResourceType level);
+  }
 }
--- a/OrthancServer/main.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/OrthancServer/main.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -572,13 +572,28 @@
   dicomServer.Start();
   LOG(WARNING) << "DICOM server listening on port: " << dicomServer.GetPortNumber();
 
-  bool restart = StartHttpServer(context, restApi, plugins);
+  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;
 }
 
@@ -617,7 +632,7 @@
                             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)
   {
@@ -626,20 +641,20 @@
 
   if (currentVersion > ORTHANC_DATABASE_VERSION)
   {
-    LOG(ERROR) << "The version of the database (" << currentVersion
+    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);
     
@@ -647,7 +662,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);
   }
 
@@ -657,14 +672,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));
@@ -700,7 +709,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
@@ -710,10 +730,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)
@@ -751,14 +796,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
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -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;
@@ -234,50 +200,26 @@
     tmp.compressedSize = attachment.GetCompressedSize();
     tmp.compressedHash = attachment.GetCompressedMD5().c_str();
 
-    OrthancPluginErrorCode error = backend_.addAttachment(payload_, id, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.attachChild(payload_, parent, child));
   }
 
 
   void OrthancPluginDatabase::ClearChanges()
   {
-    OrthancPluginErrorCode error = backend_.clearChanges(payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.clearChanges(payload_));
   }
 
 
   void OrthancPluginDatabase::ClearExportedResources()
   {
-    OrthancPluginErrorCode error = backend_.clearExportedResources(payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.clearExportedResources(payload_));
   }
 
 
@@ -285,15 +227,7 @@
                                                 ResourceType type)
   {
     int64_t id;
-
-    OrthancPluginErrorCode error = backend_.createResource(&id, payload_, publicId.c_str(), Convert(type));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type)));
     return id;
   }
 
@@ -301,38 +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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.deleteResource(payload_, id));
   }
 
 
@@ -362,15 +278,7 @@
                                               ResourceType resourceType)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType)));
     ForwardAnswers(target);
   }
 
@@ -384,16 +292,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)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
-
+      CheckSuccess(extensions_.getAllPublicIdsWithLimit
+                   (GetContext(), payload_, Plugins::Convert(resourceType), since, limit));
       ForwardAnswers(target);
     }
     else
@@ -440,13 +340,7 @@
     answerDone_ = &done;
     done = false;
 
-    OrthancPluginErrorCode error = backend_.getChanges(GetContext(), payload_, since, maxResults);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults));
   }
 
 
@@ -454,15 +348,7 @@
                                                     int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getChildrenInternalId(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id));
     ForwardAnswers(target);
   }
 
@@ -471,15 +357,7 @@
                                                   int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.getChildrenPublicId(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id));
     ForwardAnswers(target);
   }
 
@@ -494,13 +372,7 @@
     answerDone_ = &done;
     done = false;
 
-    OrthancPluginErrorCode error = backend_.getExportedResources(GetContext(), payload_, since, maxResults);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults));
   }
 
 
@@ -512,13 +384,7 @@
     answerChanges_ = &target;
     answerDone_ = &ignored;
 
-    OrthancPluginErrorCode error = backend_.getLastChange(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.getLastChange(GetContext(), payload_));
   }
 
 
@@ -530,13 +396,7 @@
     answerExportedResources_ = &target;
     answerDone_ = &ignored;
 
-    OrthancPluginErrorCode error = backend_.getLastExportedResource(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_));
   }
 
 
@@ -546,13 +406,7 @@
     ResetAnswers();
     answerDicomMap_ = &map;
 
-    OrthancPluginErrorCode error = backend_.getMainDicomTags(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id));
   }
 
 
@@ -561,14 +415,8 @@
     ResetAnswers();
     std::string s;
 
-    OrthancPluginErrorCode error = backend_.getPublicId(GetContext(), payload_, resourceId);
+    CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId));
 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-    
     if (!ForwardSingleAnswer(s))
     {
       throw OrthancException(ErrorCode_DatabasePlugin);
@@ -581,15 +429,7 @@
   uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
   {
     uint64_t count;
-
-    OrthancPluginErrorCode error = backend_.getResourceCount(&count, payload_, Convert(resourceType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType)));
     return count;
   }
 
@@ -597,31 +437,15 @@
   ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
   {
     OrthancPluginResourceType type;
-
-    OrthancPluginErrorCode error = backend_.getResourceType(&type, payload_, resourceId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getTotalCompressedSize(&size, payload_));
     return size;
   }
 
@@ -629,15 +453,7 @@
   uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
   {
     uint64_t size;
-
-    OrthancPluginErrorCode error = backend_.getTotalUncompressedSize(&size, payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_));
     return size;
   }
 
@@ -645,15 +461,7 @@
   bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
   {
     int32_t existing;
-
-    OrthancPluginErrorCode error = backend_.isExistingResource(&existing, payload_, internalId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId));
     return (existing != 0);
   }
 
@@ -661,15 +469,7 @@
   bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
   {
     int32_t isProtected;
-
-    OrthancPluginErrorCode error = backend_.isProtectedPatient(&isProtected, payload_, internalId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId));
     return (isProtected != 0);
   }
 
@@ -678,14 +478,7 @@
                                                     int64_t id)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.listAvailableMetadata(GetContext(), payload_, id);
- 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
 
     if (type_ != _OrthancPluginDatabaseAnswerType_None &&
         type_ != _OrthancPluginDatabaseAnswerType_Int32)
@@ -711,13 +504,7 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.listAvailableAttachments(GetContext(), payload_, id);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id));
 
     if (type_ != _OrthancPluginDatabaseAnswerType_None &&
         type_ != _OrthancPluginDatabaseAnswerType_Int32)
@@ -744,17 +531,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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.logChange(payload_, &tmp));
   }
 
 
@@ -762,7 +543,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();
@@ -771,13 +552,7 @@
     tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
     tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
 
-    OrthancPluginErrorCode error = backend_.logExportedResource(payload_, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.logExportedResource(payload_, &tmp));
   }
 
     
@@ -787,14 +562,8 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupAttachment
-      (GetContext(), payload_, id, static_cast<int32_t>(contentType));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.lookupAttachment
+                 (GetContext(), payload_, id, static_cast<int32_t>(contentType)));
 
     if (type_ == _OrthancPluginDatabaseAnswerType_None)
     {
@@ -818,14 +587,8 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupGlobalProperty
-      (GetContext(), payload_, static_cast<int32_t>(property));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.lookupGlobalProperty
+                 (GetContext(), payload_, static_cast<int32_t>(property)));
 
     return ForwardSingleAnswer(target);
   }
@@ -842,13 +605,7 @@
     tmp.element = tag.GetElement();
     tmp.value = value.c_str();
 
-    OrthancPluginErrorCode error = backend_.lookupIdentifier(GetContext(), payload_, &tmp);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.lookupIdentifier(GetContext(), payload_, &tmp));
 
     ForwardAnswers(target);
   }
@@ -858,15 +615,7 @@
                                                const std::string& value)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupIdentifier2(GetContext(), payload_, value.c_str());
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.lookupIdentifier2(GetContext(), payload_, value.c_str()));
     ForwardAnswers(target);
   }
 
@@ -876,15 +625,7 @@
                                              MetadataType type)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type));
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)));
     return ForwardSingleAnswer(target);
   }
 
@@ -893,15 +634,7 @@
                                            int64_t resourceId)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.lookupParent(GetContext(), payload_, resourceId);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId));
     return ForwardSingleAnswer(parentId);
   }
 
@@ -912,13 +645,7 @@
   {
     ResetAnswers();
 
-    OrthancPluginErrorCode error = backend_.lookupResource(GetContext(), payload_, publicId.c_str());
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str()));
 
     if (type_ == _OrthancPluginDatabaseAnswerType_None)
     {
@@ -941,15 +668,7 @@
   bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.selectPatientToRecycle(GetContext(), payload_);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_));
     return ForwardSingleAnswer(internalId);
   }
 
@@ -958,15 +677,7 @@
                                                      int64_t patientIdToAvoid)
   {
     ResetAnswers();
-
-    OrthancPluginErrorCode error = backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid);
-
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
-
+    CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid));
     return ForwardSingleAnswer(internalId);
   }
 
@@ -974,14 +685,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)
     {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
+      LOG(ERROR) << "Your custom index plugin does not implement the ClearMainDicomTags() extension";
+      throw OrthancException(ErrorCode_DatabasePlugin);
     }
+
+    CheckSuccess(extensions_.clearMainDicomTags(payload_, id));
   }
 
 
@@ -994,22 +711,18 @@
     tmp.element = tag.GetElement();
     tmp.value = value.c_str();
 
-    OrthancPluginErrorCode error;
+    OrthancPluginErrorCode code;
 
     if (tag.IsIdentifier())
     {
-      error = backend_.setIdentifierTag(payload_, id, &tmp);
+      code = backend_.setIdentifierTag(payload_, id, &tmp);
     }
     else
     {
-      error = backend_.setMainDicomTag(payload_, id, &tmp);
+      code = backend_.setMainDicomTag(payload_, id, &tmp);
     }
 
-    if (error != OrthancPluginErrorCode_Success)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(code);
   }
 
 
@@ -1017,27 +730,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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(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)
-    {
-      errorDictionary_.LogError(error, true);
-      throw OrthancException(static_cast<ErrorCode>(error));
-    }
+    CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected));
   }
 
 
@@ -1048,6 +749,15 @@
     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,
@@ -1060,35 +770,17 @@
 
     virtual void Begin()
     {
-      OrthancPluginErrorCode error = backend_.startTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
+      CheckSuccess(backend_.startTransaction(payload_));
     }
 
     virtual void Rollback()
     {
-      OrthancPluginErrorCode error = backend_.rollbackTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
+      CheckSuccess(backend_.rollbackTransaction(payload_));
     }
 
     virtual void Commit()
     {
-      OrthancPluginErrorCode error = backend_.commitTransaction(payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
+      CheckSuccess(backend_.commitTransaction(payload_));
     }
   };
 
@@ -1114,14 +806,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;
@@ -1138,14 +830,7 @@
     if (extensions_.getDatabaseVersion != NULL)
     {
       uint32_t version;
-      OrthancPluginErrorCode error = extensions_.getDatabaseVersion(&version, payload_);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
-
+      CheckSuccess(extensions_.getDatabaseVersion(&version, payload_));
       return version;
     }
     else
@@ -1163,14 +848,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)
       {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
+        transaction.Commit();
+      }
+      else
+      {
+        transaction.Rollback();
+        errorDictionary_.LogError(code, true);
+        throw OrthancException(static_cast<ErrorCode>(code));
       }
     }
   }
@@ -1261,7 +954,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;
       }
 
@@ -1321,7 +1014,7 @@
           answerChanges_->push_back
             (ServerIndexChange(change.seq,
                                static_cast<ChangeType>(change.changeType),
-                               Convert(change.resourceType),
+                               Plugins::Convert(change.resourceType),
                                change.publicId,
                                change.date));                                   
         }
@@ -1347,7 +1040,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	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Mon Oct 12 14:47:58 2015 +0200
@@ -72,6 +72,8 @@
       return reinterpret_cast<OrthancPluginDatabaseContext*>(this);
     }
 
+    void CheckSuccess(OrthancPluginErrorCode code);
+
     void ResetAnswers();
 
     void ForwardAnswers(std::list<int64_t>& target);
@@ -90,6 +92,16 @@
                           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_;
@@ -217,6 +229,8 @@
     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);
--- a/Plugins/Engine/OrthancPlugins.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -1006,7 +1006,7 @@
         else
         {
           Json::Value simplified;
-          SimplifyTags(simplified, instance.GetJson());
+          Toolbox::SimplifyTags(simplified, instance.GetJson());
           s = writer.write(simplified);
         }
 
@@ -1709,6 +1709,23 @@
         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;
+      }
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
--- a/Plugins/Engine/PluginsEnumerations.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/PluginsEnumerations.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -66,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)
@@ -188,7 +210,7 @@
     }
 
 
-
+#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
     DcmEVR Convert(OrthancPluginValueRepresentation vr)
     {
       switch (vr)
@@ -278,5 +300,6 @@
           throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
     }
+#endif
   }
 }
--- a/Plugins/Engine/PluginsEnumerations.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/PluginsEnumerations.h	Mon Oct 12 14:47:58 2015 +0200
@@ -37,7 +37,9 @@
 #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
 {
@@ -45,6 +47,8 @@
   {
     OrthancPluginResourceType Convert(ResourceType type);
 
+    ResourceType Convert(OrthancPluginResourceType type);
+
     OrthancPluginChangeType Convert(ChangeType type);
 
     OrthancPluginPixelFormat Convert(PixelFormat format);
@@ -55,7 +59,9 @@
 
     FileContentType Convert(OrthancPluginContentType type);
 
+#if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0
     DcmEVR Convert(OrthancPluginValueRepresentation vr);
+#endif
   }
 }
 
--- a/Plugins/Engine/PluginsManager.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Engine/PluginsManager.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -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());
       }
     }
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Mon Oct 12 14:47:58 2015 +0200
@@ -655,7 +655,12 @@
       void* payload,
       uint32_t targetVersion,
       OrthancPluginStorageArea* storageArea);
-  } OrthancPluginDatabaseExtensions;
+ 
+    OrthancPluginErrorCode  (*clearMainDicomTags) (
+      /* inputs */
+      void* payload,
+      int64_t id);
+   } OrthancPluginDatabaseExtensions;
 
 /*<! @endcond */
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Oct 12 14:47:58 2015 +0200
@@ -267,6 +267,7 @@
     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_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
   } OrthancPluginErrorCode;
@@ -422,6 +423,7 @@
     _OrthancPluginService_RestApiPostAfterPlugins = 3011,
     _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
     _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
 
     /* Access to DICOM instances */
     _OrthancPluginService_GetInstanceRemoteAet = 4000,
@@ -3766,6 +3768,43 @@
   }
 
 
+
+
+  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);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Include/orthanc/OrthancCppDatabasePlugin.h	Mon Oct 12 14:47:58 2015 +0200
@@ -122,6 +122,11 @@
     {
     }
 
+    OrthancPluginContext* GetContext()
+    {
+      return context_;
+    }
+
     void LogError(const std::string& message)
     {
       OrthancPluginLogError(context_, message.c_str());
@@ -456,8 +461,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;
   };
 
 
@@ -1780,6 +1792,28 @@
     }
 
     
+    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)
+      {
+        return e.GetErrorCode();
+      }
+    }
+
+    
   public:
     /**
      * Register a custom database back-end written in C++.
@@ -1847,6 +1881,7 @@
       extensions.getAllPublicIdsWithLimit = GetAllPublicIdsWithLimit;
       extensions.getDatabaseVersion = GetDatabaseVersion;
       extensions.upgradeDatabase = UpgradeDatabase;
+      extensions.clearMainDicomTags = ClearMainDicomTags;
 
       OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
       if (!context)
--- a/Plugins/Samples/Common/OrthancPlugins.cmake	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Samples/Common/OrthancPlugins.cmake	Mon Oct 12 14:47:58 2015 +0200
@@ -1,34 +1,11 @@
+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 ()
-
-
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
-    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${SAMPLES_ROOT}/Common/VersionScript.map -Wl,--no-undefined")
-elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
-  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${CMAKE_SOURCE_DIR}/Plugins/Samples/Common/ExportedSymbols.list")
-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)
@@ -43,8 +20,10 @@
   link_libraries(dl rt)
 endif()
 
+
 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	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,72 @@
+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/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	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,560 @@
+/**
+ * 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_);
+
+  // 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().AsString());
+  }
+}
+
+
+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	Mon Oct 12 14:47:58 2015 +0200
@@ -0,0 +1,280 @@
+/**
+ * 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 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*/,
+                                uint16_t group,
+                                uint16_t element,
+                                const char* value)
+  {
+    base_.LookupIdentifier(target, Orthanc::DicomTag(group, element), value);
+  }
+
+  virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                const char* value)
+  {
+    base_.LookupIdentifier(target, 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_.SetMainDicomTag(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	Mon Oct 12 14:47:58 2015 +0200
@@ -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	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Mon Oct 12 14:47:58 2015 +0200
@@ -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	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Samples/ServeFolders/CMakeLists.txt	Mon Oct 12 14:47:58 2015 +0200
@@ -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/WebSkeleton/CMakeLists.txt	Fri Oct 09 17:20:26 2015 +0200
+++ b/Plugins/Samples/WebSkeleton/CMakeLists.txt	Mon Oct 12 14:47:58 2015 +0200
@@ -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/Configuration.json	Fri Oct 09 17:20:26 2015 +0200
+++ b/Resources/Configuration.json	Mon Oct 12 14:47:58 2015 +0200
@@ -243,7 +243,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
--- a/Resources/ErrorCodes.json	Fri Oct 09 17:20:26 2015 +0200
+++ b/Resources/ErrorCodes.json	Mon Oct 12 14:47:58 2015 +0200
@@ -492,5 +492,10 @@
     "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"
   }
 ]
--- a/UnitTestsSources/FromDcmtkTests.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -394,7 +394,7 @@
       FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, 0, Encoding_Ascii);
 
       Json::Value c;
-      SimplifyTags(c, b);
+      Toolbox::SimplifyTags(c, b);
 
       a[1]["PatientName"] = "Hello2";  // To remove the Data URI Scheme encoding
       ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a));
@@ -473,7 +473,7 @@
     f.ToJson(b, DicomToJsonFormat_Full, 0);
 
     Json::Value c;
-    SimplifyTags(c, b);
+    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/UnitTestsSources/ServerIndexTests.cpp	Fri Oct 09 17:20:26 2015 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Mon Oct 12 14:47:58 2015 +0200
@@ -122,10 +122,12 @@
       }
 
       index_->SetListener(*listener_);
+      index_->Open();
     }
 
     virtual void TearDown()
     {
+      index_->Close();
       index_.reset(NULL);
       listener_.reset(NULL);
     }
@@ -660,6 +662,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 +672,7 @@
   ASSERT_EQ(4u, index.IncrementGlobalSequence(GlobalProperty_AnonymizationSequence));
 
   context.Stop();
+  db.Close();
 }
 
 
@@ -728,6 +732,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 +786,5 @@
   ASSERT_THROW(Toolbox::GetFileSize(path + "/index"), OrthancException);  
 
   context.Stop();
+  db.Close();
 }