changeset 5207:e7529e6241d2 db-protobuf

first successful protobuf communication
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 30 Mar 2023 21:31:56 +0200
parents fb3add662286
children 154d37a56500
files OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake OrthancServer/CMakeLists.txt OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto OrthancServer/Sources/OrthancInitialization.cpp
diffstat 7 files changed, 720 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake	Thu Mar 30 17:08:13 2023 +0200
+++ b/OrthancFramework/Resources/CMake/ProtobufConfiguration.cmake	Thu Mar 30 21:31:56 2023 +0200
@@ -23,7 +23,7 @@
   if (ENABLE_PROTOBUF_COMPILER)
     include(ExternalProject)
     externalproject_add(ProtobufCompiler
-      SOURCE_DIR "${CMAKE_SOURCE_DIR}/../OrthancFramework/Resources/ProtocolBuffers"
+      SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../ProtocolBuffers"
       BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/ProtobufCompiler-build"
       # this helps triggering build when changing the external project
       BUILD_ALWAYS 1
--- a/OrthancServer/CMakeLists.txt	Thu Mar 30 17:08:13 2023 +0200
+++ b/OrthancServer/CMakeLists.txt	Thu Mar 30 21:31:56 2023 +0200
@@ -186,6 +186,7 @@
   list(APPEND ORTHANC_SERVER_SOURCES
     ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabase.cpp
     ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabaseV3.cpp
+    ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPluginDatabaseV4.cpp
     ${CMAKE_SOURCE_DIR}/Plugins/Engine/OrthancPlugins.cpp
     ${CMAKE_SOURCE_DIR}/Plugins/Engine/PluginsEnumerations.cpp
     ${CMAKE_SOURCE_DIR}/Plugins/Engine/PluginsErrorDictionary.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Thu Mar 30 21:31:56 2023 +0200
@@ -0,0 +1,570 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * 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 "../../Sources/PrecompiledHeadersServer.h"
+#include "OrthancPluginDatabaseV4.h"
+
+#if ORTHANC_ENABLE_PLUGINS != 1
+#  error The plugin support is disabled
+#endif
+
+#include "../../../OrthancFramework/Sources/Logging.h"
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+#include "PluginsEnumerations.h"
+
+#include "OrthancDatabasePlugin.pb.h"
+
+#include <cassert>
+
+
+namespace Orthanc
+{
+  class OrthancPluginDatabaseV4::Transaction : public IDatabaseWrapper::ITransaction
+  {
+  private:
+    OrthancPluginDatabaseV4&  that_;
+    IDatabaseListener&        listener_;
+    void*                     transaction_;
+
+    void CheckSuccess(OrthancPluginErrorCode code) const
+    {
+      that_.CheckSuccess(code);
+    }
+    
+  public:
+    Transaction(OrthancPluginDatabaseV4& that,
+                IDatabaseListener& listener,
+                TransactionType type) :
+      that_(that),
+      listener_(listener)
+    {
+#if 0
+      CheckSuccess(that.database_.startTransaction(that.database_, &transaction_, type));
+      if (transaction_ == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+#endif
+    }
+
+    
+    virtual ~Transaction()
+    {
+#if 0
+      OrthancPluginErrorCode code = that_.database_.destructTransaction(transaction_);
+      if (code != OrthancPluginErrorCode_Success)
+      {
+        // Don't throw exception in destructors
+        that_.errorDictionary_.LogError(code, true);
+      }
+#endif
+    }
+    
+
+    virtual void Rollback() ORTHANC_OVERRIDE
+    {
+    }
+    
+
+    virtual void Commit(int64_t fileSizeDelta) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment,
+                               int64_t revision) ORTHANC_OVERRIDE
+    {
+    }
+
+
+    virtual void ClearChanges() ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void ClearExportedResources() ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType,
+                                 size_t since,
+                                 size_t limit) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+    }
+
+
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetMainDicomTags(DicomMap& target,
+                                  int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
+                                          int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t& revision,
+                                  int64_t id,
+                                  FileContentType contentType) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property,
+                                      bool shared) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t& revision,
+                                int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   bool shared,
+                                   const std::string& value) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value,
+                             int64_t revision) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) ORTHANC_OVERRIDE
+    {
+    }
+
+
+    virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                      std::list<std::string>* instancesId, // Can be NULL if not needed
+                                      const std::vector<DatabaseConstraint>& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool CreateInstance(CreateInstanceResult& result, /* out */
+                                int64_t& instanceId,          /* out */
+                                const std::string& patient,
+                                const std::string& study,
+                                const std::string& series,
+                                const std::string& instance) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void SetResourcesContent(const ResourcesContent& content) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual void GetChildrenMetadata(std::list<std::string>& target,
+                                     int64_t resourceId,
+                                     MetadataType metadata) ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
+    {
+    }
+
+    
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId) ORTHANC_OVERRIDE
+    {
+    }
+  };
+
+
+  static void CheckSuccess(PluginsErrorDictionary& errorDictionary,
+                           OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      errorDictionary.LogError(code, true);
+      throw OrthancException(static_cast<ErrorCode>(code));
+    }
+  }
+
+
+  static void Execute(DatabasePluginMessages::Response& response,
+                      const _OrthancPluginRegisterDatabaseBackendV4& database,
+                      PluginsErrorDictionary& errorDictionary,
+                      const DatabasePluginMessages::Request& request)
+  {
+    std::string requestSerialized;
+    request.SerializeToString(&requestSerialized);
+
+    OrthancPluginMemoryBuffer64 responseSerialized;
+    CheckSuccess(errorDictionary, database.operations(
+                   &responseSerialized, database.backend,
+                   requestSerialized.empty() ? NULL : requestSerialized.c_str(),
+                   requestSerialized.size()));
+
+    bool success = response.ParseFromArray(responseSerialized.data, responseSerialized.size);
+
+    if (responseSerialized.size > 0)
+    {
+      free(responseSerialized.data);
+    }
+
+    if (!success)
+    {
+      throw OrthancException(ErrorCode_DatabasePlugin, "Cannot unserialize protobuf originating from the database plugin");
+    }
+  }
+
+  static void ExecuteDatabase(DatabasePluginMessages::DatabaseResponse& response,
+                              const _OrthancPluginRegisterDatabaseBackendV4& database,
+                              PluginsErrorDictionary& errorDictionary,
+                              DatabasePluginMessages::DatabaseOperation operation,
+                              const DatabasePluginMessages::DatabaseRequest& request)
+  {
+    DatabasePluginMessages::Request fullRequest;
+    fullRequest.set_type(DatabasePluginMessages::REQUEST_DATABASE);
+    fullRequest.mutable_database_request()->CopyFrom(request);
+    fullRequest.mutable_database_request()->set_operation(operation);
+
+    DatabasePluginMessages::Response fullResponse;
+    Execute(fullResponse, database, errorDictionary, fullRequest);
+    
+    response.CopyFrom(fullResponse.database_response());
+  }
+
+
+  OrthancPluginDatabaseV4::OrthancPluginDatabaseV4(SharedLibrary& library,
+                                                   PluginsErrorDictionary&  errorDictionary,
+                                                   const _OrthancPluginRegisterDatabaseBackendV4& database,
+                                                   const std::string& serverIdentifier) :
+    library_(library),
+    errorDictionary_(errorDictionary),
+    database_(database),
+    serverIdentifier_(serverIdentifier),
+    open_(false),
+    databaseVersion_(0),
+    hasFlushToDisk_(false),
+    hasRevisionsSupport_(false)
+  {
+    CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties "
+                        << "of the custom database: \"" << serverIdentifier << "\"";
+
+    if (database_.backend == NULL ||
+        database_.operations == NULL ||
+        database_.finalize == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+  }
+
+  
+  OrthancPluginDatabaseV4::~OrthancPluginDatabaseV4()
+  {
+    database_.finalize(database_.backend);
+  }
+
+  
+  void OrthancPluginDatabaseV4::Open()
+  {
+    if (open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    
+    {
+      DatabasePluginMessages::DatabaseRequest request;
+      DatabasePluginMessages::DatabaseResponse response;
+      ExecuteDatabase(response, database_, errorDictionary_, DatabasePluginMessages::OPERATION_OPEN, request);
+    }
+
+    {
+      DatabasePluginMessages::DatabaseRequest request;
+      DatabasePluginMessages::DatabaseResponse response;
+      ExecuteDatabase(response, database_, errorDictionary_, DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION, request);
+      hasFlushToDisk_ = response.get_system_information().supports_flush_to_disk();
+      hasRevisionsSupport_ = response.get_system_information().supports_revisions();
+    }
+
+    open_ = true;    
+  }
+
+
+  void OrthancPluginDatabaseV4::Close()
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      DatabasePluginMessages::DatabaseRequest request;
+      DatabasePluginMessages::DatabaseResponse response;
+      ExecuteDatabase(response, database_, errorDictionary_, DatabasePluginMessages::OPERATION_CLOSE, request);
+    }
+  }
+  
+
+  bool OrthancPluginDatabaseV4::HasFlushToDisk() const
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return hasFlushToDisk_;
+    }
+  }
+
+
+  void OrthancPluginDatabaseV4::FlushToDisk()
+  {
+    if (!open_ ||
+        !hasFlushToDisk_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      DatabasePluginMessages::DatabaseRequest request;
+      DatabasePluginMessages::DatabaseResponse response;
+      ExecuteDatabase(response, database_, errorDictionary_, DatabasePluginMessages::OPERATION_FLUSH_TO_DISK, request);
+    }
+  }
+  
+
+  IDatabaseWrapper::ITransaction* OrthancPluginDatabaseV4::StartTransaction(TransactionType type,
+                                                                            IDatabaseListener& listener)
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    switch (type)
+    {
+      case TransactionType_ReadOnly:
+
+      case TransactionType_ReadWrite:
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+  
+  unsigned int OrthancPluginDatabaseV4::GetDatabaseVersion()
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return databaseVersion_;
+    }
+  }
+
+  
+  void OrthancPluginDatabaseV4::Upgrade(unsigned int targetVersion,
+                                        IStorageArea& storageArea)
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      // TODO
+    }
+  }
+
+  
+  bool OrthancPluginDatabaseV4::HasRevisionsSupport() const
+  {
+    if (!open_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return hasRevisionsSupport_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h	Thu Mar 30 21:31:56 2023 +0200
@@ -0,0 +1,84 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, 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.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+
+#include "../../../OrthancFramework/Sources/SharedLibrary.h"
+#include "../../Sources/Database/IDatabaseWrapper.h"
+#include "../Include/orthanc/OrthancCPlugin.h"
+#include "PluginsErrorDictionary.h"
+
+namespace Orthanc
+{
+  class OrthancPluginDatabaseV4 : public IDatabaseWrapper
+  {
+  private:
+    class Transaction;
+
+    SharedLibrary&                          library_;
+    PluginsErrorDictionary&                 errorDictionary_;
+    _OrthancPluginRegisterDatabaseBackendV4 database_;
+    std::string                             serverIdentifier_;
+    bool                                    open_;
+    unsigned int                            databaseVersion_;
+    bool                                    hasFlushToDisk_;
+    bool                                    hasRevisionsSupport_;
+
+    void CheckSuccess(OrthancPluginErrorCode code) const;
+
+  public:
+    OrthancPluginDatabaseV4(SharedLibrary& library,
+                            PluginsErrorDictionary& errorDictionary,
+                            const _OrthancPluginRegisterDatabaseBackendV4& database,
+                            const std::string& serverIdentifier);
+
+    virtual ~OrthancPluginDatabaseV4();
+
+    virtual void Open() ORTHANC_OVERRIDE;
+
+    virtual void Close() ORTHANC_OVERRIDE;
+
+    const SharedLibrary& GetSharedLibrary() const
+    {
+      return library_;
+    }
+
+    virtual void FlushToDisk() ORTHANC_OVERRIDE;
+
+    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE;
+
+    virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
+                                                             IDatabaseListener& listener)
+      ORTHANC_OVERRIDE;
+
+    virtual unsigned int GetDatabaseVersion() ORTHANC_OVERRIDE;
+
+    virtual void Upgrade(unsigned int targetVersion,
+                         IStorageArea& storageArea) ORTHANC_OVERRIDE;    
+
+    virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE;
+  };
+}
+
+#endif
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Mar 30 17:08:13 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Mar 30 21:31:56 2023 +0200
@@ -63,6 +63,7 @@
 #include "../../Sources/ServerToolbox.h"
 #include "OrthancPluginDatabase.h"
 #include "OrthancPluginDatabaseV3.h"
+#include "OrthancPluginDatabaseV4.h"
 #include "PluginsEnumerations.h"
 #include "PluginsJob.h"
 
@@ -1544,8 +1545,9 @@
     Properties properties_;
     int argc_;
     char** argv_;
-    std::unique_ptr<OrthancPluginDatabase>  database_;
+    std::unique_ptr<OrthancPluginDatabase>    database_;
     std::unique_ptr<OrthancPluginDatabaseV3>  databaseV3_;  // New in Orthanc 1.9.2
+    std::unique_ptr<OrthancPluginDatabaseV4>  databaseV4_;  // New in Orthanc 1.12.0
     PluginsErrorDictionary  dictionary_;
     std::string databaseServerIdentifier_;   // New in Orthanc 1.9.2
     unsigned int maxDatabaseRetries_;   // New in Orthanc 1.9.2
@@ -5487,14 +5489,15 @@
 
       case _OrthancPluginService_RegisterDatabaseBackend:
       {
-        LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API";
+        LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 1)";
         LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
 
         const _OrthancPluginRegisterDatabaseBackend& p =
           *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackend*>(parameters);
 
         if (pimpl_->database_.get() == NULL &&
-            pimpl_->databaseV3_.get() == NULL)
+            pimpl_->databaseV3_.get() == NULL &&
+            pimpl_->databaseV4_.get() == NULL)
         {
           pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(), 
                                                             *p.backend, NULL, 0, p.payload));
