changeset 1311:d1a430176401

integration db-changes->default
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 11 Feb 2015 10:51:45 +0100
parents f796207e3df1 (current diff) 61ce8147f30d (diff)
children b82a3c05b66b
files
diffstat 13 files changed, 3681 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Feb 11 10:26:17 2015 +0100
+++ b/CMakeLists.txt	Wed Feb 11 10:51:45 2015 +0100
@@ -129,6 +129,7 @@
   Plugins/Engine/SharedLibrary.cpp
   Plugins/Engine/PluginsManager.cpp
   Plugins/Engine/OrthancPlugins.cpp
+  Plugins/Engine/OrthancPluginDatabase.cpp
   )
 
 
@@ -477,6 +478,8 @@
     FILES
     ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h 
     ${ORTHANC_ROOT}/Plugins/Include/OrthancCPlugin.h 
+    ${ORTHANC_ROOT}/Plugins/Include/OrthancCDatabasePlugin.h 
+    ${ORTHANC_ROOT}/Plugins/Include/OrthancCppDatabasePlugin.h 
     DESTINATION include/orthanc
     )
 endif()
--- a/Core/DicomFormat/DicomMap.cpp	Wed Feb 11 10:26:17 2015 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Wed Feb 11 10:51:45 2015 +0100
@@ -191,6 +191,17 @@
   }
 
 
+  void DicomMap::Assign(const DicomMap& other)
+  {
+    Clear();
+
+    for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it)
+    {
+      map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+  }
+
+
   const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
   {
     const DicomValue* value = TestAndGetValue(tag);
--- a/Core/DicomFormat/DicomMap.h	Wed Feb 11 10:26:17 2015 +0100
+++ b/Core/DicomFormat/DicomMap.h	Wed Feb 11 10:51:45 2015 +0100
@@ -77,8 +77,10 @@
     {
       Clear();
     }
+    
+    DicomMap* Clone() const;
 
-    DicomMap* Clone() const;
+    void Assign(const DicomMap& other);
 
     void Clear();
 
--- a/OrthancServer/ServerIndex.cpp	Wed Feb 11 10:26:17 2015 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Feb 11 10:51:45 2015 +0100
@@ -232,6 +232,11 @@
     ~Transaction()
     {
       index_.listener_->EndTransaction();
+
+      if (!isCommitted_)
+      {
+        transaction_->Rollback();
+      }
     }
 
     void Commit(uint64_t sizeOfAddedFiles)
@@ -810,8 +815,8 @@
     uint64_t us = db_.GetTotalUncompressedSize();
     target["TotalDiskSize"] = boost::lexical_cast<std::string>(cs);
     target["TotalUncompressedSize"] = boost::lexical_cast<std::string>(us);
-    target["TotalDiskSizeMB"] = boost::lexical_cast<unsigned int>(cs / MEGA_BYTES);
-    target["TotalUncompressedSizeMB"] = boost::lexical_cast<unsigned int>(us / MEGA_BYTES);
+    target["TotalDiskSizeMB"] = static_cast<unsigned int>(cs / MEGA_BYTES);
+    target["TotalUncompressedSizeMB"] = static_cast<unsigned int>(us / MEGA_BYTES);
 
     target["CountPatients"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Patient));
     target["CountStudies"] = static_cast<unsigned int>(db_.GetResourceCount(ResourceType_Study));
@@ -1147,6 +1152,7 @@
                                         const std::string& remoteModality)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction transaction(*this);
 
     int64_t id;
     ResourceType type;
@@ -1205,7 +1211,6 @@
       }
     }
 
-    // No need for a SQLite::ITransaction here, as we only insert 1 record
     ExportedResource resource(-1, 
                               type,
                               publicId,
@@ -1215,7 +1220,9 @@
                               studyInstanceUid,
                               seriesInstanceUid,
                               sopInstanceUid);
+
     db_.LogExportedResource(resource);
+    transaction.Commit(0);
   }
 
 
@@ -1384,6 +1391,7 @@
                                         bool isProtected)
   {
     boost::mutex::scoped_lock lock(mutex_);
+    Transaction transaction(*this);
 
     // Lookup for the requested resource
     int64_t id;
@@ -1394,8 +1402,8 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    // No need for a SQLite::ITransaction here, as we only make 1 write to the DB
     db_.SetProtectedPatient(id, isProtected);
+    transaction.Commit(0);
 
     if (isProtected)
       LOG(INFO) << "Patient " << publicId << " has been protected";
@@ -1597,12 +1605,10 @@
   uint64_t ServerIndex::IncrementGlobalSequence(GlobalProperty sequence)
   {
     boost::mutex::scoped_lock lock(mutex_);
-
-    std::auto_ptr<SQLite::ITransaction> transaction(db_.StartTransaction());
+    Transaction transaction(*this);
 
-    transaction->Begin();
     uint64_t seq = IncrementGlobalSequenceInternal(sequence);
-    transaction->Commit();
+    transaction.Commit(0);
 
     return seq;
   }
@@ -1613,8 +1619,7 @@
                               const std::string& publicId)
   {
     boost::mutex::scoped_lock lock(mutex_);
-    std::auto_ptr<SQLite::ITransaction> transaction(db_.StartTransaction());
-    transaction->Begin();
+    Transaction transaction(*this);
 
     int64_t id;
     ResourceType type;
@@ -1624,8 +1629,7 @@
     }
 
     LogChange(id, changeType, type, publicId);
-
-    transaction->Commit();
+    transaction.Commit(0);
   }
 
 
@@ -1747,9 +1751,9 @@
 
     target = Json::objectValue;
     target["DiskSize"] = boost::lexical_cast<std::string>(compressedSize);
-    target["DiskSizeMB"] = boost::lexical_cast<unsigned int>(compressedSize / MEGA_BYTES);
+    target["DiskSizeMB"] = static_cast<unsigned int>(compressedSize / MEGA_BYTES);
     target["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize);
-    target["UncompressedSizeMB"] = boost::lexical_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
+    target["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES);
 
     switch (type)
     {
@@ -1977,7 +1981,6 @@
                                      FileContentType type)
   {
     boost::mutex::scoped_lock lock(mutex_);
-
     Transaction t(*this);
 
     ResourceType rtype;
--- a/OrthancServer/main.cpp	Wed Feb 11 10:26:17 2015 +0100
+++ b/OrthancServer/main.cpp	Wed Feb 11 10:51:45 2015 +0100
@@ -385,47 +385,61 @@
 
 static bool StartOrthanc(int argc, char *argv[])
 {
-  std::auto_ptr<IDatabaseWrapper> database;
-  database.reset(Configuration::CreateDatabaseWrapper());
+#if ENABLE_PLUGINS == 1
+  OrthancPlugins orthancPlugins;
+  orthancPlugins.SetCommandLineArguments(argc, argv);
+  PluginsManager pluginsManager;
+  pluginsManager.RegisterServiceProvider(orthancPlugins);
+  LoadPlugins(pluginsManager);
+#endif
 
-
-  // "storage" must be declared BEFORE "ServerContext context", to
-  // avoid mess in the invokation order of the destructors.
+  // "storage" and "database" must be declared BEFORE "ServerContext
+  // context", to avoid mess in the invokation order of the destructors.
+  std::auto_ptr<IDatabaseWrapper> database;
   std::auto_ptr<IStorageArea>  storage;
-
-  ServerContext context(*database);
+  std::auto_ptr<ServerContext> context;
 
-  context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
-  context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
+  if (orthancPlugins.HasDatabase())
+  {
+    context.reset(new ServerContext(orthancPlugins.GetDatabase()));
+  }
+  else
+  {
+    database.reset(Configuration::CreateDatabaseWrapper());
+    context.reset(new ServerContext(*database));
+  }
 
-  LoadLuaScripts(context);
+  context->SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
+  context->SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
+
+  LoadLuaScripts(*context);
 
   try
   {
-    context.GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0));
+    context->GetIndex().SetMaximumPatientCount(Configuration::GetGlobalIntegerParameter("MaximumPatientCount", 0));
   }
   catch (...)
   {
-    context.GetIndex().SetMaximumPatientCount(0);
+    context->GetIndex().SetMaximumPatientCount(0);
   }
 
   try
   {
     uint64_t size = Configuration::GetGlobalIntegerParameter("MaximumStorageSize", 0);
-    context.GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
+    context->GetIndex().SetMaximumStorageSize(size * 1024 * 1024);
   }
   catch (...)
   {
-    context.GetIndex().SetMaximumStorageSize(0);
+    context->GetIndex().SetMaximumStorageSize(0);
   }
 
-  MyDicomServerFactory serverFactory(context);
+  MyDicomServerFactory serverFactory(*context);
   bool isReset = false;
     
   {
     // DICOM server
     DicomServer dicomServer;
-    OrthancApplicationEntityFilter dicomFilter(context);
+    OrthancApplicationEntityFilter dicomFilter(*context);
     dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false));
     dicomServer.SetStoreRequestHandlerFactory(serverFactory);
     dicomServer.SetMoveRequestHandlerFactory(serverFactory);
@@ -435,7 +449,7 @@
     dicomServer.SetApplicationEntityFilter(dicomFilter);
 
     // HTTP server
-    MyIncomingHttpRequestFilter httpFilter(context);
+    MyIncomingHttpRequestFilter httpFilter(*context);
     MongooseServer httpServer;
     httpServer.SetPortNumber(Configuration::GetGlobalIntegerParameter("HttpPort", 8042));
     httpServer.SetRemoteAccessAllowed(Configuration::GetGlobalBoolParameter("RemoteAccessAllowed", false));
@@ -457,7 +471,7 @@
       httpServer.SetSslEnabled(false);
     }
 
