diff OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp @ 4591:ff8170d17d90 db-changes

moving all accesses to databases from IDatabaseWrapper to ITransaction
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 15 Mar 2021 15:30:42 +0100
parents bec74e29f86b
children 36bbf3169a27
line wrap: on
line diff
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -41,13 +41,20 @@
 
 #include "../../../OrthancFramework/Sources/Logging.h"
 #include "../../../OrthancFramework/Sources/OrthancException.h"
+#include "../../Sources/Database/VoidDatabaseListener.h"
 #include "PluginsEnumerations.h"
 
 #include <cassert>
 
 namespace Orthanc
 {
-  class OrthancPluginDatabase::Transaction : public IDatabaseWrapper::ITransaction
+  class OrthancPluginDatabase::Transaction :
+    public IDatabaseWrapper::ITransaction,
+    public Compatibility::ICreateInstance,
+    public Compatibility::IGetChildrenMetadata,
+    public Compatibility::ILookupResources,
+    public Compatibility::ILookupResourceAndParent,
+    public Compatibility::ISetResourcesContent
   {
   private:
     OrthancPluginDatabase&  that_;
@@ -101,7 +108,7 @@
 
         uint64_t newDiskSize = (that_.currentDiskSize_ + diskSizeDelta);
 
-        assert(newDiskSize == that_.GetTotalCompressedSize());
+        assert(newDiskSize == GetTotalCompressedSize());
 
         CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
 
@@ -109,6 +116,871 @@
         that_.currentDiskSize_ = newDiskSize;
       }
     }