@@ -5511,14 +5514,15 @@
 
       case _OrthancPluginService_RegisterDatabaseBackendV2:
       {
-        LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API";
+        LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 2)";
         LOG(WARNING) << "The database backend has *no* support for revisions of metadata and attachments";
 
         const _OrthancPluginRegisterDatabaseBackendV2& p =
           *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV2*>(parameters);
 
         if (pimpl_->database_.get() == NULL &&
-            pimpl_->databaseV3_.get() == NULL)
+            pimpl_->databaseV3_.get() == NULL &&
+            pimpl_->databaseV4_.get() == NULL)
         {
           pimpl_->database_.reset(new OrthancPluginDatabase(plugin, GetErrorDictionary(),
                                                             *p.backend, p.extensions,
@@ -5536,13 +5540,14 @@
 
       case _OrthancPluginService_RegisterDatabaseBackendV3:
       {
-        CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end";
+        LOG(WARNING) << "Performance warning: Plugin has registered a custom database back-end with an old API (version 3)";
 
         const _OrthancPluginRegisterDatabaseBackendV3& p =
           *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV3*>(parameters);
 
         if (pimpl_->database_.get() == NULL &&
-            pimpl_->databaseV3_.get() == NULL)
+            pimpl_->databaseV3_.get() == NULL &&
+            pimpl_->databaseV4_.get() == NULL)
         {
           pimpl_->databaseV3_.reset(new OrthancPluginDatabaseV3(plugin, GetErrorDictionary(), p.backend,
                                                                 p.backendSize, p.database, pimpl_->databaseServerIdentifier_));
@@ -5556,6 +5561,28 @@
         return true;
       }
 
+      case _OrthancPluginService_RegisterDatabaseBackendV4:
+      {
+        CLOG(INFO, PLUGINS) << "Plugin has registered a custom database back-end";
+
+        const _OrthancPluginRegisterDatabaseBackendV4& p =
+          *reinterpret_cast<const _OrthancPluginRegisterDatabaseBackendV4*>(parameters);
+
+        if (pimpl_->database_.get() == NULL &&
+            pimpl_->databaseV3_.get() == NULL &&
+            pimpl_->databaseV4_.get() == NULL)
+        {
+          pimpl_->databaseV4_.reset(new OrthancPluginDatabaseV4(plugin, GetErrorDictionary(), p, pimpl_->databaseServerIdentifier_));
+          pimpl_->maxDatabaseRetries_ = p.maxDatabaseRetries;
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_DatabaseBackendAlreadyRegistered);
+        }
+
+        return true;
+      }
+
       case _OrthancPluginService_DatabaseAnswer:
         throw OrthancException(ErrorCode_InternalError);   // Implemented before locking (*)
 
@@ -5695,7 +5722,8 @@
   {
     boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_);
     return (pimpl_->database_.get() != NULL ||
-            pimpl_->databaseV3_.get() != NULL);
+            pimpl_->databaseV3_.get() != NULL ||
+            pimpl_->databaseV4_.get() != NULL);
   }
 
 