-    OrthancRestApi restApi(context);
+    OrthancRestApi restApi(*context);
 
 #if ORTHANC_STANDALONE == 1
     EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER);
@@ -466,15 +480,10 @@
 #endif
 
 #if ENABLE_PLUGINS == 1
-    OrthancPlugins orthancPlugins(context);
-    orthancPlugins.SetCommandLineArguments(argc, argv);
+    orthancPlugins.SetServerContext(*context);
     orthancPlugins.SetOrthancRestApi(restApi);
-
-    PluginsManager pluginsManager;
-    pluginsManager.RegisterServiceProvider(orthancPlugins);
-    LoadPlugins(pluginsManager);
     httpServer.RegisterHandler(orthancPlugins);
-    context.SetOrthancPlugins(pluginsManager, orthancPlugins);
+    context->SetOrthancPlugins(pluginsManager, orthancPlugins);
 #endif
 
     httpServer.RegisterHandler(staticResources);
@@ -494,7 +503,7 @@
       storage.reset(Configuration::CreateStorageArea());
     }
     
-    context.SetStorageArea(*storage);
+    context->SetStorageArea(*storage);
 
 
     // GO !!! Start the requested servers
@@ -531,7 +540,7 @@
     LOG(WARNING) << "Orthanc is stopping";
 
 #if ENABLE_PLUGINS == 1