+
+
+    // From the "ILookupResources" interface
+    void GetAllInternalIds(std::list<int64_t>& target,
+                           ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllInternalIds == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "The database plugin does not implement the mandatory GetAllInternalIds() extension");
+      }
+
+      that_.ResetAnswers();
+      CheckSuccess(that_.extensions_.getAllInternalIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
+      that_.ForwardAnswers(target);
+    }
+
+
+
+    // From the "ILookupResources" interface
+    void LookupIdentifier(std::list<int64_t>& result,
+                          ResourceType level,
+                          const DicomTag& tag,
+                          Compatibility::IdentifierConstraintType type,
+                          const std::string& value) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupIdentifier3 == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "The database plugin does not implement the mandatory LookupIdentifier3() extension");
+      }
+
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      that_.ResetAnswers();
+      CheckSuccess(that_.extensions_.lookupIdentifier3(that_.GetContext(), that_.payload_, Plugins::Convert(level),
+                                                 &tmp, Compatibility::Convert(type)));
+      that_.ForwardAnswers(result);
+    }
+
+    
+
+    /**
+     * Implementation of "ITransaction"
+     **/
+    
+    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                      std::list<std::string>* instancesId,
+                                      const std::vector<DatabaseConstraint>& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupResources == NULL)
+      {
+        // Fallback to compatibility mode
+        ILookupResources::Apply
+          (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
+      }
+      else
+      {
+        std::vector<OrthancPluginDatabaseConstraint> constraints;
+        std::vector< std::vector<const char*> > constraintsValues;
+
+        constraints.resize(lookup.size());
+        constraintsValues.resize(lookup.size());
+
+        for (size_t i = 0; i < lookup.size(); i++)
+        {
+          lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
+        }
+
+        that_.ResetAnswers();
+        that_.answerMatchingResources_ = &resourcesId;
+        that_.answerMatchingInstances_ = instancesId;
+      
+        CheckSuccess(that_.extensions_.lookupResources(that_.GetContext(), that_.payload_, lookup.size(),
+                                                 (lookup.empty() ? NULL : &constraints[0]),
+                                                 Plugins::Convert(queryLevel),
+                                                 limit, (instancesId == NULL ? 0 : 1)));
+      }
+    }
+
+
+    bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result,
+                        int64_t& instanceId,
+                        const std::string& patient,
+                        const std::string& study,
+                        const std::string& series,
+                        const std::string& instance) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.createInstance == NULL)
+      {
+        // Fallback to compatibility mode
+        return ICreateInstance::Apply
+          (*this, result, instanceId, patient, study, series, instance);
+      }
+      else
+      {
+        OrthancPluginCreateInstanceResult output;
+        memset(&output, 0, sizeof(output));
+
+        CheckSuccess(that_.extensions_.createInstance(&output, that_.payload_, patient.c_str(),
+                                                study.c_str(), series.c_str(), instance.c_str()));
+
+        instanceId = output.instanceId;
+      
+        if (output.isNewInstance)
+        {
+          result.isNewPatient_ = output.isNewPatient;
+          result.isNewStudy_ = output.isNewStudy;
+          result.isNewSeries_ = output.isNewSeries;
+          result.patientId_ = output.patientId;
+          result.studyId_ = output.studyId;
+          result.seriesId_ = output.seriesId;
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+
+
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type) ORTHANC_OVERRIDE
+    {
+      int64_t id;
+      CheckSuccess(that_.backend_.createResource(&id, that_.payload_, publicId.c_str(), Plugins::Convert(type)));
+      return id;
+    }
+    
+
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment) ORTHANC_OVERRIDE
+    {
+      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();
+
+      CheckSuccess(that_.backend_.addAttachment(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.attachChild(that_.payload_, parent, child));
+    }
+
+
+    virtual void ClearChanges() ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.clearChanges(that_.payload_));
+    }
+
+
+    virtual void ClearExportedResources() ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.clearExportedResources(that_.payload_));
+    }
+
+
+    virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.clearMainDicomTags == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
+      }
+
+      CheckSuccess(that_.extensions_.clearMainDicomTags(that_.payload_, id));
+    }
+
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteAttachment(that_.payload_, id, static_cast<int32_t>(attachment)));
+    }
+
+
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteMetadata(that_.payload_, id, static_cast<int32_t>(type)));
+    }
+
+
+    virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteResource(that_.payload_, id));
+    }
+
+
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllMetadata == NULL)
+      {
+        // Fallback implementation if extension is missing
+        target.clear();
+
+        that_.ResetAnswers();
+        CheckSuccess(that_.backend_.listAvailableMetadata(that_.GetContext(), that_.payload_, id));
+
+        if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+            that_.type_ != _OrthancPluginDatabaseAnswerType_Int32)
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+
+        target.clear();
+
+        if (that_.type_ == _OrthancPluginDatabaseAnswerType_Int32)
+        {
+          for (std::list<int32_t>::const_iterator 
+                 it = that_.answerInt32_.begin(); it != that_.answerInt32_.end(); ++it)
+          {
+            MetadataType type = static_cast<MetadataType>(*it);
+
+            std::string value;
+            if (LookupMetadata(value, id, type))
+            {
+              target[type] = value;
+            }
+          }
+        }
+      }
+      else
+      {
+        that_.ResetAnswers();
+
+        that_.answerMetadata_ = &target;
+        target.clear();
+      
+        CheckSuccess(that_.extensions_.getAllMetadata(that_.GetContext(), that_.payload_, id));
+
+        if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+            that_.type_ != _OrthancPluginDatabaseAnswerType_Metadata)
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+      }
+    }
+
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getAllPublicIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType,
+                                 size_t since,
+                                 size_t limit) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllPublicIdsWithLimit != NULL)
+      {
+        // This extension is available since Orthanc 0.9.4
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.getAllPublicIdsWithLimit
+                     (that_.GetContext(), that_.payload_, Plugins::Convert(resourceType), since, limit));
+        that_.ForwardAnswers(target);
+      }
+      else
+      {
+        // The extension is not available in the database plugin, use a
+        // fallback implementation
+        target.clear();
+
+        if (limit == 0)
+        {
+          return;
+        }
+
+        std::list<std::string> tmp;
+        GetAllPublicIds(tmp, resourceType);
+    
+        if (tmp.size() <= since)
+        {
+          // Not enough results => empty answer
+          return;
+        }
+
+        std::list<std::string>::iterator current = tmp.begin();
+        std::advance(current, since);
+
+        while (limit > 0 && current != tmp.end())
+        {
+          target.push_back(*current);
+          --limit;
+          ++current;
+        }
+      }
+    }
+
+
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerChanges_ = &target;
+      that_.answerDone_ = &done;
+      done = false;
+
+      CheckSuccess(that_.backend_.getChanges(that_.GetContext(), that_.payload_, since, maxResults));
+    }
+
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getChildrenInternalId(that_.GetContext(), that_.payload_, id));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetChildrenMetadata(std::list<std::string>& target,
+                                     int64_t resourceId,
+                                     MetadataType metadata) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getChildrenMetadata == NULL)
+      {
+        IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
+      }
+      else
+      {
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.getChildrenMetadata
+                     (that_.GetContext(), that_.payload_, resourceId, static_cast<int32_t>(metadata)));
+        that_.ForwardAnswers(target);
+      }
+    }
+
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getChildrenPublicId(that_.GetContext(), that_.payload_, id));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerExportedResources_ = &target;
+      that_.answerDone_ = &done;
+      done = false;
+
+      CheckSuccess(that_.backend_.getExportedResources(that_.GetContext(), that_.payload_, since, maxResults));
+    }
+
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
+    {
+      that_.answerDoneIgnored_ = false;
+
+      that_.ResetAnswers();
+      that_.answerChanges_ = &target;
+      that_.answerDone_ = &that_.answerDoneIgnored_;
+
+      CheckSuccess(that_.backend_.getLastChange(that_.GetContext(), that_.payload_));
+    }
+
+
+    int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getLastChangeIndex == NULL)
+      {
+        // This was the default behavior in Orthanc <= 1.5.1
+        // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
+        return 0;
+      }
+      else
+      {
+        int64_t result = 0;
+        CheckSuccess(that_.extensions_.getLastChangeIndex(&result, that_.payload_));
+        return result;
+      }
+    }
+
+  
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
+    {
+      that_.answerDoneIgnored_ = false;
+
+      that_.ResetAnswers();
+      that_.answerExportedResources_ = &target;
+      that_.answerDone_ = &that_.answerDoneIgnored_;
+
+      CheckSuccess(that_.backend_.getLastExportedResource(that_.GetContext(), that_.payload_));
+    }
+
+
+    virtual void GetMainDicomTags(DicomMap& map,
+                                  int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerDicomMap_ = &map;
+
+      CheckSuccess(that_.backend_.getMainDicomTags(that_.GetContext(), that_.payload_, id));
+    }
+
+
+    virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      std::string s;
+
+      CheckSuccess(that_.backend_.getPublicId(that_.GetContext(), that_.payload_, resourceId));
+
+      if (!that_.ForwardSingleAnswer(s))
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+
+      return s;
+    }
+
+
+    virtual uint64_t GetResourceCount(ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      uint64_t count;
+      CheckSuccess(that_.backend_.getResourceCount(&count, that_.payload_, Plugins::Convert(resourceType)));
+      return count;
+    }
+
+
+    virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      OrthancPluginResourceType type;
+      CheckSuccess(that_.backend_.getResourceType(&type, that_.payload_, resourceId));
+      return Plugins::Convert(type);
+    }
+
+
+    virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
+    {
+      uint64_t size;
+      CheckSuccess(that_.backend_.getTotalCompressedSize(&size, that_.payload_));
+      return size;
+    }
+
+    
+    virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
+    {
+      uint64_t size;
+      CheckSuccess(that_.backend_.getTotalUncompressedSize(&size, that_.payload_));
+      return size;
+    }
+    
+
+    virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
+    {
+      if (that_.fastGetTotalSize_)
+      {
+        return GetTotalCompressedSize() > threshold;
+      }
+      else
+      {
+        assert(GetTotalCompressedSize() == that_.currentDiskSize_);
+        return that_.currentDiskSize_ > threshold;
+      }      
+    }
+
+
+    virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      int32_t existing;
+      CheckSuccess(that_.backend_.isExistingResource(&existing, that_.payload_, internalId));
+      return (existing != 0);
+    }
+
+
+    virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      int32_t isProtected;
+      CheckSuccess(that_.backend_.isProtectedPatient(&isProtected, that_.payload_, internalId));
+      return (isProtected != 0);
+    }
+
+
+    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
+                                          int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.listAvailableAttachments(that_.GetContext(), that_.payload_, id));
+
+      if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+          that_.type_ != _OrthancPluginDatabaseAnswerType_Int32)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+
+      target.clear();
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_Int32)
+      {
+        for (std::list<int32_t>::const_iterator 
+               it = that_.answerInt32_.begin(); it != that_.answerInt32_.end(); ++it)
+        {
+          target.insert(static_cast<FileContentType>(*it));
+        }
+      }
+    }
+
+
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change) ORTHANC_OVERRIDE
+    {
+      OrthancPluginChange tmp;
+      tmp.seq = change.GetSeq();
+      tmp.changeType = static_cast<int32_t>(change.GetChangeType());
+      tmp.resourceType = Plugins::Convert(change.GetResourceType());
+      tmp.publicId = change.GetPublicId().c_str();
+      tmp.date = change.GetDate().c_str();
+
+      CheckSuccess(that_.backend_.logChange(that_.payload_, &tmp));
+    }
+
+
+    virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
+    {
+      OrthancPluginExportedResource tmp;
+      tmp.seq = resource.GetSeq();
+      tmp.resourceType = Plugins::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();
+
+      CheckSuccess(that_.backend_.logExportedResource(that_.payload_, &tmp));
+    }
+
+    
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupAttachment
+                   (that_.GetContext(), that_.payload_, id, static_cast<int32_t>(contentType)));
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_None)
+      {
+        return false;
+      }
+      else if (that_.type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
+               that_.answerAttachments_.size() == 1)
+      {
+        attachment = that_.answerAttachments_.front();
+        return true; 
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
+
+
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupGlobalProperty
+                   (that_.GetContext(), that_.payload_, static_cast<int32_t>(property)));
+
+      return that_.ForwardSingleAnswer(target);
+    }
+
+
+    virtual void LookupIdentifierRange(std::list<int64_t>& result,
+                                       ResourceType level,
+                                       const DicomTag& tag,
+                                       const std::string& start,
+                                       const std::string& end) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupIdentifierRange == NULL)
+      {
+        // Default implementation, for plugins using Orthanc SDK <= 1.3.2
+
+        LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
+
+        std::list<int64_t> b;
+        LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
+
+        result.splice(result.end(), b);
+      }
+      else
+      {
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.lookupIdentifierRange(that_.GetContext(), that_.payload_, Plugins::Convert(level),
+                                                       tag.GetGroup(), tag.GetElement(),
+                                                       start.c_str(), end.c_str()));
+        that_.ForwardAnswers(result);
+      }
+    }
+
+
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.lookupMetadata(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(type)));
+      return that_.ForwardSingleAnswer(target);
+    }
+
+
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.lookupParent(that_.GetContext(), that_.payload_, resourceId));
+      return that_.ForwardSingleAnswer(parentId);
+    }
+
+
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupResource(that_.GetContext(), that_.payload_, publicId.c_str()));
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_None)
+      {
+        return false;
+      }
+      else if (that_.type_ == _OrthancPluginDatabaseAnswerType_Resource &&
+               that_.answerResources_.size() == 1)
+      {
+        id = that_.answerResources_.front().first;
+        type = that_.answerResources_.front().second;
+        return true; 
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
+
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupResourceAndParent == NULL)
+      {
+        return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
+      }
+      else
+      {
+        std::list<std::string> parent;
+
+        uint8_t isExisting;
+        OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
+      
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.lookupResourceAndParent
+                     (that_.GetContext(), &isExisting, &id, &pluginType, that_.payload_, publicId.c_str()));
+        that_.ForwardAnswers(parent);
+
+        if (isExisting)
+        {
+          type = Plugins::Convert(pluginType);
+
+          if (parent.empty())
+          {
+            if (type != ResourceType_Patient)
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+          }
+          else if (parent.size() == 1)
+          {
+            if ((type != ResourceType_Study &&
+                 type != ResourceType_Series &&
+                 type != ResourceType_Instance) ||
+                parent.front().empty())
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+
+            parentPublicId = parent.front();
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_DatabasePlugin);
+          }
+
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.selectPatientToRecycle(that_.GetContext(), that_.payload_));
+      return that_.ForwardSingleAnswer(internalId);
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.selectPatientToRecycle2(that_.GetContext(), that_.payload_, patientIdToAvoid));
+      return that_.ForwardSingleAnswer(internalId);
+    }
+
+
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setGlobalProperty
+                   (that_.payload_, static_cast<int32_t>(property), value.c_str()));
+    }
+
+
+    virtual void SetIdentifierTag(int64_t id,
+                                  const DicomTag& tag,
+                                  const std::string& value) ORTHANC_OVERRIDE
+    {
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      CheckSuccess(that_.backend_.setIdentifierTag(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value) ORTHANC_OVERRIDE
+    {
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      CheckSuccess(that_.backend_.setMainDicomTag(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setMetadata
+                   (that_.payload_, id, static_cast<int32_t>(type), value.c_str()));
+    }
+
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setProtectedPatient(that_.payload_, internalId, isProtected));
+    }
+
+
+    virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.setResourcesContent == NULL)
+      {
+        ISetResourcesContent::Apply(*this, content);
+      }
+      else
+      {
+        std::vector<OrthancPluginResourcesContentTags> identifierTags;
+        std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
+        std::vector<OrthancPluginResourcesContentMetadata> metadata;
+
+        identifierTags.reserve(content.GetListTags().size());
+        mainDicomTags.reserve(content.GetListTags().size());
+        metadata.reserve(content.GetListMetadata().size());
+
+        for (ResourcesContent::ListTags::const_iterator
+               it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
+        {
+          OrthancPluginResourcesContentTags tmp;
+          tmp.resource = it->resourceId_;
+          tmp.group = it->tag_.GetGroup();
+          tmp.element = it->tag_.GetElement();
+          tmp.value = it->value_.c_str();
+
+          if (it->isIdentifier_)
+          {
+            identifierTags.push_back(tmp);
+          }
+          else
+          {
+            mainDicomTags.push_back(tmp);
+          }
+        }
+
+        for (ResourcesContent::ListMetadata::const_iterator
+               it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
+        {
+          OrthancPluginResourcesContentMetadata tmp;
+          tmp.resource = it->resourceId_;
+          tmp.metadata = it->metadata_;
+          tmp.value = it->value_.c_str();
+          metadata.push_back(tmp);
+        }
+
+        assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
+               metadata.size() == content.GetListMetadata().size());
+       
+        CheckSuccess(that_.extensions_.setResourcesContent(
+                       that_.payload_,
+                       identifierTags.size(),
+                       (identifierTags.empty() ? NULL : &identifierTags[0]),
+                       mainDicomTags.size(),
+                       (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
+                       metadata.size(),
+                       (metadata.empty() ? NULL : &metadata[0])));
+      }
+    }
+
+
+    virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.tagMostRecentPatient != NULL)
+      {
+        CheckSuccess(that_.extensions_.tagMostRecentPatient(that_.payload_, patient));
+      }
+    }
+
   };
 
 