@@ -5735,6 +5763,10 @@
     {
       return *pimpl_->databaseV3_;
     }
+    else if (pimpl_->databaseV4_.get() != NULL)
+    {
+      return *pimpl_->databaseV4_;
+    }
     else
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
@@ -5752,6 +5784,10 @@
     {
       return pimpl_->databaseV3_->GetSharedLibrary();
     }
+    else if (pimpl_->databaseV4_.get() != NULL)
+    {
+      return pimpl_->databaseV4_->GetSharedLibrary();
+    }
     else
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Thu Mar 30 17:08:13 2023 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Thu Mar 30 21:31:56 2023 +0200
@@ -31,6 +31,14 @@
 
 syntax = "proto3";
 
+/**
+ * Turn off protobuf reflection to avoid clashes between the Orthanc
+ * core and the database plugin, otherwise both will try to register
+ * the same messages in the process-wide descriptor pool, which would
+ * result in protobuf error "File already exists in database".
+ **/
+option optimize_for = LITE_RUNTIME;
+
 package Orthanc.DatabasePluginMessages;
 
 
--- a/OrthancServer/Sources/OrthancInitialization.cpp	Thu Mar 30 17:08:13 2023 +0200
+++ b/OrthancServer/Sources/OrthancInitialization.cpp	Thu Mar 30 21:31:56 2023 +0200
@@ -51,6 +51,10 @@
 
 #include <dcmtk/dcmnet/diutil.h>  // For DCM_dcmnetLogger
 
+#if ORTHANC_ENABLE_PLUGINS == 1
+#  include <google/protobuf/any.h>
+#endif
+
 
 static const char* const STORAGE_DIRECTORY = "StorageDirectory";
 static const char* const ORTHANC_STORAGE = "OrthancStorage";
@@ -315,6 +319,10 @@
     
     OrthancConfiguration::WriterLock lock;
 
+#if ORTHANC_ENABLE_PLUGINS == 1
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+#endif
+    
     InitializeServerEnumerations();
 
     // Read the user-provided configuration
@@ -391,6 +399,10 @@
   {
     OrthancConfiguration::WriterLock lock;
     Orthanc::FinalizeFramework();
+
+#if ORTHANC_ENABLE_PLUGINS == 1
+    google::protobuf::ShutdownProtobufLibrary();
+#endif
   }