-    context.ResetOrthancPlugins();
+    context->ResetOrthancPlugins();
     orthancPlugins.Stop();
     LOG(WARNING) << "    Plugins have stopped";
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Feb 11 10:51:45 2015 +0100
@@ -0,0 +1,1099 @@
+/**
+ * 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 "OrthancPluginDatabase.h"
+
+#include "../../Core/OrthancException.h"
+
+#include <cassert>
+#include <glog/logging.h>
+
+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,
+                    static_cast<FileContentType>(attachment.contentType),
+                    attachment.uncompressedSize,
+                    attachment.uncompressedHash,
+                    static_cast<CompressionType>(attachment.compressionType),
+                    attachment.compressedSize,
+                    attachment.compressedHash);
+  }
+
+
+  void OrthancPluginDatabase::ResetAnswers()
+  {
+    type_ = _OrthancPluginDatabaseAnswerType_None;
+
+    answerDicomMap_ = NULL;
+    answerChanges_ = NULL;
+    answerExportedResources_ = NULL;
+    answerDone_ = NULL;
+  }
+
+
+  void OrthancPluginDatabase::ForwardAnswers(std::list<int64_t>& target)
+  {
+    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
+        type_ != _OrthancPluginDatabaseAnswerType_Int64)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    target.clear();
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_Int64)
+    {
+      for (std::list<int64_t>::const_iterator 
+             it = answerInt64_.begin(); it != answerInt64_.end(); it++)
+      {
+        target.push_back(*it);
+      }
+    }
+  }
+
+
+  void OrthancPluginDatabase::ForwardAnswers(std::list<std::string>& target)
+  {
+    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
+        type_ != _OrthancPluginDatabaseAnswerType_String)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    target.clear();
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_String)
+    {
+      for (std::list<std::string>::const_iterator 
+             it = answerStrings_.begin(); it != answerStrings_.end(); it++)
+      {
+        target.push_back(*it);
+      }
+    }
+  }
+
+
+  bool OrthancPluginDatabase::ForwardSingleAnswer(std::string& target)
+  {
+    if (type_ == _OrthancPluginDatabaseAnswerType_None)
+    {
+      return false;
+    }
+    else if (type_ == _OrthancPluginDatabaseAnswerType_String &&
+             answerStrings_.size() == 1)
+    {
+      target = answerStrings_.front();
+      return true; 
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  bool OrthancPluginDatabase::ForwardSingleAnswer(int64_t& target)
+  {
+    if (type_ == _OrthancPluginDatabaseAnswerType_None)
+    {
+      return false;
+    }
+    else if (type_ == _OrthancPluginDatabaseAnswerType_Int64 &&
+             answerInt64_.size() == 1)
+    {
+      target = answerInt64_.front();
+      return true; 
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::AddAttachment(int64_t id,
+                                            const FileInfo& attachment)
+  {
+    OrthancPluginAttachment tmp;
+    tmp.uuid = attachment.GetUuid().c_str();
+    tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
+    tmp.uncompressedSize = attachment.GetUncompressedSize();
+    tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
+    tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
+    tmp.compressedSize = attachment.GetCompressedSize();
+    tmp.compressedHash = attachment.GetCompressedMD5().c_str();
+
+    if (backend_.addAttachment(payload_, id, &tmp) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::AttachChild(int64_t parent,
+                                          int64_t child)
+  {
+    if (backend_.attachChild(payload_, parent, child) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::ClearChanges()
+  {
+    if (backend_.clearChanges(payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::ClearExportedResources()
+  {
+    if (backend_.clearExportedResources(payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  int64_t OrthancPluginDatabase::CreateResource(const std::string& publicId,
+                                                ResourceType type)
+  {
+    int64_t id;
+
+    if (backend_.createResource(&id, payload_, publicId.c_str(), Convert(type)) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return id;
+  }
+
+
+  void OrthancPluginDatabase::DeleteAttachment(int64_t id,
+                                               FileContentType attachment)
+  {
+    if (backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::DeleteMetadata(int64_t id,
+                                             MetadataType type)
+  {
+    if (backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::DeleteResource(int64_t id)
+  {
+    if (backend_.deleteResource(payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                             int64_t id)
+  {
+    std::list<MetadataType> metadata;
+    ListAvailableMetadata(metadata, id);
+
+    target.clear();
+
+    for (std::list<MetadataType>::const_iterator
+           it = metadata.begin(); it != metadata.end(); it++)
+    {
+      std::string value;
+      if (!LookupMetadata(value, id, *it))
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      target[*it] = value;
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
+                                              ResourceType resourceType)
+  {
+    ResetAnswers();
+
+    if (backend_.getAllPublicIds(GetContext(), payload_, Convert(resourceType)) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    ForwardAnswers(target);
+  }
+
+
+
+  void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                                         bool& done /*out*/,
+                                         int64_t since,
+                                         uint32_t maxResults)
+  {
+    ResetAnswers();
+    answerChanges_ = &target;
+    answerDone_ = &done;
+    done = false;
+
+    if (backend_.getChanges(GetContext(), payload_, since, maxResults) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetChildrenInternalId(std::list<int64_t>& target,
+                                                    int64_t id)
+  {
+    ResetAnswers();
+
+    if (backend_.getChildrenInternalId(GetContext(), payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    ForwardAnswers(target);
+  }
+
+
+  void OrthancPluginDatabase::GetChildrenPublicId(std::list<std::string>& target,
+                                                  int64_t id)
+  {
+    ResetAnswers();
+
+    if (backend_.getChildrenPublicId(GetContext(), payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    ForwardAnswers(target);
+  }
+
+
+  void OrthancPluginDatabase::GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                                   bool& done /*out*/,
+                                                   int64_t since,
+                                                   uint32_t maxResults)
+  {
+    ResetAnswers();
+    answerExportedResources_ = &target;
+    answerDone_ = &done;
+    done = false;
+
+    if (backend_.getExportedResources(GetContext(), payload_, since, maxResults) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
+  {
+    bool ignored = false;
+
+    ResetAnswers();
+    answerChanges_ = &target;
+    answerDone_ = &ignored;
+
+    if (backend_.getLastChange(GetContext(), payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
+  {
+    bool ignored = false;
+
+    ResetAnswers();
+    answerExportedResources_ = &target;
+    answerDone_ = &ignored;
+
+    if (backend_.getLastExportedResource(GetContext(), payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::GetMainDicomTags(DicomMap& map,
+                                               int64_t id)
+  {
+    ResetAnswers();
+    answerDicomMap_ = &map;
+
+    if (backend_.getMainDicomTags(GetContext(), payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  std::string OrthancPluginDatabase::GetPublicId(int64_t resourceId)
+  {
+    ResetAnswers();
+    std::string s;
+
+    if (backend_.getPublicId(GetContext(), payload_, resourceId) != 0 ||
+        !ForwardSingleAnswer(s))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return s;
+  }
+
+
+  uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
+  {
+    uint64_t count;
+
+    if (backend_.getResourceCount(&count, payload_, Convert(resourceType)) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return count;
+  }
+
+
+  ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
+  {
+    OrthancPluginResourceType type;
+
+    if (backend_.getResourceType(&type, payload_, resourceId) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return Convert(type);
+  }
+
+
+  uint64_t OrthancPluginDatabase::GetTotalCompressedSize()
+  {
+    uint64_t size;
+
+    if (backend_.getTotalCompressedSize(&size, payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return size;
+  }
+
+    
+  uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
+  {
+    uint64_t size;
+
+    if (backend_.getTotalUncompressedSize(&size, payload_) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return size;
+  }
+
+
+  bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
+  {
+    int32_t existing;
+
+    if (backend_.isExistingResource(&existing, payload_, internalId) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return existing;
+  }
+
+
+  bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
+  {
+    int32_t isProtected;
+
+    if (backend_.isProtectedPatient(&isProtected, payload_, internalId) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return isProtected;
+  }
+
+
+  void OrthancPluginDatabase::ListAvailableMetadata(std::list<MetadataType>& target,
+                                                    int64_t id)
+  {
+    ResetAnswers();
+
+    if (backend_.listAvailableMetadata(GetContext(), payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
+        type_ != _OrthancPluginDatabaseAnswerType_Int32)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    target.clear();
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
+    {
+      for (std::list<int32_t>::const_iterator 
+             it = answerInt32_.begin(); it != answerInt32_.end(); it++)
+      {
+        target.push_back(static_cast<MetadataType>(*it));
+      }
+    }
+  }
+
+
+  void OrthancPluginDatabase::ListAvailableAttachments(std::list<FileContentType>& target,
+                                                       int64_t id)
+  {
+    ResetAnswers();
+
+    if (backend_.listAvailableAttachments(GetContext(), payload_, id) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
+        type_ != _OrthancPluginDatabaseAnswerType_Int32)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    target.clear();
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
+    {
+      for (std::list<int32_t>::const_iterator 
+             it = answerInt32_.begin(); it != answerInt32_.end(); it++)
+      {
+        target.push_back(static_cast<FileContentType>(*it));
+      }
+    }
+  }
+
+
+  void OrthancPluginDatabase::LogChange(int64_t internalId,
+                                        const ServerIndexChange& change)
+  {
+    OrthancPluginChange tmp;
+    tmp.seq = change.GetSeq();
+    tmp.changeType = static_cast<int32_t>(change.GetChangeType());
+    tmp.resourceType = Convert(change.GetResourceType());
+    tmp.publicId = change.GetPublicId().c_str();
+    tmp.date = change.GetDate().c_str();
+
+    if (backend_.logChange(payload_, &tmp) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::LogExportedResource(const ExportedResource& resource)
+  {
+    OrthancPluginExportedResource tmp;
+    tmp.seq = resource.GetSeq();
+    tmp.resourceType = Convert(resource.GetResourceType());
+    tmp.publicId = resource.GetPublicId().c_str();
+    tmp.modality = resource.GetModality().c_str();
+    tmp.date = resource.GetDate().c_str();
+    tmp.patientId = resource.GetPatientId().c_str();
+    tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str();
+    tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
+    tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
+
+    if (backend_.logExportedResource(payload_, &tmp) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+    
+  bool OrthancPluginDatabase::LookupAttachment(FileInfo& attachment,
+                                               int64_t id,
+                                               FileContentType contentType)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupAttachment(GetContext(), payload_, id, static_cast<int32_t>(contentType)))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_None)
+    {
+      return false;
+    }
+    else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
+             answerAttachments_.size() == 1)
+    {
+      attachment = answerAttachments_.front();
+      return true; 
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  bool OrthancPluginDatabase::LookupGlobalProperty(std::string& target,
+                                                   GlobalProperty property)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupGlobalProperty(GetContext(), payload_, 
+                                      static_cast<int32_t>(property)))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return ForwardSingleAnswer(target);
+  }
+
+
+  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& target,
+                                               const DicomTag& tag,
+                                               const std::string& value)
+  {
+    ResetAnswers();
+
+    OrthancPluginDicomTag tmp;
+    tmp.group = tag.GetGroup();
+    tmp.element = tag.GetElement();
+    tmp.value = value.c_str();
+
+    if (backend_.lookupIdentifier(GetContext(), payload_, &tmp) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    ForwardAnswers(target);
+  }
+
+
+  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& target,
+                                               const std::string& value)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupIdentifier2(GetContext(), payload_, value.c_str()) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    ForwardAnswers(target);
+  }
+
+
+  bool OrthancPluginDatabase::LookupMetadata(std::string& target,
+                                             int64_t id,
+                                             MetadataType type)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return ForwardSingleAnswer(target);
+  }
+
+
+  bool OrthancPluginDatabase::LookupParent(int64_t& parentId,
+                                           int64_t resourceId)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupParent(GetContext(), payload_, resourceId))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return ForwardSingleAnswer(parentId);
+  }
+
+
+  bool OrthancPluginDatabase::LookupResource(int64_t& id,
+                                             ResourceType& type,
+                                             const std::string& publicId)
+  {
+    ResetAnswers();
+
+    if (backend_.lookupResource(GetContext(), payload_, publicId.c_str()))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_None)
+    {
+      return false;
+    }
+    else if (type_ == _OrthancPluginDatabaseAnswerType_Resource &&
+             answerResources_.size() == 1)
+    {
+      id = answerResources_.front().first;
+      type = answerResources_.front().second;
+      return true; 
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
+  {
+    ResetAnswers();
+
+    if (backend_.selectPatientToRecycle(GetContext(), payload_))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return ForwardSingleAnswer(internalId);
+  }
+
+
+  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId,
+                                                     int64_t patientIdToAvoid)
+  {
+    ResetAnswers();
+
+    if (backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid))
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    return ForwardSingleAnswer(internalId);
+  }
+
+
+  void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property,
+                                                const std::string& value)
+  {
+    if (backend_.setGlobalProperty(payload_, static_cast<int32_t>(property), 
+                                   value.c_str()) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::SetMainDicomTag(int64_t id,
+                                              const DicomTag& tag,
+                                              const std::string& value)
+  {
+    int32_t status;
+    OrthancPluginDicomTag tmp;
+    tmp.group = tag.GetGroup();
+    tmp.element = tag.GetElement();
+    tmp.value = value.c_str();
+
+    if (tag.IsIdentifier())
+    {
+      status = backend_.setIdentifierTag(payload_, id, &tmp);
+    }
+    else
+    {
+      status = backend_.setMainDicomTag(payload_, id, &tmp);
+    }
+
+    if (status != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::SetMetadata(int64_t id,
+                                          MetadataType type,
+                                          const std::string& value)
+  {
+    if (backend_.setMetadata(payload_, id, static_cast<int32_t>(type), 
+                             value.c_str()) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, 
+                                                  bool isProtected)
+  {
+    if (backend_.setProtectedPatient(payload_, internalId, isProtected) != 0)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  class OrthancPluginDatabase::Transaction : public SQLite::ITransaction
+  {
+  private:
+    const OrthancPluginDatabaseBackend& backend_;
+    void* payload_;
+
+  public:
+    Transaction(const OrthancPluginDatabaseBackend& backend,
+                void* payload) :
+      backend_(backend),
+      payload_(payload)
+    {
+    }
+
+    virtual void Begin()
+    {
+      if (backend_.startTransaction(payload_) != 0)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+
+    virtual void Rollback()
+    {
+      if (backend_.rollbackTransaction(payload_) != 0)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+
+    virtual void Commit()
+    {
+      if (backend_.commitTransaction(payload_) != 0)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+  };
+
+
+  SQLite::ITransaction* OrthancPluginDatabase::StartTransaction()
+  {
+    return new Transaction(backend_, payload_);
+  }
+
+
+  static void ProcessEvent(IServerIndexListener& listener,
+                           const _OrthancPluginDatabaseAnswer& answer)
+  {
+    switch (answer.type)
+    {
+      case _OrthancPluginDatabaseAnswerType_DeletedAttachment:
+      {
+        const OrthancPluginAttachment& attachment = 
+          *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
+        listener.SignalFileDeleted(Convert(attachment));
+        break;
+      }
+        
+      case _OrthancPluginDatabaseAnswerType_RemainingAncestor:
+      {
+        ResourceType type = Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
+        listener.SignalRemainingAncestor(type, answer.valueString);
+        break;
+      }
+      
+      case _OrthancPluginDatabaseAnswerType_DeletedResource:
+      {
+        ResourceType type = Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
+        ServerIndexChange change(ChangeType_Deleted, type, answer.valueString);
+        listener.SignalChange(change);
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+
+
+  void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer)
+  {
+    if (answer.type == _OrthancPluginDatabaseAnswerType_None)
+    {
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    if (answer.type == _OrthancPluginDatabaseAnswerType_DeletedAttachment ||
+        answer.type == _OrthancPluginDatabaseAnswerType_DeletedResource ||
+        answer.type == _OrthancPluginDatabaseAnswerType_RemainingAncestor)
+    {
+      assert(listener_ != NULL);
+      ProcessEvent(*listener_, answer);
+      return;
+    }
+
+    if (type_ == _OrthancPluginDatabaseAnswerType_None)
+    {
+      type_ = answer.type;
+
+      switch (type_)
+      {
+        case _OrthancPluginDatabaseAnswerType_Int32:
+          answerInt32_.clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_Int64:
+          answerInt64_.clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_Resource:
+          answerResources_.clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_Attachment:
+          answerAttachments_.clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_String:
+          answerStrings_.clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_DicomTag:
+          assert(answerDicomMap_ != NULL);
+          answerDicomMap_->Clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_Change:
+          assert(answerChanges_ != NULL);
+          answerChanges_->clear();
+          break;
+
+        case _OrthancPluginDatabaseAnswerType_ExportedResource:
+          assert(answerExportedResources_ != NULL);
+          answerExportedResources_->clear();
+          break;
+
+        default:
+          LOG(ERROR) << "Unhandled type of answer for custom index plugin: " << answer.type;
+          throw OrthancException(ErrorCode_Plugin);
+      }
+    }
+    else if (type_ != answer.type)
+    {
+      LOG(ERROR) << "Error in the plugin protocol: Cannot change the answer type";
+      throw OrthancException(ErrorCode_Plugin);
+    }
+
+    switch (answer.type)
+    {
+      case _OrthancPluginDatabaseAnswerType_Int32:
+      {
+        answerInt32_.push_back(answer.valueInt32);
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_Int64:
+      {
+        answerInt64_.push_back(answer.valueInt64);
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_Resource:
+      {
+        OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(answer.valueInt32);
+        answerResources_.push_back(std::make_pair(answer.valueInt64, Convert(type)));
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_Attachment:
+      {
+        const OrthancPluginAttachment& attachment = 
+          *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
+
+        answerAttachments_.push_back(Convert(attachment));
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_DicomTag:
+      {
+        const OrthancPluginDicomTag& tag = *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric);
+        assert(answerDicomMap_ != NULL);
+        answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value));
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_String:
+      {
+        if (answer.valueString == NULL)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+
+        if (type_ == _OrthancPluginDatabaseAnswerType_None)
+        {
+          type_ = _OrthancPluginDatabaseAnswerType_String;
+          answerStrings_.clear();
+        }
+        else if (type_ != _OrthancPluginDatabaseAnswerType_String)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+
+        answerStrings_.push_back(std::string(answer.valueString));
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_Change:
+      {
+        assert(answerDone_ != NULL);
+        if (answer.valueUint32 == 1)
+        {
+          *answerDone_ = true;
+        }
+        else if (*answerDone_)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+        else
+        {
+          const OrthancPluginChange& change = *reinterpret_cast<const OrthancPluginChange*>(answer.valueGeneric);
+          assert(answerChanges_ != NULL);
+          answerChanges_->push_back
+            (ServerIndexChange(change.seq,
+                               static_cast<ChangeType>(change.changeType),
+                               Convert(change.resourceType),
+                               change.publicId,
+                               change.date));                                   
+        }
+
+        break;
+      }
+
+      case _OrthancPluginDatabaseAnswerType_ExportedResource:
+      {
+        assert(answerDone_ != NULL);
+        if (answer.valueUint32 == 1)
+        {
+          *answerDone_ = true;
+        }
+        else if (*answerDone_)
+        {
+          throw OrthancException(ErrorCode_Plugin);
+        }
+        else
+        {
+          const OrthancPluginExportedResource& exported = 
+            *reinterpret_cast<const OrthancPluginExportedResource*>(answer.valueGeneric);
+          assert(answerExportedResources_ != NULL);
+          answerExportedResources_->push_back
+            (ExportedResource(exported.seq,
+                              Convert(exported.resourceType),
+                              exported.publicId,
+                              exported.modality,
+                              exported.date,
+                              exported.patientId,
+                              exported.studyInstanceUid,
+                              exported.seriesInstanceUid,
+                              exported.sopInstanceUid));
+        }
+
+        break;
+      }
+
+      default:
+        LOG(ERROR) << "Unhandled type of answer for custom index plugin: " << answer.type;
+        throw OrthancException(ErrorCode_Plugin);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Wed Feb 11 10:51:45 2015 +0100
@@ -0,0 +1,226 @@
+/**
+ * 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 "../../OrthancServer/IDatabaseWrapper.h"
+#include "../Include/OrthancCDatabasePlugin.h"
+
+namespace Orthanc
+{
+  class OrthancPluginDatabase : public IDatabaseWrapper
+  {
+  private:
+    class Transaction;
+
+    typedef std::pair<int64_t, ResourceType>  AnswerResource;
+
+    _OrthancPluginDatabaseAnswerType type_;
+    OrthancPluginDatabaseBackend backend_;
+    void* payload_;
+    IServerIndexListener* listener_;
+
+    std::list<std::string>         answerStrings_;
+    std::list<int32_t>             answerInt32_;
+    std::list<int64_t>             answerInt64_;
+    std::list<AnswerResource>      answerResources_;
+    std::list<FileInfo>            answerAttachments_;
+
+    DicomMap*                      answerDicomMap_;
+    std::list<ServerIndexChange>*  answerChanges_;
+    std::list<ExportedResource>*   answerExportedResources_;
+    bool*                          answerDone_;
+
+    OrthancPluginDatabaseContext* GetContext()
+    {
+      return reinterpret_cast<OrthancPluginDatabaseContext*>(this);
+    }
+
+    void ResetAnswers();
+
+    void ForwardAnswers(std::list<int64_t>& target);
+
+    void ForwardAnswers(std::list<std::string>& target);
+
+    bool ForwardSingleAnswer(std::string& target);
+
+    bool ForwardSingleAnswer(int64_t& target);
+
+  public:
+    OrthancPluginDatabase(const OrthancPluginDatabaseBackend& backend,
+                          void *payload) : 
+    type_(_OrthancPluginDatabaseAnswerType_None),
+    backend_(backend),
+    payload_(payload),
+    listener_(NULL)
+    {
+    }
+
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment);
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child);
+
+    virtual void ClearChanges();
+
+    virtual void ClearExportedResources();
+
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type);
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment);
+
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type);
+
+    virtual void DeleteResource(int64_t id);
+
+    virtual void FlushToDisk()
+    {
+    }
+
+    virtual bool HasFlushToDisk() const
+    {
+      return false;
+    }
+
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id);
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType);
+
+
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults);
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id);
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id);
+
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults);
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/);
+
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/);
+
+    virtual void GetMainDicomTags(DicomMap& map,
+                                  int64_t id);
+
+    virtual std::string GetPublicId(int64_t resourceId);
+
+    virtual uint64_t GetResourceCount(ResourceType resourceType);
+
+    virtual ResourceType GetResourceType(int64_t resourceId);
+
+    virtual uint64_t GetTotalCompressedSize();
+    
+    virtual uint64_t GetTotalUncompressedSize();
+
+    virtual bool IsExistingResource(int64_t internalId);
+
+    virtual bool IsProtectedPatient(int64_t internalId);
+
+    virtual void ListAvailableMetadata(std::list<MetadataType>& target,
+                                       int64_t id);
+
+    virtual void ListAvailableAttachments(std::list<FileContentType>& target,
+                                          int64_t id);
+
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change);
+
+    virtual void LogExportedResource(const ExportedResource& resource);
+    
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType);
+
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property);
+
+    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);
+
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type);
+
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId);
+
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId);
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId);
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid);
+
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value);
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value);
+
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value);
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected);
+
+    virtual SQLite::ITransaction* StartTransaction();
+
+    virtual void SetListener(IServerIndexListener& listener)
+    {
+      listener_ = &listener;
+    }
+
+    void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
+  };
+}
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Feb 11 10:26:17 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Feb 11 10:51:45 2015 +0100
@@ -180,7 +180,7 @@
     typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
     typedef std::map<Property, std::string>  Properties;
 
-    ServerContext& context_;
+    ServerContext* context_;
     RestCallbacks restCallbacks_;
     OrthancRestApi* restApi_;
     OnStoredCallbacks  onStoredCallbacks_;
@@ -194,9 +194,10 @@
     Properties properties_;
     int argc_;
     char** argv_;
+    std::auto_ptr<OrthancPluginDatabase>  database_;
 
-    PImpl(ServerContext& context) : 
-      context_(context), 
+    PImpl() : 
+      context_(NULL), 
       restApi_(NULL),
       hasStorageArea_(false),
       done_(false),
@@ -246,13 +247,20 @@
   }
 
 
-  OrthancPlugins::OrthancPlugins(ServerContext& context)
+  OrthancPlugins::OrthancPlugins()
   {
-    pimpl_.reset(new PImpl(context));
+    pimpl_.reset(new PImpl());
     pimpl_->changeThread_ = boost::thread(PImpl::ChangeThread, pimpl_.get());
   }
 
   
+  void OrthancPlugins::SetServerContext(ServerContext& context)
+  {
+    pimpl_->context_ = &context;
+  }
+
+
+  
   OrthancPlugins::~OrthancPlugins()
   {
     Stop();
@@ -642,11 +650,13 @@
 
   void OrthancPlugins::GetDicomForInstance(const void* parameters)
   {
+    assert(pimpl_->context_ != NULL);
+
     const _OrthancPluginGetDicomForInstance& p = 
       *reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
 
     std::string dicom;
-    pimpl_->context_.ReadFile(dicom, p.instanceId, FileContentType_Dicom);
+    pimpl_->context_->ReadFile(dicom, p.instanceId, FileContentType_Dicom);
     CopyToMemoryBuffer(*p.target, dicom);
   }
 
@@ -829,8 +839,10 @@
         throw OrthancException(ErrorCode_InternalError);
     }
 
+    assert(pimpl_->context_ != NULL);
+
     std::list<std::string> result;
-    pimpl_->context_.GetIndex().LookupIdentifier(result, tag, p.argument, level);
+    pimpl_->context_->GetIndex().LookupIdentifier(result, tag, p.argument, level);
 
     if (result.size() == 1)
     {
@@ -1081,6 +1093,7 @@
 
       case _OrthancPluginService_RegisterStorageArea:
       {
+        LOG(INFO) << "Plugin has registered a custom storage area";
         const _OrthancPluginRegisterStorageArea& p = 
           *reinterpret_cast<const _OrthancPluginRegisterStorageArea*>(parameters);
         
@@ -1107,16 +1120,19 @@
         }
         else
         {
-          pimpl_->context_.GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+          assert(pimpl_->context_ != NULL);
+          pimpl_->context_->GetIndex().SetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
           return true;
         }
       }
 
       case _OrthancPluginService_GetGlobalProperty:
       {
+        assert(pimpl_->context_ != NULL);
+
         const _OrthancPluginGlobalProperty& p = 
           *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters);
-        std::string result = pimpl_->context_.GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
+        std::string result = pimpl_->context_->GetIndex().GetGlobalProperty(static_cast<GlobalProperty>(p.property), p.value);
         *(p.result) = CopyString(result);
         return true;
       }
@@ -1146,6 +1162,34 @@
         }
       }
 
+      case _OrthancPluginService_RegisterDatabaseBackend:
+      {
+        LOG(INFO) << "Plugin has registered a custom database back-end";
+        const _OrthancPluginRegisterDatabaseBackend& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters);
+
+        pimpl_->database_.reset(new OrthancPluginDatabase(*p.backend, p.payload));
+        *(p.result) = reinterpret_cast<OrthancPluginDatabaseContext*>(pimpl_->database_.get());
+
+        return true;
+      }
+
+      case _OrthancPluginService_DatabaseAnswer:
+      {
+        const _OrthancPluginDatabaseAnswer& p =
+          *reinterpret_cast<const _OrthancPluginDatabaseAnswer*>(parameters);
+        if (pimpl_->database_.get() != NULL)
+        {
+          pimpl_->database_->AnswerReceived(p);
+          return true;
+        }
+        else
+        {
+          LOG(ERROR) << "Cannot invoke this service without a custom database back-end";
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+
       default:
         return false;
     }
@@ -1162,6 +1206,12 @@
   {
     return pimpl_->hasStorageArea_;
   }
+  
+  bool OrthancPlugins::HasDatabase() const
+  {
+    return pimpl_->database_.get() != NULL;
+  }
+
 
 
   namespace
@@ -1263,6 +1313,19 @@
   }
 
 
+  IDatabaseWrapper& OrthancPlugins::GetDatabase()
+  {
+    if (!HasDatabase())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return *pimpl_->database_;
+  }
+
+
+
+
 
   const char* OrthancPlugins::GetProperty(const char* plugin,
                                           _OrthancPluginProperty property) const
--- a/Plugins/Engine/OrthancPlugins.h	Wed Feb 11 10:26:17 2015 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Wed Feb 11 10:51:45 2015 +0100
@@ -36,7 +36,7 @@
 #include "../../Core/HttpServer/HttpHandler.h"
 #include "../../OrthancServer/ServerContext.h"
 #include "../../OrthancServer/OrthancRestApi/OrthancRestApi.h"
-#include "../Include/OrthancCPlugin.h"
+#include "OrthancPluginDatabase.h"
 
 #include <list>
 #include <boost/shared_ptr.hpp>
@@ -87,10 +87,12 @@
     void SetHttpHeader(const void* parameters);
 
   public:
-    OrthancPlugins(ServerContext& context);
+    OrthancPlugins();
 
     virtual ~OrthancPlugins();
 
+    void SetServerContext(ServerContext& context);
+
     virtual bool Handle(HttpOutput& output,
                         HttpMethod method,
                         const UriComponents& uri,
@@ -110,7 +112,11 @@
 
     bool HasStorageArea() const;
 
-    IStorageArea* GetStorageArea();
+    IStorageArea* GetStorageArea();  // To be freed after use
+
+    bool HasDatabase() const;
+
+    IDatabaseWrapper& GetDatabase();
 
     void Stop();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Include/OrthancCDatabasePlugin.h	Wed Feb 11 10:51:45 2015 +0100
@@ -0,0 +1,664 @@
+/**
+ * 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 "OrthancCPlugin.h"
+
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  typedef struct _OrthancPluginDatabaseContext_t OrthancPluginDatabaseContext;
+
+
+  typedef enum
+  {
+    _OrthancPluginDatabaseAnswerType_None = 0,
+
+    /* Events */
+    _OrthancPluginDatabaseAnswerType_DeletedAttachment = 1,
+    _OrthancPluginDatabaseAnswerType_DeletedResource = 2,
+    _OrthancPluginDatabaseAnswerType_RemainingAncestor = 3,
+
+    /* Return value */
+    _OrthancPluginDatabaseAnswerType_Attachment = 10,
+    _OrthancPluginDatabaseAnswerType_Change = 11,
+    _OrthancPluginDatabaseAnswerType_DicomTag = 12,
+    _OrthancPluginDatabaseAnswerType_ExportedResource = 13,
+    _OrthancPluginDatabaseAnswerType_Int32 = 14,
+    _OrthancPluginDatabaseAnswerType_Int64 = 15,
+    _OrthancPluginDatabaseAnswerType_Resource = 16,
+    _OrthancPluginDatabaseAnswerType_String = 17
+  } _OrthancPluginDatabaseAnswerType;
+
+
+  typedef struct
+  {
+    const char* uuid;
+    int32_t     contentType;
+    uint64_t    uncompressedSize;
+    const char* uncompressedHash;
+    int32_t     compressionType;
+    uint64_t    compressedSize;
+    const char* compressedHash;
+  } OrthancPluginAttachment;
+
+  typedef struct
+  {
+    uint16_t     group;
+    uint16_t     element;
+    const char*  value;
+  } OrthancPluginDicomTag;
+
+  typedef struct
+  {
+    int64_t                    seq;
+    int32_t                    changeType;
+    OrthancPluginResourceType  resourceType;
+    const char*                publicId;
+    const char*                date;
+  } OrthancPluginChange;
+
+  typedef struct
+  {
+    int64_t                    seq;
+    OrthancPluginResourceType  resourceType;
+    const char*                publicId;
+    const char*                modality;
+    const char*                date;
+    const char*                patientId;
+    const char*                studyInstanceUid;
+    const char*                seriesInstanceUid;
+    const char*                sopInstanceUid;
+  } OrthancPluginExportedResource;
+
+
+  typedef struct
+  {
+    OrthancPluginDatabaseContext* database;
+    _OrthancPluginDatabaseAnswerType  type;
+    int32_t      valueInt32;
+    uint32_t     valueUint32;
+    int64_t      valueInt64;
+    const char  *valueString;
+    const void  *valueGeneric;
+  } _OrthancPluginDatabaseAnswer;
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerString(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_String;
+    params.valueString = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChange(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginChange*     change)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Change;
+    params.valueUint32 = 0;
+    params.valueGeneric = change;
+
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerChangesDone(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Change;
+    params.valueUint32 = 1;
+    params.valueGeneric = NULL;
+
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt32(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int32_t                        value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Int32;
+    params.valueInt32 = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerInt64(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int64_t                        value)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Int64;
+    params.valueInt64 = value;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResource(
+    OrthancPluginContext*                 context,
+    OrthancPluginDatabaseContext*         database,
+    const OrthancPluginExportedResource*  exported)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_ExportedResource;
+    params.valueUint32 = 0;
+    params.valueGeneric = exported;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerExportedResourcesDone(
+    OrthancPluginContext*                 context,
+    OrthancPluginDatabaseContext*         database)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_ExportedResource;
+    params.valueUint32 = 1;
+    params.valueGeneric = NULL;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerDicomTag(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginDicomTag*   tag)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DicomTag;
+    params.valueGeneric = tag;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerAttachment(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginAttachment* attachment)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Attachment;
+    params.valueGeneric = attachment;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseAnswerResource(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    int64_t                        id,
+    OrthancPluginResourceType      resourceType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_Resource;
+    params.valueInt64 = id;
+    params.valueInt32 = (int32_t) resourceType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedAttachment(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const OrthancPluginAttachment* attachment)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DeletedAttachment;
+    params.valueGeneric = attachment;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalDeletedResource(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    publicId,
+    OrthancPluginResourceType      resourceType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_DeletedResource;
+    params.valueString = publicId;
+    params.valueInt32 = (int32_t) resourceType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginDatabaseSignalRemainingAncestor(
+    OrthancPluginContext*          context,
+    OrthancPluginDatabaseContext*  database,
+    const char*                    ancestorId,
+    OrthancPluginResourceType      ancestorType)
+  {
+    _OrthancPluginDatabaseAnswer params;
+    memset(&params, 0, sizeof(params));
+    params.database = database;
+    params.type = _OrthancPluginDatabaseAnswerType_RemainingAncestor;
+    params.valueString = ancestorId;
+    params.valueInt32 = (int32_t) ancestorType;
+    context->InvokeService(context, _OrthancPluginService_DatabaseAnswer, &params);
+  }
+
+
+
+
+
+  typedef struct
+  {
+    int32_t  (*addAttachment) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginAttachment* attachment);
+                             
+    int32_t  (*attachChild) (
+      /* inputs */
+      void* payload,
+      int64_t parent,
+      int64_t child);
+                             
+    int32_t  (*clearChanges) (
+      /* inputs */
+      void* payload);
+                             
+    int32_t  (*clearExportedResources) (
+      /* inputs */
+      void* payload);
+
+    int32_t  (*createResource) (
+      /* outputs */
+      int64_t* id, 
+      /* inputs */
+      void* payload,
+      const char* publicId,
+      OrthancPluginResourceType resourceType);           
+                   
+    int32_t  (*deleteAttachment) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t contentType);
+   
+    int32_t  (*deleteMetadata) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadataType);
+   
+    int32_t  (*deleteResource) (
+      /* inputs */
+      void* payload,
+      int64_t id);    
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    int32_t  (*getAllPublicIds) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType resourceType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerChange() and
+     * OrthancPluginDatabaseAnswerChangesDone() */
+    int32_t  (*getChanges) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t since,
+      uint32_t maxResult);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*getChildrenInternalId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    int32_t  (*getChildrenPublicId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerExportedResource() and
+     * OrthancPluginDatabaseAnswerExportedResourcesDone() */
+    int32_t  (*getExportedResources) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t  since,
+      uint32_t  maxResult);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerChange() */
+    int32_t  (*getLastChange) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+
+    /* Output: Use OrthancPluginDatabaseAnswerExportedResource() */
+    int32_t  (*getLastExportedResource) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerDicomTag() */
+    int32_t  (*getMainDicomTags) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    int32_t  (*getPublicId) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    int32_t  (*getResourceCount) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload,
+      OrthancPluginResourceType  resourceType);
+                   
+    int32_t  (*getResourceType) (
+      /* outputs */
+      OrthancPluginResourceType* resourceType,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    int32_t  (*getTotalCompressedSize) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload);
+                   
+    int32_t  (*getTotalUncompressedSize) (
+      /* outputs */
+      uint64_t* target,
+      /* inputs */
+      void* payload);
+                   
+    int32_t  (*isExistingResource) (
+      /* outputs */
+      int32_t* existing,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    int32_t  (*isProtectedPatient) (
+      /* outputs */
+      int32_t* isProtected,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt32() */
+    int32_t  (*listAvailableMetadata) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerInt32() */
+    int32_t  (*listAvailableAttachments) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    int32_t  (*logChange) (
+      /* inputs */
+      void* payload,
+      const OrthancPluginChange* change);
+                   
+    int32_t  (*logExportedResource) (
+      /* inputs */
+      void* payload,
+      const OrthancPluginExportedResource* exported);
+                   
+    /* Output: Use OrthancPluginDatabaseAnswerAttachment() */
+    int32_t  (*lookupAttachment) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t contentType);
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    int32_t  (*lookupGlobalProperty) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int32_t property);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*lookupIdentifier) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const OrthancPluginDicomTag* tag);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*lookupIdentifier2) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const char* value);
+
+    /* Output: Use OrthancPluginDatabaseAnswerString() */
+    int32_t  (*lookupMetadata) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadata);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*lookupParent) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t id);
+
+    /* Output: Use OrthancPluginDatabaseAnswerResource() */
+    int32_t  (*lookupResource) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      const char* publicId);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*selectPatientToRecycle) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload);
+
+    /* Output: Use OrthancPluginDatabaseAnswerInt64() */
+    int32_t  (*selectPatientToRecycle2) (
+      /* outputs */
+      OrthancPluginDatabaseContext* context,
+      /* inputs */
+      void* payload,
+      int64_t patientIdToAvoid);
+
+    int32_t  (*setGlobalProperty) (
+      /* inputs */
+      void* payload,
+      int32_t property,
+      const char* value);
+
+    int32_t  (*setMainDicomTag) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginDicomTag* tag);
+
+    int32_t  (*setIdentifierTag) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      const OrthancPluginDicomTag* tag);
+
+    int32_t  (*setMetadata) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t metadata,
+      const char* value);
+
+    int32_t  (*setProtectedPatient) (
+      /* inputs */
+      void* payload,
+      int64_t id,
+      int32_t isProtected);
+
+    int32_t (*startTransaction) (
+      /* inputs */
+      void* payload);
+
+    int32_t (*rollbackTransaction) (
+      /* inputs */
+      void* payload);
+
+    int32_t (*commitTransaction) (
+      /* inputs */
+      void* payload);
+
+    int32_t (*open) (
+      /* inputs */
+      void* payload);
+
+    int32_t (*close) (
+      /* inputs */
+      void* payload);
+
+  } OrthancPluginDatabaseBackend;
+
+
+
+  typedef struct
+  {
+    OrthancPluginDatabaseContext**       result;
+    const OrthancPluginDatabaseBackend*  backend;
+    void*                                payload;
+  } _OrthancPluginRegisterDatabaseBackend;
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginDatabaseContext* OrthancPluginRegisterDatabaseBackend(
+    OrthancPluginContext*                context,
+    const OrthancPluginDatabaseBackend*  backend,
+    void*                                payload)
+  {
+    OrthancPluginDatabaseContext* result = NULL;
+
+    _OrthancPluginRegisterDatabaseBackend params;
+    memset(&params, 0, sizeof(params));
+    params.backend = backend;
+    params.result = &result;
+    params.payload = payload;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterDatabaseBackend, &params) ||
+        result == NULL)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- a/Plugins/Include/OrthancCPlugin.h	Wed Feb 11 10:26:17 2015 +0100
+++ b/Plugins/Include/OrthancCPlugin.h	Wed Feb 11 10:51:45 2015 +0100
@@ -16,6 +16,7 @@
  *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
  *    - Register all its callbacks for received instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
  *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using ::OrthancPluginRegisterDatabaseBackend().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    This function is invoked by Orthanc during its shutdown. The plugin
  *    must free all its memory.
@@ -293,7 +294,12 @@
     _OrthancPluginService_GetInstanceJson = 4003,
     _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
     _OrthancPluginService_HasInstanceMetadata = 4005,
-    _OrthancPluginService_GetInstanceMetadata = 4006
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,
+    _OrthancPluginService_DatabaseAnswer = 5001
+
   } _OrthancPluginService;
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Include/OrthancCppDatabasePlugin.h	Wed Feb 11 10:51:45 2015 +0100
@@ -0,0 +1,1532 @@
+/**
+ * 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 "OrthancCDatabasePlugin.h"
+
+#include <stdexcept>
+#include <list>
+#include <string>
+
+namespace OrthancPlugins
+{
+  // This class mimics "boost::noncopyable"
+  class NonCopyable
+  {
+  private:
+    NonCopyable(const NonCopyable&);
+
+    NonCopyable& operator= (const NonCopyable&);
+
+  protected:
+    NonCopyable()
+    {
+    }
+
+    ~NonCopyable()
+    {
+    }
+  };
+
+
+
+  class DatabaseBackendOutput : public NonCopyable
+  {
+    friend class DatabaseBackendAdapter;
+
+  private:
+    enum AllowedAnswers
+    {
+      AllowedAnswers_All,
+      AllowedAnswers_None,
+      AllowedAnswers_Attachment,
+      AllowedAnswers_Change,
+      AllowedAnswers_DicomTag,
+      AllowedAnswers_ExportedResource
+    };
+
+    OrthancPluginContext*         context_;
+    OrthancPluginDatabaseContext* database_;
+    AllowedAnswers                allowedAnswers_;
+
+    void SetAllowedAnswers(AllowedAnswers allowed)
+    {
+      allowedAnswers_ = allowed;
+    }
+
+  public:
+    DatabaseBackendOutput(OrthancPluginContext*         context,
+                          OrthancPluginDatabaseContext* database) :
+      context_(context),
+      database_(database),
+      allowedAnswers_(AllowedAnswers_All /* for unit tests */)
+    {
+    }
+
+    void LogError(const std::string& message)
+    {
+      OrthancPluginLogError(context_, message.c_str());
+    }
+
+    void LogWarning(const std::string& message)
+    {
+      OrthancPluginLogWarning(context_, message.c_str());
+    }
+
+    void LogInfo(const std::string& message)
+    {
+      OrthancPluginLogInfo(context_, message.c_str());
+    }
+
+    void SignalDeletedAttachment(const std::string& uuid,
+                                 int32_t            contentType,
+                                 uint64_t           uncompressedSize,
+                                 const std::string& uncompressedHash,
+                                 int32_t            compressionType,
+                                 uint64_t           compressedSize,
+                                 const std::string& compressedHash)
+    {
+      OrthancPluginAttachment attachment;
+      attachment.uuid = uuid.c_str();
+      attachment.contentType = contentType;
+      attachment.uncompressedSize = uncompressedSize;
+      attachment.uncompressedHash = uncompressedHash.c_str();
+      attachment.compressionType = compressionType;
+      attachment.compressedSize = compressedSize;
+      attachment.compressedHash = compressedHash.c_str();
+
+      OrthancPluginDatabaseSignalDeletedAttachment(context_, database_, &attachment);
+    }
+
+    void SignalDeletedResource(const std::string& publicId,
+                               OrthancPluginResourceType resourceType)
+    {
+      OrthancPluginDatabaseSignalDeletedResource(context_, database_, publicId.c_str(), resourceType);
+    }
+
+    void SignalRemainingAncestor(const std::string& ancestorId,
+                                 OrthancPluginResourceType ancestorType)
+    {
+      OrthancPluginDatabaseSignalRemainingAncestor(context_, database_, ancestorId.c_str(), ancestorType);
+    }
+
+    void AnswerAttachment(const std::string& uuid,
+                          int32_t            contentType,
+                          uint64_t           uncompressedSize,
+                          const std::string& uncompressedHash,
+                          int32_t            compressionType,
+                          uint64_t           compressedSize,
+                          const std::string& compressedHash)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_Attachment)
+      {
+        throw std::runtime_error("Cannot answer with an attachment in the current state");
+      }
+
+      OrthancPluginAttachment attachment;
+      attachment.uuid = uuid.c_str();
+      attachment.contentType = contentType;
+      attachment.uncompressedSize = uncompressedSize;
+      attachment.uncompressedHash = uncompressedHash.c_str();
+      attachment.compressionType = compressionType;
+      attachment.compressedSize = compressedSize;
+      attachment.compressedHash = compressedHash.c_str();
+
+      OrthancPluginDatabaseAnswerAttachment(context_, database_, &attachment);
+    }
+
+    void AnswerChange(int64_t                    seq,
+                      int32_t                    changeType,
+                      OrthancPluginResourceType  resourceType,
+                      const std::string&         publicId,
+                      const std::string&         date)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_Change)
+      {
+        throw std::runtime_error("Cannot answer with a change in the current state");
+      }
+
+      OrthancPluginChange change;
+      change.seq = seq;
+      change.changeType = changeType;
+      change.resourceType = resourceType;
+      change.publicId = publicId.c_str();
+      change.date = date.c_str();
+
+      OrthancPluginDatabaseAnswerChange(context_, database_, &change);
+    }
+
+    void AnswerDicomTag(uint16_t group,
+                        uint16_t element,
+                        const std::string& value)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_DicomTag)
+      {
+        throw std::runtime_error("Cannot answer with a DICOM tag in the current state");
+      }
+
+      OrthancPluginDicomTag tag;
+      tag.group = group;
+      tag.element = element;
+      tag.value = value.c_str();
+
+      OrthancPluginDatabaseAnswerDicomTag(context_, database_, &tag);
+    }
+
+    void AnswerExportedResource(int64_t                    seq,
+                                OrthancPluginResourceType  resourceType,
+                                const std::string&         publicId,
+                                const std::string&         modality,
+                                const std::string&         date,
+                                const std::string&         patientId,
+                                const std::string&         studyInstanceUid,
+                                const std::string&         seriesInstanceUid,
+                                const std::string&         sopInstanceUid)
+    {
+      if (allowedAnswers_ != AllowedAnswers_All &&
+          allowedAnswers_ != AllowedAnswers_ExportedResource)
+      {
+        throw std::runtime_error("Cannot answer with an exported resource in the current state");
+      }
+
+      OrthancPluginExportedResource exported;
+      exported.seq = seq;
+      exported.resourceType = resourceType;
+      exported.publicId = publicId.c_str();
+      exported.modality = modality.c_str();
+      exported.date = date.c_str();
+      exported.patientId = patientId.c_str();
+      exported.studyInstanceUid = studyInstanceUid.c_str();
+      exported.seriesInstanceUid = seriesInstanceUid.c_str();
+      exported.sopInstanceUid = sopInstanceUid.c_str();
+
+      OrthancPluginDatabaseAnswerExportedResource(context_, database_, &exported);
+    }
+  };
+
+
+
+  class IDatabaseBackend : public NonCopyable
+  {
+    friend class DatabaseBackendAdapter;
+
+  private:
+    DatabaseBackendOutput*  output_;
+
+    void Finalize()
+    {
+      if (output_ != NULL)
+      {
+        delete output_;
+        output_ = NULL;
+      }
+    }
+
+  protected:
+    DatabaseBackendOutput& GetOutput()
+    {
+      return *output_;
+    }
+
+  public:
+    IDatabaseBackend() : output_(NULL)
+    {
+    }
+
+    virtual ~IDatabaseBackend()
+    {
+      Finalize();
+    }
+
+    // This takes the ownership
+    void RegisterOutput(DatabaseBackendOutput* output)
+    {
+      Finalize();
+      output_ = output;
+    }
+
+    virtual void Open() = 0;
+
+    virtual void Close() = 0;
+
+    virtual void AddAttachment(int64_t id,
+                               const OrthancPluginAttachment& attachment) = 0;
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) = 0;
+
+    virtual void ClearChanges() = 0;
+
+    virtual void ClearExportedResources() = 0;
+
+    virtual int64_t CreateResource(const char* publicId,
+                                   OrthancPluginResourceType type) = 0;
+
+    virtual void DeleteAttachment(int64_t id,
+                                  int32_t attachment) = 0;
+
+    virtual void DeleteMetadata(int64_t id,
+                                int32_t metadataType) = 0;
+
+    virtual void DeleteResource(int64_t id) = 0;
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 OrthancPluginResourceType resourceType) = 0;
+
+    /* Use GetOutput().AnswerChange() */
+    virtual void GetChanges(bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) = 0;
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
+                                       int64_t id) = 0;
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target /*out*/,
+                                     int64_t id) = 0;
+
+    /* Use GetOutput().AnswerExportedResource() */
+    virtual void GetExportedResources(bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) = 0;
+
+    /* Use GetOutput().AnswerChange() */
+    virtual void GetLastChange() = 0;
+
+    /* Use GetOutput().AnswerExportedResource() */
+    virtual void GetLastExportedResource() = 0;
+
+    /* Use GetOutput().AnswerDicomTag() */
+    virtual void GetMainDicomTags(int64_t id) = 0;
+
+    virtual std::string GetPublicId(int64_t resourceId) = 0;
+
+    virtual uint64_t GetResourceCount(OrthancPluginResourceType resourceType) = 0;
+
+    virtual OrthancPluginResourceType GetResourceType(int64_t resourceId) = 0;
+
+    virtual uint64_t GetTotalCompressedSize() = 0;
+    
+    virtual uint64_t GetTotalUncompressedSize() = 0;
+
+    virtual bool IsExistingResource(int64_t internalId) = 0;
+
+    virtual bool IsProtectedPatient(int64_t internalId) = 0;
+
+    virtual void ListAvailableMetadata(std::list<int32_t>& target /*out*/,
+                                       int64_t id) = 0;
+
+    virtual void ListAvailableAttachments(std::list<int32_t>& target /*out*/,
+                                          int64_t id) = 0;
+
+    virtual void LogChange(const OrthancPluginChange& change) = 0;
+
+    virtual void LogExportedResource(const OrthancPluginExportedResource& resource) = 0;
+    
+    /* Use GetOutput().AnswerAttachment() */
+    virtual bool LookupAttachment(int64_t id,
+                                  int32_t contentType) = 0;
+
+    virtual bool LookupGlobalProperty(std::string& target /*out*/,
+                                      int32_t property) = 0;
+
+    /**
+     * "Identifiers" are necessarily one of the following tags:
+     * PatientID (0x0010, 0x0020), StudyInstanceUID (0x0020, 0x000d),
+     * SeriesInstanceUID (0x0020, 0x000e), SOPInstanceUID (0x0008,
+     * 0x0018) or AccessionNumber (0x0008, 0x0050).
+     **/
+    virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  const char* value) = 0;
+
+    virtual void LookupIdentifier(std::list<int64_t>& target /*out*/,
+                                  const char* value) = 0;
+
+    virtual bool LookupMetadata(std::string& target /*out*/,
+                                int64_t id,
+                                int32_t metadataType) = 0;
+
+    virtual bool LookupParent(int64_t& parentId /*out*/,
+                              int64_t resourceId) = 0;
+
+    virtual bool LookupResource(int64_t& id /*out*/,
+                                OrthancPluginResourceType& type /*out*/,
+                                const char* publicId) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/) = 0;
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId /*out*/,
+                                        int64_t patientIdToAvoid) = 0;
+
+    virtual void SetGlobalProperty(int32_t property,
+                                   const char* value) = 0;
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 uint16_t group,
+                                 uint16_t element,
+                                 const char* value) = 0;
+
+    virtual void SetIdentifierTag(int64_t id,
+                                  uint16_t group,
+                                  uint16_t element,
+                                  const char* value) = 0;
+
+    virtual void SetMetadata(int64_t id,
+                             int32_t metadataType,
+                             const char* value) = 0;
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) = 0;
+
+    virtual void StartTransaction() = 0;
+
+    virtual void RollbackTransaction() = 0;
+
+    virtual void CommitTransaction() = 0;
+  };
+
+
+
+  class DatabaseBackendAdapter
+  {
+  private:
+    // This class cannot be instantiated
+    DatabaseBackendAdapter()
+    {
+    }
+
+    static void LogError(IDatabaseBackend* backend,
+                         const std::runtime_error& e)
+    {
+      backend->GetOutput().LogError("Exception in database back-end: " + std::string(e.what()));
+    }
+
+
+    static int32_t  AddAttachment(void* payload,
+                                  int64_t id,
+                                  const OrthancPluginAttachment* attachment)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->AddAttachment(id, *attachment);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+                             
+    static int32_t  AttachChild(void* payload,
+                                int64_t parent,
+                                int64_t child)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->AttachChild(parent, child);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+                   
+    static int32_t  ClearChanges(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->ClearChanges();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+                             
+
+    static int32_t  ClearExportedResources(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->ClearExportedResources();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  CreateResource(int64_t* id, 
+                                   void* payload,
+                                   const char* publicId,
+                                   OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *id = backend->CreateResource(publicId, resourceType);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  DeleteAttachment(void* payload,
+                                     int64_t id,
+                                     int32_t contentType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteAttachment(id, contentType);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+   
+
+    static int32_t  DeleteMetadata(void* payload,
+                                   int64_t id,
+                                   int32_t metadataType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteMetadata(id, metadataType);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+   
+
+    static int32_t  DeleteResource(void* payload,
+                                   int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->DeleteResource(id);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetAllPublicIds(OrthancPluginDatabaseContext* context,
+                                    void* payload,
+                                    OrthancPluginResourceType resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<std::string> ids;
+        backend->GetAllPublicIds(ids, resourceType);
+
+        for (std::list<std::string>::const_iterator
+               it = ids.begin(); it != ids.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            it->c_str());
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetChanges(OrthancPluginDatabaseContext* context,
+                               void* payload,
+                               int64_t since,
+                               uint32_t maxResult)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change);
+
+      try
+      {
+        bool done;
+        backend->GetChanges(done, since, maxResult);
+        
+        if (done)
+        {
+          OrthancPluginDatabaseAnswerChangesDone(backend->GetOutput().context_,
+                                                 backend->GetOutput().database_);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetChildrenInternalId(OrthancPluginDatabaseContext* context,
+                                          void* payload,
+                                          int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->GetChildrenInternalId(target, id);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  GetChildrenPublicId(OrthancPluginDatabaseContext* context,
+                                        void* payload,
+                                        int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<std::string> ids;
+        backend->GetChildrenPublicId(ids, id);
+
+        for (std::list<std::string>::const_iterator
+               it = ids.begin(); it != ids.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            it->c_str());
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetExportedResources(OrthancPluginDatabaseContext* context,
+                                         void* payload,
+                                         int64_t  since,
+                                         uint32_t  maxResult)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource);
+
+      try
+      {
+        bool done;
+        backend->GetExportedResources(done, since, maxResult);
+
+        if (done)
+        {
+          OrthancPluginDatabaseAnswerExportedResourcesDone(backend->GetOutput().context_,
+                                                           backend->GetOutput().database_);
+        }
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  GetLastChange(OrthancPluginDatabaseContext* context,
+                                  void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Change);
+
+      try
+      {
+        backend->GetLastChange();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetLastExportedResource(OrthancPluginDatabaseContext* context,
+                                            void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_ExportedResource);
+
+      try
+      {
+        backend->GetLastExportedResource();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+    
+               
+    static int32_t  GetMainDicomTags(OrthancPluginDatabaseContext* context,
+                                     void* payload,
+                                     int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_DicomTag);
+
+      try
+      {
+        backend->GetMainDicomTags(id);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  GetPublicId(OrthancPluginDatabaseContext* context,
+                                void* payload,
+                                int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s = backend->GetPublicId(id);
+        OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                          backend->GetOutput().database_,
+                                          s.c_str());
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetResourceCount(uint64_t* target,
+                                     void* payload,
+                                     OrthancPluginResourceType  resourceType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetResourceCount(resourceType);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+                   
+
+    static int32_t  GetResourceType(OrthancPluginResourceType* resourceType,
+                                    void* payload,
+                                    int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *resourceType = backend->GetResourceType(id);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  GetTotalCompressedSize(uint64_t* target,
+                                           void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetTotalCompressedSize();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  GetTotalUncompressedSize(uint64_t* target,
+                                             void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *target = backend->GetTotalUncompressedSize();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+                   
+
+    static int32_t  IsExistingResource(int32_t* existing,
+                                       void* payload,
+                                       int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *existing = backend->IsExistingResource(id);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  IsProtectedPatient(int32_t* isProtected,
+                                       void* payload,
+                                       int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        *isProtected = backend->IsProtectedPatient(id);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  ListAvailableMetadata(OrthancPluginDatabaseContext* context,
+                                          void* payload,
+                                          int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int32_t> target;
+        backend->ListAvailableMetadata(target, id);
+
+        for (std::list<int32_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_,
+                                           backend->GetOutput().database_,
+                                           *it);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  ListAvailableAttachments(OrthancPluginDatabaseContext* context,
+                                             void* payload,
+                                             int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int32_t> target;
+        backend->ListAvailableAttachments(target, id);
+
+        for (std::list<int32_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt32(backend->GetOutput().context_,
+                                           backend->GetOutput().database_,
+                                           *it);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LogChange(void* payload,
+                              const OrthancPluginChange* change)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->LogChange(*change);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  LogExportedResource(void* payload,
+                                        const OrthancPluginExportedResource* exported)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->LogExportedResource(*exported);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+          
+         
+    static int32_t  LookupAttachment(OrthancPluginDatabaseContext* context,
+                                     void* payload,
+                                     int64_t id,
+                                     int32_t contentType)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_Attachment);
+
+      try
+      {
+        backend->LookupAttachment(id, contentType);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupGlobalProperty(OrthancPluginDatabaseContext* context,
+                                         void* payload,
+                                         int32_t property)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s;
+        if (backend->LookupGlobalProperty(s, property))
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_,
+                                            s.c_str());
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupIdentifier(OrthancPluginDatabaseContext* context,
+                                     void* payload,
+                                     const OrthancPluginDicomTag* tag)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->LookupIdentifier(target, tag->group, tag->element, tag->value);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupIdentifier2(OrthancPluginDatabaseContext* context,
+                                      void* payload,
+                                      const char* value)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::list<int64_t> target;
+        backend->LookupIdentifier(target, value);
+
+        for (std::list<int64_t>::const_iterator
+               it = target.begin(); it != target.end(); ++it)
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, *it);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupMetadata(OrthancPluginDatabaseContext* context,
+                                   void* payload,
+                                   int64_t id,
+                                   int32_t metadata)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        std::string s;
+        if (backend->LookupMetadata(s, id, metadata))
+        {
+          OrthancPluginDatabaseAnswerString(backend->GetOutput().context_,
+                                            backend->GetOutput().database_, s.c_str());
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupParent(OrthancPluginDatabaseContext* context,
+                                 void* payload,
+                                 int64_t id)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t parent;
+        if (backend->LookupParent(parent, id))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, parent);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  LookupResource(OrthancPluginDatabaseContext* context,
+                                   void* payload,
+                                   const char* publicId)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        OrthancPluginResourceType type;
+        if (backend->LookupResource(id, type, publicId))
+        {
+          OrthancPluginDatabaseAnswerResource(backend->GetOutput().context_,
+                                              backend->GetOutput().database_, 
+                                              id, type);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SelectPatientToRecycle(OrthancPluginDatabaseContext* context,
+                                           void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        if (backend->SelectPatientToRecycle(id))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, id);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SelectPatientToRecycle2(OrthancPluginDatabaseContext* context,
+                                            void* payload,
+                                            int64_t patientIdToAvoid)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        int64_t id;
+        if (backend->SelectPatientToRecycle(id, patientIdToAvoid))
+        {
+          OrthancPluginDatabaseAnswerInt64(backend->GetOutput().context_,
+                                           backend->GetOutput().database_, id);
+        }
+
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SetGlobalProperty(void* payload,
+                                      int32_t property,
+                                      const char* value)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetGlobalProperty(property, value);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SetMainDicomTag(void* payload,
+                                    int64_t id,
+                                    const OrthancPluginDicomTag* tag)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetMainDicomTag(id, tag->group, tag->element, tag->value);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SetIdentifierTag(void* payload,
+                                    int64_t id,
+                                    const OrthancPluginDicomTag* tag)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetIdentifierTag(id, tag->group, tag->element, tag->value);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SetMetadata(void* payload,
+                                int64_t id,
+                                int32_t metadata,
+                                const char* value)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetMetadata(id, metadata, value);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t  SetProtectedPatient(void* payload,
+                                        int64_t id,
+                                        int32_t isProtected)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->SetProtectedPatient(id, isProtected);
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t StartTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->StartTransaction();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t RollbackTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->RollbackTransaction();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t CommitTransaction(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->CommitTransaction();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t Open(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->Open();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+
+    static int32_t Close(void* payload)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+      backend->GetOutput().SetAllowedAnswers(DatabaseBackendOutput::AllowedAnswers_None);
+
+      try
+      {
+        backend->Close();
+        return 0;
+      }
+      catch (std::runtime_error& e)
+      {
+        LogError(backend, e);
+        return -1;
+      }
+    }
+
+    
+  public:
+    static void Register(OrthancPluginContext* context,
+                         IDatabaseBackend& backend)
+    {
+      OrthancPluginDatabaseBackend  params;
+      memset(&params, 0, sizeof(params));
+
+      params.addAttachment = AddAttachment;
+      params.attachChild = AttachChild;
+      params.clearChanges = ClearChanges;
+      params.clearExportedResources = ClearExportedResources;
+      params.createResource = CreateResource;
+      params.deleteAttachment = DeleteAttachment;
+      params.deleteMetadata = DeleteMetadata;
+      params.deleteResource = DeleteResource;
+      params.getAllPublicIds = GetAllPublicIds;
+      params.getChanges = GetChanges;
+      params.getChildrenInternalId = GetChildrenInternalId;
+      params.getChildrenPublicId = GetChildrenPublicId;
+      params.getExportedResources = GetExportedResources;
+      params.getLastChange = GetLastChange;
+      params.getLastExportedResource = GetLastExportedResource;
+      params.getMainDicomTags = GetMainDicomTags;
+      params.getPublicId = GetPublicId;
+      params.getResourceCount = GetResourceCount;
+      params.getResourceType = GetResourceType;
+      params.getTotalCompressedSize = GetTotalCompressedSize;
+      params.getTotalUncompressedSize = GetTotalUncompressedSize;
+      params.isExistingResource = IsExistingResource;
+      params.isProtectedPatient = IsProtectedPatient;
+      params.listAvailableMetadata = ListAvailableMetadata;
+      params.listAvailableAttachments = ListAvailableAttachments;
+      params.logChange = LogChange;
+      params.logExportedResource = LogExportedResource;
+      params.lookupAttachment = LookupAttachment;
+      params.lookupGlobalProperty = LookupGlobalProperty;
+      params.lookupIdentifier = LookupIdentifier;
+      params.lookupIdentifier2 = LookupIdentifier2;
+      params.lookupMetadata = LookupMetadata;
+      params.lookupParent = LookupParent;
+      params.lookupResource = LookupResource;
+      params.selectPatientToRecycle = SelectPatientToRecycle;
+      params.selectPatientToRecycle2 = SelectPatientToRecycle2;
+      params.setGlobalProperty = SetGlobalProperty;
+      params.setMainDicomTag = SetMainDicomTag;
+      params.setIdentifierTag = SetIdentifierTag;
+      params.setMetadata = SetMetadata;
+      params.setProtectedPatient = SetProtectedPatient;
+      params.startTransaction = StartTransaction;
+      params.rollbackTransaction = RollbackTransaction;
+      params.commitTransaction = CommitTransaction;
+      params.open = Open;
+      params.close = Close;
+
+      OrthancPluginDatabaseContext* database = OrthancPluginRegisterDatabaseBackend(context, &params, &backend);
+      if (!context)
+      {
+        throw std::runtime_error("Unable to register the database backend");
+      }
+
+      backend.RegisterOutput(new DatabaseBackendOutput(context, database));
+    }
+  };
+}
--- a/Resources/OrthancPlugin.doxygen	Wed Feb 11 10:26:17 2015 +0100
+++ b/Resources/OrthancPlugin.doxygen	Wed Feb 11 10:51:45 2015 +0100
@@ -655,7 +655,7 @@
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/Include/OrthancCPlugin.h
+INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/Include/
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is