@@ -319,36 +1191,11 @@
   }
 
 
-  namespace
-  {
-    class VoidListener : public IDatabaseListener
-    {
-    public:
-      virtual void SignalRemainingAncestor(ResourceType parentType,
-                                           const std::string& publicId)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }
-      
-      virtual void SignalAttachmentDeleted(const FileInfo& info)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }
-
-      virtual void SignalResourceDeleted(ResourceType type,
-                                         const std::string& publicId)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }      
-    };
-  }
-
-
   void OrthancPluginDatabase::Open()
   {
     CheckSuccess(backend_.open(payload_));
 
-    VoidListener listener;
+    VoidDatabaseListener listener;
     
     {
       Transaction transaction(*this, listener);
@@ -356,7 +1203,7 @@
 
       std::string tmp;
       fastGetTotalSize_ =
-        (LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) &&
+        (transaction.LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) &&
          tmp == "1");
       
       if (fastGetTotalSize_)
@@ -367,7 +1214,7 @@
       {
         // This is the case of database plugins using Orthanc SDK <= 1.5.2
         LOG(WARNING) << "Your database index plugin is not compatible with multiple Orthanc writers";
-        currentDiskSize_ = GetTotalCompressedSize();
+        currentDiskSize_ = transaction.GetTotalCompressedSize();
       }
 
       transaction.Commit(0);
@@ -375,556 +1222,6 @@
   }
 
 
-  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();
-
-    CheckSuccess(backend_.addAttachment(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::AttachChild(int64_t parent,
-                                          int64_t child)
-  {
-    CheckSuccess(backend_.attachChild(payload_, parent, child));
-  }
-
-
-  void OrthancPluginDatabase::ClearChanges()
-  {
-    CheckSuccess(backend_.clearChanges(payload_));
-  }
-
-
-  void OrthancPluginDatabase::ClearExportedResources()
-  {
-    CheckSuccess(backend_.clearExportedResources(payload_));
-  }
-
-
-  int64_t OrthancPluginDatabase::CreateResource(const std::string& publicId,
-                                                ResourceType type)
-  {
-    int64_t id;
-    CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type)));
-    return id;
-  }
-
-
-  void OrthancPluginDatabase::DeleteAttachment(int64_t id,
-                                               FileContentType attachment)
-  {
-    CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)));
-  }
-
-
-  void OrthancPluginDatabase::DeleteMetadata(int64_t id,
-                                             MetadataType type)
-  {
-    CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)));
-  }
-
-
-  void OrthancPluginDatabase::DeleteResource(int64_t id)
-  {
-    CheckSuccess(backend_.deleteResource(payload_, id));
-  }
-
-
-  void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                             int64_t id)
-  {
-    if (extensions_.getAllMetadata == NULL)
-    {
-      // Fallback implementation if extension is missing
-      target.clear();
-
-      ResetAnswers();
-      CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
-
-      if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-          type_ != _OrthancPluginDatabaseAnswerType_Int32)
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-
-      target.clear();
-
-      if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
-      {
-        for (std::list<int32_t>::const_iterator 
-               it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
-        {
-          MetadataType type = static_cast<MetadataType>(*it);
-
-          std::string value;
-          if (LookupMetadata(value, id, type))
-          {
-            target[type] = value;
-          }
-        }
-      }
-    }
-    else
-    {
-      ResetAnswers();
-
-      answerMetadata_ = &target;
-      target.clear();
-      
-      CheckSuccess(extensions_.getAllMetadata(GetContext(), payload_, id));
-
-      if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-          type_ != _OrthancPluginDatabaseAnswerType_Metadata)
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-    }
-  }
-
-
-  void OrthancPluginDatabase::GetAllInternalIds(std::list<int64_t>& target,
-                                                ResourceType resourceType)
-  {
-    if (extensions_.getAllInternalIds == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "The database plugin does not implement the mandatory GetAllInternalIds() extension");
-    }
-
-    ResetAnswers();
-    CheckSuccess(extensions_.getAllInternalIds(GetContext(), payload_, Plugins::Convert(resourceType)));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType)));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType,
-                                              size_t since,
-                                              size_t limit)
-  {
-    if (extensions_.getAllPublicIdsWithLimit != NULL)
-    {
-      // This extension is available since Orthanc 0.9.4
-      ResetAnswers();
-      CheckSuccess(extensions_.getAllPublicIdsWithLimit
-                   (GetContext(), payload_, Plugins::Convert(resourceType), since, limit));
-      ForwardAnswers(target);
-    }
-    else
-    {
-      // The extension is not available in the database plugin, use a
-      // fallback implementation
-      target.clear();
-
-      if (limit == 0)
-      {
-        return;
-      }
-
-      std::list<std::string> tmp;
-      GetAllPublicIds(tmp, resourceType);
-    
-      if (tmp.size() <= since)
-      {
-        // Not enough results => empty answer
-        return;
-      }
-
-      std::list<std::string>::iterator current = tmp.begin();
-      std::advance(current, since);
-
-      while (limit > 0 && current != tmp.end())
-      {
-        target.push_back(*current);
-        --limit;
-        ++current;
-      }
-    }
-  }
-
-
-
-  void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                                         bool& done /*out*/,
-                                         int64_t since,
-                                         uint32_t maxResults)
-  {
-    ResetAnswers();
-    answerChanges_ = &target;
-    answerDone_ = &done;
-    done = false;
-
-    CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults));
-  }
-
-
-  void OrthancPluginDatabase::GetChildrenInternalId(std::list<int64_t>& target,
-                                                    int64_t id)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetChildrenPublicId(std::list<std::string>& target,
-                                                  int64_t id)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id));
-    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;
-
-    CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults));
-  }
-
-
-  void OrthancPluginDatabase::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
-  {
-    answerDoneIgnored_ = false;
-
-    ResetAnswers();
-    answerChanges_ = &target;
-    answerDone_ = &answerDoneIgnored_;
-
-    CheckSuccess(backend_.getLastChange(GetContext(), payload_));
-  }
-
-
-  void OrthancPluginDatabase::GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
-  {
-    answerDoneIgnored_ = false;
-
-    ResetAnswers();
-    answerExportedResources_ = &target;
-    answerDone_ = &answerDoneIgnored_;
-
-    CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_));
-  }
-
-
-  void OrthancPluginDatabase::GetMainDicomTags(DicomMap& map,
-                                               int64_t id)
-  {
-    ResetAnswers();
-    answerDicomMap_ = &map;
-
-    CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id));
-  }
-
-
-  std::string OrthancPluginDatabase::GetPublicId(int64_t resourceId)
-  {
-    ResetAnswers();
-    std::string s;
-
-    CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId));
-
-    if (!ForwardSingleAnswer(s))
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-
-    return s;
-  }
-
-
-  uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
-  {
-    uint64_t count;
-    CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType)));
-    return count;
-  }
-
-
-  ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
-  {
-    OrthancPluginResourceType type;
-    CheckSuccess(backend_.getResourceType(&type, payload_, resourceId));
-    return Plugins::Convert(type);
-  }
-
-
-  uint64_t OrthancPluginDatabase::GetTotalCompressedSize()
-  {
-    uint64_t size;
-    CheckSuccess(backend_.getTotalCompressedSize(&size, payload_));
-    return size;
-  }
-
-    
-  uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
-  {
-    uint64_t size;
-    CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_));
-    return size;
-  }
-
-
-  bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
-  {
-    int32_t existing;
-    CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId));
-    return (existing != 0);
-  }
-
-
-  bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
-  {
-    int32_t isProtected;
-    CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId));
-    return (isProtected != 0);
-  }
-
-
-  void OrthancPluginDatabase::ListAvailableAttachments(std::set<FileContentType>& target,
-                                                       int64_t id)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id));
-
-    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-        type_ != _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-
-    target.clear();
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      for (std::list<int32_t>::const_iterator 
-             it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
-      {
-        target.insert(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 = Plugins::Convert(change.GetResourceType());
-    tmp.publicId = change.GetPublicId().c_str();
-    tmp.date = change.GetDate().c_str();
-
-    CheckSuccess(backend_.logChange(payload_, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::LogExportedResource(const ExportedResource& resource)
-  {
-    OrthancPluginExportedResource tmp;
-    tmp.seq = resource.GetSeq();
-    tmp.resourceType = Plugins::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();
-
-    CheckSuccess(backend_.logExportedResource(payload_, &tmp));
-  }
-
-    
-  bool OrthancPluginDatabase::LookupAttachment(FileInfo& attachment,
-                                               int64_t id,
-                                               FileContentType contentType)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupAttachment
-                 (GetContext(), payload_, id, static_cast<int32_t>(contentType)));
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_None)
-    {
-      return false;
-    }
-    else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
-             answerAttachments_.size() == 1)
-    {
-      attachment = answerAttachments_.front();
-      return true; 
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-  }
-
-
-  bool OrthancPluginDatabase::LookupGlobalProperty(std::string& target,
-                                                   GlobalProperty property)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupGlobalProperty
-                 (GetContext(), payload_, static_cast<int32_t>(property)));
-
-    return ForwardSingleAnswer(target);
-  }
-
-
-  bool OrthancPluginDatabase::LookupMetadata(std::string& target,
-                                             int64_t id,
-                                             MetadataType type)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)));
-    return ForwardSingleAnswer(target);
-  }
-
-
-  bool OrthancPluginDatabase::LookupParent(int64_t& parentId,
-                                           int64_t resourceId)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId));
-    return ForwardSingleAnswer(parentId);
-  }
-
-
-  bool OrthancPluginDatabase::LookupResource(int64_t& id,
-                                             ResourceType& type,
-                                             const std::string& publicId)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str()));
-
-    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_DatabasePlugin);
-    }
-  }
-
-
-  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_));
-    return ForwardSingleAnswer(internalId);
-  }
-
-
-  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId,
-                                                     int64_t patientIdToAvoid)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid));
-    return ForwardSingleAnswer(internalId);
-  }
-
-
-  void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property,
-                                                const std::string& value)
-  {
-    CheckSuccess(backend_.setGlobalProperty
-                 (payload_, static_cast<int32_t>(property), value.c_str()));
-  }
-
-
-  void OrthancPluginDatabase::ClearMainDicomTags(int64_t id)
-  {
-    if (extensions_.clearMainDicomTags == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
-    }
-
-    CheckSuccess(extensions_.clearMainDicomTags(payload_, id));
-  }
-
-
-  void OrthancPluginDatabase::SetMainDicomTag(int64_t id,
-                                              const DicomTag& tag,
-                                              const std::string& value)
-  {
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    CheckSuccess(backend_.setMainDicomTag(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::SetIdentifierTag(int64_t id,
-                                               const DicomTag& tag,
-                                               const std::string& value)
-  {
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    CheckSuccess(backend_.setIdentifierTag(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::SetMetadata(int64_t id,
-                                          MetadataType type,
-                                          const std::string& value)
-  {
-    CheckSuccess(backend_.setMetadata
-                 (payload_, id, static_cast<int32_t>(type), value.c_str()));
-  }
-
-
-  void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, 
-                                                  bool isProtected)
-  {
-    CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected));
-  }
-
-
   IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction(TransactionType type,
                                                                           IDatabaseListener& listener)
   {
@@ -990,7 +1287,7 @@
   void OrthancPluginDatabase::Upgrade(unsigned int targetVersion,
                                       IStorageArea& storageArea)
   {
-    VoidListener listener;
+    VoidDatabaseListener listener;
     
     if (extensions_.upgradeDatabase != NULL)
     {
@@ -1267,313 +1564,4 @@
                                boost::lexical_cast<std::string>(answer.type));
     }
   }
-
-    
-  bool OrthancPluginDatabase::IsDiskSizeAbove(uint64_t threshold)
-  {
-    if (fastGetTotalSize_)
-    {
-      return GetTotalCompressedSize() > threshold;
-    }
-    else
-    {
-      assert(GetTotalCompressedSize() == currentDiskSize_);
-      return currentDiskSize_ > threshold;
-    }      
-  }
-
-
-  void OrthancPluginDatabase::ApplyLookupResources(std::list<std::string>& resourcesId,
-                                                   std::list<std::string>* instancesId,
-                                                   const std::vector<DatabaseConstraint>& lookup,
-                                                   ResourceType queryLevel,
-                                                   size_t limit)
-  {
-    if (extensions_.lookupResources == NULL)
-    {
-      // Fallback to compatibility mode
-      ILookupResources::Apply
-        (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
-    }
-    else
-    {
-      std::vector<OrthancPluginDatabaseConstraint> constraints;
-      std::vector< std::vector<const char*> > constraintsValues;
-
-      constraints.resize(lookup.size());
-      constraintsValues.resize(lookup.size());
-
-      for (size_t i = 0; i < lookup.size(); i++)
-      {
-        lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
-      }
-
-      ResetAnswers();
-      answerMatchingResources_ = &resourcesId;
-      answerMatchingInstances_ = instancesId;
-      
-      CheckSuccess(extensions_.lookupResources(GetContext(), payload_, lookup.size(),
-                                               (lookup.empty() ? NULL : &constraints[0]),
-                                               Plugins::Convert(queryLevel),
-                                               limit, (instancesId == NULL ? 0 : 1)));
-    }
-  }
-
-
-  bool OrthancPluginDatabase::CreateInstance(
-    IDatabaseWrapper::CreateInstanceResult& result,
-    int64_t& instanceId,
-    const std::string& patient,
-    const std::string& study,
-    const std::string& series,
-    const std::string& instance)
-  {
-    if (extensions_.createInstance == NULL)
-    {
-      // Fallback to compatibility mode
-      return ICreateInstance::Apply
-        (*this, result, instanceId, patient, study, series, instance);
-    }
-    else
-    {
-      OrthancPluginCreateInstanceResult output;
-      memset(&output, 0, sizeof(output));
-
-      CheckSuccess(extensions_.createInstance(&output, payload_, patient.c_str(),
-                                              study.c_str(), series.c_str(), instance.c_str()));
-
-      instanceId = output.instanceId;
-      
-      if (output.isNewInstance)
-      {
-        result.isNewPatient_ = output.isNewPatient;
-        result.isNewStudy_ = output.isNewStudy;
-        result.isNewSeries_ = output.isNewSeries;
-        result.patientId_ = output.patientId;
-        result.studyId_ = output.studyId;
-        result.seriesId_ = output.seriesId;
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
-
-
-  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& result,
-                                               ResourceType level,
-                                               const DicomTag& tag,
-                                               Compatibility::IdentifierConstraintType type,
-                                               const std::string& value)
-  {
-    if (extensions_.lookupIdentifier3 == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "The database plugin does not implement the mandatory LookupIdentifier3() extension");
-    }
-
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    ResetAnswers();
-    CheckSuccess(extensions_.lookupIdentifier3(GetContext(), payload_, Plugins::Convert(level),
-                                               &tmp, Compatibility::Convert(type)));
-    ForwardAnswers(result);
-  }
-
-
-  void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result,
-                                                    ResourceType level,
-                                                    const DicomTag& tag,
-                                                    const std::string& start,
-                                                    const std::string& end)
-  {
-    if (extensions_.lookupIdentifierRange == NULL)
-    {
-      // Default implementation, for plugins using Orthanc SDK <= 1.3.2
-
-      LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
-
-      std::list<int64_t> b;
-      LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
-
-      result.splice(result.end(), b);
-    }
-    else
-    {
-      ResetAnswers();
-      CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level),
-                                                     tag.GetGroup(), tag.GetElement(),
-                                                     start.c_str(), end.c_str()));
-      ForwardAnswers(result);
-    }
-  }
-
-
-  void OrthancPluginDatabase::SetResourcesContent(const Orthanc::ResourcesContent& content)
-  {
-    if (extensions_.setResourcesContent == NULL)
-    {
-      ISetResourcesContent::Apply(*this, content);
-    }
-    else
-    {
-      std::vector<OrthancPluginResourcesContentTags> identifierTags;
-      std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
-      std::vector<OrthancPluginResourcesContentMetadata> metadata;
-
-      identifierTags.reserve(content.GetListTags().size());
-      mainDicomTags.reserve(content.GetListTags().size());
-      metadata.reserve(content.GetListMetadata().size());
-
-      for (ResourcesContent::ListTags::const_iterator
-             it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
-      {
-        OrthancPluginResourcesContentTags tmp;
-        tmp.resource = it->resourceId_;
-        tmp.group = it->tag_.GetGroup();
-        tmp.element = it->tag_.GetElement();
-        tmp.value = it->value_.c_str();
-
-        if (it->isIdentifier_)
-        {
-          identifierTags.push_back(tmp);
-        }
-        else
-        {
-          mainDicomTags.push_back(tmp);
-        }
-      }
-
-      for (ResourcesContent::ListMetadata::const_iterator
-             it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
-      {
-        OrthancPluginResourcesContentMetadata tmp;
-        tmp.resource = it->resourceId_;
-        tmp.metadata = it->metadata_;
-        tmp.value = it->value_.c_str();
-        metadata.push_back(tmp);
-      }
-
-      assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
-             metadata.size() == content.GetListMetadata().size());
-       
-      CheckSuccess(extensions_.setResourcesContent(
-                     payload_,
-                     identifierTags.size(),
-                     (identifierTags.empty() ? NULL : &identifierTags[0]),
-                     mainDicomTags.size(),
-                     (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
-                     metadata.size(),
-                     (metadata.empty() ? NULL : &metadata[0])));
-    }
-  }
-
-
-
-  void OrthancPluginDatabase::GetChildrenMetadata(std::list<std::string>& target,
-                                                  int64_t resourceId,
-                                                  MetadataType metadata)
-  {
-    if (extensions_.getChildrenMetadata == NULL)
-    {
-      IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
-    }
-    else
-    {
-      ResetAnswers();
-      CheckSuccess(extensions_.getChildrenMetadata
-                   (GetContext(), payload_, resourceId, static_cast<int32_t>(metadata)));
-      ForwardAnswers(target);
-    }
-  }
-
-
-  int64_t OrthancPluginDatabase::GetLastChangeIndex()
-  {
-    if (extensions_.getLastChangeIndex == NULL)
-    {
-      // This was the default behavior in Orthanc <= 1.5.1
-      // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
-      return 0;
-    }
-    else
-    {
-      int64_t result = 0;
-      CheckSuccess(extensions_.getLastChangeIndex(&result, payload_));
-      return result;
-    }
-  }
-
-  
-  void OrthancPluginDatabase::TagMostRecentPatient(int64_t patient)
-  {
-    if (extensions_.tagMostRecentPatient != NULL)
-    {
-      CheckSuccess(extensions_.tagMostRecentPatient(payload_, patient));
-    }
-  }
-
-
-  bool OrthancPluginDatabase::LookupResourceAndParent(int64_t& id,
-                                                      ResourceType& type,
-                                                      std::string& parentPublicId,
-                                                      const std::string& publicId)
-  {
-    if (extensions_.lookupResourceAndParent == NULL)
-    {
-      return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
-    }
-    else
-    {
-      std::list<std::string> parent;
-
-      uint8_t isExisting;
-      OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
-      
-      ResetAnswers();
-      CheckSuccess(extensions_.lookupResourceAndParent
-                   (GetContext(), &isExisting, &id, &pluginType, payload_, publicId.c_str()));
-      ForwardAnswers(parent);
-
-      if (isExisting)
-      {
-        type = Plugins::Convert(pluginType);
-
-        if (parent.empty())
-        {
-          if (type != ResourceType_Patient)
-          {
-            throw OrthancException(ErrorCode_DatabasePlugin);
-          }
-        }
-        else if (parent.size() == 1)
-        {
-          if ((type != ResourceType_Study &&
-               type != ResourceType_Series &&
-               type != ResourceType_Instance) ||
-              parent.front().empty())
-          {
-            throw OrthancException(ErrorCode_DatabasePlugin);
-          }
-
-          parentPublicId = parent.front();
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_DatabasePlugin);
-        }
-
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
 }