# HG changeset patch # User Sebastien Jodogne # Date 1680534728 -7200 # Node ID f35b17a383018aedf4b259c528f56e3d3a379d5b # Parent c1fe28de1bf650d12cc56b1769d68a010f297ca3# Parent 0fed785e974e61d315717e5f66fd813c95acf980 integration db-protobuf->mainline diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/DatabaseBackendAdapterV2.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -946,7 +946,9 @@ try { DatabaseBackendAdapterV2::Adapter::DatabaseAccessor accessor(*adapter); - adapter->GetBackend().LogExportedResource(accessor.GetManager(), *exported); + adapter->GetBackend().LogExportedResource(accessor.GetManager(), exported->resourceType, exported->publicId, + exported->modality, exported->date, exported->patientId, + exported->studyInstanceUid, exported->seriesInstanceUid, exported->sopInstanceUid); return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH; @@ -1632,17 +1634,21 @@ void DatabaseBackendAdapterV2::Register(IDatabaseBackend* backend) { - if (backend == NULL) { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } + std::unique_ptr protection(backend); + + if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } - if (adapter_.get() != NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + if (adapter_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + adapter_.reset(new Adapter(protection.release())); } - - adapter_.reset(new Adapter(backend)); OrthancPluginDatabaseBackend params; memset(¶ms, 0, sizeof(params)); diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/DatabaseBackendAdapterV3.cpp --- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -25,8 +25,9 @@ #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) +#include "IndexConnectionsPool.h" + #include -#include #include #include @@ -82,172 +83,6 @@ } - class DatabaseBackendAdapterV3::Adapter : public boost::noncopyable - { - private: - class ManagerReference : public Orthanc::IDynamicObject - { - private: - DatabaseManager* manager_; - - public: - ManagerReference(DatabaseManager& manager) : - manager_(&manager) - { - } - - DatabaseManager& GetManager() - { - assert(manager_ != NULL); - return *manager_; - } - }; - - std::unique_ptr backend_; - OrthancPluginContext* context_; - boost::shared_mutex connectionsMutex_; - size_t countConnections_; - std::list connections_; - Orthanc::SharedMessageQueue availableConnections_; - - public: - Adapter(IndexBackend* backend, - size_t countConnections) : - backend_(backend), - countConnections_(countConnections) - { - if (countConnections == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "There must be a non-zero number of connections to the database"); - } - else if (backend == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - context_ = backend_->GetContext(); - } - } - - ~Adapter() - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - } - - OrthancPluginContext* GetContext() const - { - return context_; - } - - void OpenConnections() - { - boost::unique_lock lock(connectionsMutex_); - - if (connections_.size() == 0) - { - assert(backend_.get() != NULL); - - { - std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); - manager->GetDatabase(); // Make sure to open the database connection - - backend_->ConfigureDatabase(*manager); - connections_.push_back(manager.release()); - } - - for (size_t i = 1; i < countConnections_; i++) - { - connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); - connections_.back()->GetDatabase(); // Make sure to open the database connection - } - - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - availableConnections_.Enqueue(new ManagerReference(**it)); - } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - void CloseConnections() - { - boost::unique_lock lock(connectionsMutex_); - - if (connections_.size() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else if (availableConnections_.GetSize() != countConnections_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); - } - else - { - for (std::list::iterator - it = connections_.begin(); it != connections_.end(); ++it) - { - assert(*it != NULL); - (*it)->Close(); - } - } - } - - class DatabaseAccessor : public boost::noncopyable - { - private: - boost::shared_lock lock_; - Adapter& adapter_; - DatabaseManager* manager_; - - public: - DatabaseAccessor(Adapter& adapter) : - lock_(adapter.connectionsMutex_), - adapter_(adapter), - manager_(NULL) - { - for (;;) - { - std::unique_ptr manager(adapter.availableConnections_.Dequeue(100)); - if (manager.get() != NULL) - { - manager_ = &dynamic_cast(*manager).GetManager(); - return; - } - } - } - - ~DatabaseAccessor() - { - assert(manager_ != NULL); - adapter_.availableConnections_.Enqueue(new ManagerReference(*manager_)); - } - - IndexBackend& GetBackend() const - { - return *adapter_.backend_; - } - - DatabaseManager& GetManager() const - { - assert(manager_ != NULL); - return *manager_; - } - }; - }; - - class DatabaseBackendAdapterV3::Output : public IDatabaseBackendOutput { private: @@ -802,14 +637,14 @@ class DatabaseBackendAdapterV3::Transaction : public boost::noncopyable { private: - Adapter& adapter_; - std::unique_ptr accessor_; - std::unique_ptr output_; + IndexConnectionsPool& pool_; + std::unique_ptr accessor_; + std::unique_ptr output_; public: - Transaction(Adapter& adapter) : - adapter_(adapter), - accessor_(new Adapter::DatabaseAccessor(adapter)), + Transaction(IndexConnectionsPool& pool) : + pool_(pool), + accessor_(new IndexConnectionsPool::Accessor(pool)), output_(new Output) { } @@ -961,35 +796,35 @@ static OrthancPluginErrorCode Open(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - adapter->OpenConnections(); + pool->OpenConnections(); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode Close(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - adapter->CloseConnections(); + pool->CloseConnections(); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode DestructDatabase(void* database) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); - if (adapter == NULL) + if (pool == NULL) { return OrthancPluginErrorCode_InternalError; } @@ -1001,10 +836,10 @@ } else { - OrthancPluginLogError(adapter->GetContext(), "More than one index backend was registered, internal error"); + OrthancPluginLogError(pool->GetContext(), "More than one index backend was registered, internal error"); } - delete adapter; + delete pool; return OrthancPluginErrorCode_Success; } @@ -1014,15 +849,15 @@ static OrthancPluginErrorCode GetDatabaseVersion(void* database, uint32_t* version) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); *version = accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1030,30 +865,30 @@ OrthancPluginStorageArea* storageArea, uint32_t targetVersion) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), targetVersion, storageArea); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } static OrthancPluginErrorCode HasRevisionsSupport(void* database, uint8_t* target) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter); + IndexConnectionsPool::Accessor accessor(*pool); *target = (accessor.GetBackend().HasRevisionsSupport() ? 1 : 0); return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1061,11 +896,11 @@ OrthancPluginDatabaseTransaction** target /* out */, OrthancPluginDatabaseTransactionType type) { - DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast(database); + IndexConnectionsPool* pool = reinterpret_cast(database); try { - std::unique_ptr transaction(new DatabaseBackendAdapterV3::Transaction(*adapter)); + std::unique_ptr transaction(new DatabaseBackendAdapterV3::Transaction(*pool)); switch (type) { @@ -1085,7 +920,7 @@ return OrthancPluginErrorCode_Success; } - ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext()); + ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext()); } @@ -1666,19 +1501,9 @@ try { - OrthancPluginExportedResource exported; - exported.seq = 0; - exported.resourceType = resourceType; - exported.publicId = publicId; - exported.modality = modality; - exported.date = date; - exported.patientId = patientId; - exported.studyInstanceUid = studyInstanceUid; - exported.seriesInstanceUid = seriesInstanceUid; - exported.sopInstanceUid = sopInstanceUid; - t->GetOutput().Clear(); - t->GetBackend().LogExportedResource(t->GetManager(), exported); + t->GetBackend().LogExportedResource(t->GetManager(), resourceType, publicId, modality, date, + patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); return OrthancPluginErrorCode_Success; } ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext()); @@ -1988,6 +1813,8 @@ size_t countConnections, unsigned int maxDatabaseRetries) { + std::unique_ptr protection(backend); + if (isBackendInUse_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); @@ -2071,12 +1898,13 @@ params.setProtectedPatient = SetProtectedPatient; params.setResourcesContent = SetResourcesContent; - OrthancPluginContext* context = backend->GetContext(); + OrthancPluginContext* context = protection->GetContext(); if (OrthancPluginRegisterDatabaseBackendV3( context, ¶ms, sizeof(params), maxDatabaseRetries, - new Adapter(backend, countConnections)) != OrthancPluginErrorCode_Success) + new IndexConnectionsPool(protection.release(), countConnections)) != OrthancPluginErrorCode_Success) { + delete backend; throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend"); } diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/DatabaseBackendAdapterV3.h --- a/Framework/Plugins/DatabaseBackendAdapterV3.h Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/DatabaseBackendAdapterV3.h Mon Apr 03 17:12:08 2023 +0200 @@ -49,7 +49,6 @@ } public: - class Adapter; class Transaction; class Factory : public IDatabaseBackendOutput::IFactory diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/DatabaseBackendAdapterV4.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -0,0 +1,1301 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "DatabaseBackendAdapterV4.h" + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + +#include "IndexConnectionsPool.h" + +#include // Include protobuf messages + +#include +#include + +#include +#include +#include +#include + + +#define ORTHANC_PLUGINS_DATABASE_CATCH(context) \ + + +namespace OrthancDatabases +{ + static bool isBackendInUse_ = false; // Only for sanity checks + + + static Orthanc::DatabasePluginMessages::ResourceType Convert(OrthancPluginResourceType resourceType) + { + switch (resourceType) + { + case OrthancPluginResourceType_Patient: + return Orthanc::DatabasePluginMessages::RESOURCE_PATIENT; + + case OrthancPluginResourceType_Study: + return Orthanc::DatabasePluginMessages::RESOURCE_STUDY; + + case OrthancPluginResourceType_Series: + return Orthanc::DatabasePluginMessages::RESOURCE_SERIES; + + case OrthancPluginResourceType_Instance: + return Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + static OrthancPluginResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType) + { + switch (resourceType) + { + case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT: + return OrthancPluginResourceType_Patient; + + case Orthanc::DatabasePluginMessages::RESOURCE_STUDY: + return OrthancPluginResourceType_Study; + + case Orthanc::DatabasePluginMessages::RESOURCE_SERIES: + return OrthancPluginResourceType_Series; + + case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE: + return OrthancPluginResourceType_Instance; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + class Output : public IDatabaseBackendOutput + { + private: + Orthanc::DatabasePluginMessages::DeleteAttachment::Response* deleteAttachment_; + Orthanc::DatabasePluginMessages::DeleteResource::Response* deleteResource_; + Orthanc::DatabasePluginMessages::GetChanges::Response* getChanges_; + Orthanc::DatabasePluginMessages::GetExportedResources::Response* getExportedResources_; + Orthanc::DatabasePluginMessages::GetLastChange::Response* getLastChange_; + Orthanc::DatabasePluginMessages::GetLastExportedResource::Response* getLastExportedResource_; + Orthanc::DatabasePluginMessages::GetMainDicomTags::Response* getMainDicomTags_; + Orthanc::DatabasePluginMessages::LookupAttachment::Response* lookupAttachment_; + Orthanc::DatabasePluginMessages::LookupResources::Response* lookupResources_; + + void Clear() + { + deleteAttachment_ = NULL; + deleteResource_ = NULL; + getChanges_ = NULL; + getExportedResources_ = NULL; + getLastChange_ = NULL; + getLastExportedResource_ = NULL; + getMainDicomTags_ = NULL; + lookupAttachment_ = NULL; + lookupResources_ = NULL; + } + + public: + Output(Orthanc::DatabasePluginMessages::DeleteAttachment::Response& deleteAttachment) + { + Clear(); + deleteAttachment_ = &deleteAttachment; + } + + Output(Orthanc::DatabasePluginMessages::DeleteResource::Response& deleteResource) + { + Clear(); + deleteResource_ = &deleteResource; + } + + Output(Orthanc::DatabasePluginMessages::GetChanges::Response& getChanges) + { + Clear(); + getChanges_ = &getChanges; + } + + Output(Orthanc::DatabasePluginMessages::GetExportedResources::Response& getExportedResources) + { + Clear(); + getExportedResources_ = &getExportedResources; + } + + Output(Orthanc::DatabasePluginMessages::GetLastChange::Response& getLastChange) + { + Clear(); + getLastChange_ = &getLastChange; + } + + Output(Orthanc::DatabasePluginMessages::GetLastExportedResource::Response& getLastExportedResource) + { + Clear(); + getLastExportedResource_ = &getLastExportedResource; + } + + Output(Orthanc::DatabasePluginMessages::GetMainDicomTags::Response& getMainDicomTags) + { + Clear(); + getMainDicomTags_ = &getMainDicomTags; + } + + Output(Orthanc::DatabasePluginMessages::LookupAttachment::Response& lookupAttachment) + { + Clear(); + lookupAttachment_ = &lookupAttachment; + } + + Output(Orthanc::DatabasePluginMessages::LookupResources::Response& lookupResources) + { + Clear(); + lookupResources_ = &lookupResources; + } + + virtual 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) ORTHANC_OVERRIDE + { + Orthanc::DatabasePluginMessages::FileInfo* attachment; + + if (deleteAttachment_ != NULL) + { + if (deleteAttachment_->has_deleted_attachment()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + attachment = deleteAttachment_->mutable_deleted_attachment(); + } + else if (deleteResource_ != NULL) + { + attachment = deleteResource_->add_deleted_attachments(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + attachment->set_uuid(uuid); + attachment->set_content_type(contentType); + attachment->set_uncompressed_size(uncompressedSize); + attachment->set_uncompressed_hash(uncompressedHash); + attachment->set_compression_type(compressionType); + attachment->set_compressed_size(compressedSize); + attachment->set_compressed_hash(compressedHash); + } + + virtual void SignalDeletedResource(const std::string& publicId, + OrthancPluginResourceType resourceType) ORTHANC_OVERRIDE + { + if (deleteResource_ != NULL) + { + Orthanc::DatabasePluginMessages::DeleteResource_Response_Resource* resource = deleteResource_->add_deleted_resources(); + resource->set_level(Convert(resourceType)); + resource->set_public_id(publicId); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + virtual void SignalRemainingAncestor(const std::string& ancestorId, + OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE + { + if (deleteResource_ != NULL) + { + if (deleteResource_->is_remaining_ancestor()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + deleteResource_->set_is_remaining_ancestor(true); + deleteResource_->mutable_remaining_ancestor()->set_level(Convert(ancestorType)); + deleteResource_->mutable_remaining_ancestor()->set_public_id(ancestorId); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + virtual 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) ORTHANC_OVERRIDE + { + if (lookupAttachment_ != NULL) + { + if (lookupAttachment_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + lookupAttachment_->set_found(true); + lookupAttachment_->mutable_attachment()->set_uuid(uuid); + lookupAttachment_->mutable_attachment()->set_content_type(contentType); + lookupAttachment_->mutable_attachment()->set_uncompressed_size(uncompressedSize); + lookupAttachment_->mutable_attachment()->set_uncompressed_hash(uncompressedHash); + lookupAttachment_->mutable_attachment()->set_compression_type(compressionType); + lookupAttachment_->mutable_attachment()->set_compressed_size(compressedSize); + lookupAttachment_->mutable_attachment()->set_compressed_hash(compressedHash); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + virtual void AnswerChange(int64_t seq, + int32_t changeType, + OrthancPluginResourceType resourceType, + const std::string& publicId, + const std::string& date) ORTHANC_OVERRIDE + { + Orthanc::DatabasePluginMessages::ServerIndexChange* change; + + if (getChanges_ != NULL) + { + change = getChanges_->add_changes(); + } + else if (getLastChange_ != NULL) + { + if (getLastChange_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + getLastChange_->set_found(true); + change = getLastChange_->mutable_change(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + change->set_seq(seq); + change->set_change_type(changeType); + change->set_resource_type(Convert(resourceType)); + change->set_public_id(publicId); + change->set_date(date); + } + + virtual void AnswerDicomTag(uint16_t group, + uint16_t element, + const std::string& value) ORTHANC_OVERRIDE + { + if (getMainDicomTags_ != NULL) + { + Orthanc::DatabasePluginMessages::GetMainDicomTags_Response_Tag* tag = getMainDicomTags_->add_tags(); + tag->set_group(group); + tag->set_element(element); + tag->set_value(value); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + virtual 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) ORTHANC_OVERRIDE + { + Orthanc::DatabasePluginMessages::ExportedResource* resource; + + if (getExportedResources_ != NULL) + { + resource = getExportedResources_->add_resources(); + } + else if (getLastExportedResource_ != NULL) + { + if (getLastExportedResource_->found()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + getLastExportedResource_->set_found(true); + resource = getLastExportedResource_->mutable_resource(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + resource->set_seq(seq); + resource->set_resource_type(Convert(resourceType)); + resource->set_public_id(publicId); + resource->set_modality(modality); + resource->set_date(date); + resource->set_patient_id(patientId); + resource->set_study_instance_uid(studyInstanceUid); + resource->set_series_instance_uid(seriesInstanceUid); + resource->set_sop_instance_uid(sopInstanceUid); + } + + virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE + { + if (lookupResources_ != NULL) + { + lookupResources_->add_resources_ids(resourceId); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + virtual void AnswerMatchingResource(const std::string& resourceId, + const std::string& someInstanceId) ORTHANC_OVERRIDE + { + if (lookupResources_ != NULL) + { + lookupResources_->add_resources_ids(resourceId); + lookupResources_->add_instances_ids(someInstanceId); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + }; + + + static void ProcessDatabaseOperation(Orthanc::DatabasePluginMessages::DatabaseResponse& response, + const Orthanc::DatabasePluginMessages::DatabaseRequest& request, + IndexConnectionsPool& pool) + { + switch (request.operation()) + { + case Orthanc::DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION: + { + IndexConnectionsPool::Accessor accessor(pool); + response.mutable_get_system_information()->set_database_version(accessor.GetBackend().GetDatabaseVersion(accessor.GetManager())); + response.mutable_get_system_information()->set_supports_flush_to_disk(false); + response.mutable_get_system_information()->set_supports_revisions(accessor.GetBackend().HasRevisionsSupport()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_OPEN: + { + pool.OpenConnections(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLOSE: + { + pool.CloseConnections(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_FLUSH_TO_DISK: + { + // Raise an exception since "set_supports_flush_to_disk(false)" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + case Orthanc::DatabasePluginMessages::OPERATION_START_TRANSACTION: + { + std::unique_ptr transaction(new IndexConnectionsPool::Accessor(pool)); + + switch (request.start_transaction().type()) + { + case Orthanc::DatabasePluginMessages::TRANSACTION_READ_ONLY: + transaction->GetManager().StartTransaction(TransactionType_ReadOnly); + break; + + case Orthanc::DatabasePluginMessages::TRANSACTION_READ_WRITE: + transaction->GetManager().StartTransaction(TransactionType_ReadWrite); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + response.mutable_start_transaction()->set_transaction(reinterpret_cast(transaction.release())); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_UPGRADE: + { + IndexConnectionsPool::Accessor accessor(pool); + OrthancPluginStorageArea* storageArea = reinterpret_cast(request.upgrade().storage_area()); + accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), request.upgrade().target_version(), storageArea); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_FINALIZE_TRANSACTION: + { + IndexConnectionsPool::Accessor* transaction = reinterpret_cast(request.finalize_transaction().transaction()); + + if (transaction == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + delete transaction; + } + + break; + } + + default: + LOG(ERROR) << "Not implemented database operation from protobuf: " << request.operation(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + static void ApplyLookupResources(Orthanc::DatabasePluginMessages::LookupResources_Response& response, + const Orthanc::DatabasePluginMessages::LookupResources_Request& request, + IndexBackend& backend, + DatabaseManager& manager) + { + std::vector lookup; + lookup.reserve(request.lookup().size()); + + size_t countValues = 0; + + for (int i = 0; i < request.lookup().size(); i++) + { + const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i); + countValues += constraint.values().size(); + } + + std::vector values; + values.reserve(countValues); + + for (int i = 0; i < request.lookup().size(); i++) + { + const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i); + + if (constraint.tag_group() > 0xffffu || + constraint.tag_element() > 0xffffu) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancPluginDatabaseConstraint c; + c.level = Convert(constraint.level()); + c.tagGroup = constraint.tag_group(); + c.tagElement = constraint.tag_element(); + c.isIdentifierTag = (constraint.is_identifier_tag() ? 1 : 0); + c.isCaseSensitive = (constraint.is_case_sensitive() ? 1 : 0); + c.isMandatory = (constraint.is_mandatory() ? 1 : 0); + + switch (constraint.type()) + { + case Orthanc::DatabasePluginMessages::CONSTRAINT_EQUAL: + c.type = OrthancPluginConstraintType_Equal; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL: + c.type = OrthancPluginConstraintType_SmallerOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL: + c.type = OrthancPluginConstraintType_GreaterOrEqual; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_WILDCARD: + c.type = OrthancPluginConstraintType_Wildcard; + break; + + case Orthanc::DatabasePluginMessages::CONSTRAINT_LIST: + c.type = OrthancPluginConstraintType_List; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + c.valuesCount = constraint.values().size(); + + if (c.valuesCount == 0) + { + c.values = NULL; + } + else + { + c.values = &values[values.size()]; + + for (int j = 0; j < constraint.values().size(); j++) + { + assert(values.size() < countValues); + values.push_back(constraint.values(j).c_str()); + } + } + + lookup.push_back(Orthanc::DatabaseConstraint(c)); + } + + assert(values.size() == countValues); + + Output output(response); + backend.LookupResources(output, manager, lookup, Convert(request.query_level()), + request.limit(), request.retrieve_instances_ids()); + } + + + static void ProcessTransactionOperation(Orthanc::DatabasePluginMessages::TransactionResponse& response, + const Orthanc::DatabasePluginMessages::TransactionRequest& request, + IndexBackend& backend, + DatabaseManager& manager) + { + switch (request.operation()) + { + case Orthanc::DatabasePluginMessages::OPERATION_ROLLBACK: + { + manager.RollbackTransaction(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_COMMIT: + { + manager.CommitTransaction(); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_ADD_ATTACHMENT: + { + OrthancPluginAttachment attachment; + attachment.uuid = request.add_attachment().attachment().uuid().c_str(); + attachment.contentType = request.add_attachment().attachment().content_type(); + attachment.uncompressedSize = request.add_attachment().attachment().uncompressed_size(); + attachment.uncompressedHash = request.add_attachment().attachment().uncompressed_hash().c_str(); + attachment.compressionType = request.add_attachment().attachment().compression_type(); + attachment.compressedSize = request.add_attachment().attachment().compressed_size(); + attachment.compressedHash = request.add_attachment().attachment().compressed_hash().c_str(); + + backend.AddAttachment(manager, request.add_attachment().id(), attachment, request.add_attachment().revision()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_CHANGES: + { + backend.ClearChanges(manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES: + { + backend.ClearExportedResources(manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT: + { + Output output(*response.mutable_delete_attachment()); + backend.DeleteAttachment(output, manager, request.delete_attachment().id(), request.delete_attachment().type()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_METADATA: + { + backend.DeleteMetadata(manager, request.delete_metadata().id(), request.delete_metadata().type()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_DELETE_RESOURCE: + { + response.mutable_delete_resource()->set_is_remaining_ancestor(false); + + Output output(*response.mutable_delete_resource()); + backend.DeleteResource(output, manager, request.delete_resource().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_METADATA: + { + typedef std::map Values; + + Values values; + backend.GetAllMetadata(values, manager, request.get_all_metadata().id()); + + response.mutable_get_all_metadata()->mutable_metadata()->Reserve(values.size()); + for (Values::const_iterator it = values.begin(); it != values.end(); ++it) + { + Orthanc::DatabasePluginMessages::GetAllMetadata_Response_Metadata* metadata = + response.mutable_get_all_metadata()->add_metadata(); + metadata->set_type(it->first); + metadata->set_value(it->second); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS: + { + std::list values; + backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids().resource_type())); + + response.mutable_get_all_public_ids()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_all_public_ids()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS: + { + std::list values; + backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids_with_limits().resource_type()), + request.get_all_public_ids_with_limits().since(), + request.get_all_public_ids_with_limits().limit()); + + response.mutable_get_all_public_ids_with_limits()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_all_public_ids_with_limits()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES: + { + Output output(*response.mutable_get_changes()); + + bool done; + backend.GetChanges(output, done, manager, request.get_changes().since(), request.get_changes().limit()); + + response.mutable_get_changes()->set_done(done); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID: + { + std::list values; + backend.GetChildrenInternalId(values, manager, request.get_children_internal_id().id()); + + response.mutable_get_children_internal_id()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_internal_id()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_PUBLIC_ID: + { + std::list values; + backend.GetChildrenPublicId(values, manager, request.get_children_public_id().id()); + + response.mutable_get_children_public_id()->mutable_ids()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_public_id()->add_ids(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_EXPORTED_RESOURCES: + { + Output output(*response.mutable_get_exported_resources()); + + bool done; + backend.GetExportedResources(output, done, manager, request.get_exported_resources().since(), + request.get_exported_resources().limit()); + + response.mutable_get_exported_resources()->set_done(done); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE: + { + response.mutable_get_last_change()->set_found(false); + + Output output(*response.mutable_get_last_change()); + backend.GetLastChange(output, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_EXPORTED_RESOURCE: + { + response.mutable_get_last_exported_resource()->set_found(false); + + Output output(*response.mutable_get_last_exported_resource()); + backend.GetLastExportedResource(output, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_MAIN_DICOM_TAGS: + { + Output output(*response.mutable_get_main_dicom_tags()); + backend.GetMainDicomTags(output, manager, request.get_main_dicom_tags().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_PUBLIC_ID: + { + const std::string id = backend.GetPublicId(manager, request.get_public_id().id()); + response.mutable_get_public_id()->set_id(id); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCES_COUNT: + { + OrthancPluginResourceType type = Convert(request.get_resources_count().type()); + uint64_t count = backend.GetResourcesCount(manager, type); + response.mutable_get_resources_count()->set_count(count); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCE_TYPE: + { + OrthancPluginResourceType type = backend.GetResourceType(manager, request.get_resource_type().id()); + response.mutable_get_resource_type()->set_type(Convert(type)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_COMPRESSED_SIZE: + { + response.mutable_get_total_compressed_size()->set_size(backend.GetTotalCompressedSize(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE: + { + response.mutable_get_total_uncompressed_size()->set_size(backend.GetTotalUncompressedSize(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_IS_PROTECTED_PATIENT: + { + bool isProtected = backend.IsProtectedPatient(manager, request.is_protected_patient().patient_id()); + response.mutable_is_protected_patient()->set_protected_patient(isProtected); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LIST_AVAILABLE_ATTACHMENTS: + { + std::list values; + backend.ListAvailableAttachments(values, manager, request.list_available_attachments().id()); + + response.mutable_list_available_attachments()->mutable_attachments()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_list_available_attachments()->add_attachments(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOG_CHANGE: + { + backend.LogChange(manager, request.log_change().change_type(), + request.log_change().resource_id(), + Convert(request.log_change().resource_type()), + request.log_change().date().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOG_EXPORTED_RESOURCE: + { + backend.LogExportedResource(manager, + Convert(request.log_exported_resource().resource_type()), + request.log_exported_resource().public_id().c_str(), + request.log_exported_resource().modality().c_str(), + request.log_exported_resource().date().c_str(), + request.log_exported_resource().patient_id().c_str(), + request.log_exported_resource().study_instance_uid().c_str(), + request.log_exported_resource().series_instance_uid().c_str(), + request.log_exported_resource().sop_instance_uid().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_ATTACHMENT: + { + Output output(*response.mutable_lookup_attachment()); + + int64_t revision = -1; + backend.LookupAttachment(output, revision, manager, request.lookup_attachment().id(), request.lookup_attachment().content_type()); + + if (response.lookup_attachment().found()) + { + response.mutable_lookup_attachment()->set_revision(revision); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_GLOBAL_PROPERTY: + { + std::string value; + if (backend.LookupGlobalProperty(value, manager, request.lookup_global_property().server_id().c_str(), + request.lookup_global_property().property())) + { + response.mutable_lookup_global_property()->set_found(true); + response.mutable_lookup_global_property()->set_value(value); + } + else + { + response.mutable_lookup_global_property()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_METADATA: + { + std::string value; + int64_t revision = -1; + if (backend.LookupMetadata(value, revision, manager, request.lookup_metadata().id(), request.lookup_metadata().metadata_type())) + { + response.mutable_lookup_metadata()->set_found(true); + response.mutable_lookup_metadata()->set_value(value); + response.mutable_lookup_metadata()->set_revision(revision); + } + else + { + response.mutable_lookup_metadata()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_PARENT: + { + int64_t parent = -1; + if (backend.LookupParent(parent, manager, request.lookup_parent().id())) + { + response.mutable_lookup_parent()->set_found(true); + response.mutable_lookup_parent()->set_parent(parent); + } + else + { + response.mutable_lookup_parent()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE: + { + int64_t internalId = -1; + OrthancPluginResourceType type; + if (backend.LookupResource(internalId, type, manager, request.lookup_resource().public_id().c_str())) + { + response.mutable_lookup_resource()->set_found(true); + response.mutable_lookup_resource()->set_internal_id(internalId); + response.mutable_lookup_resource()->set_type(Convert(type)); + } + else + { + response.mutable_lookup_resource()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE: + { + int64_t patientId = -1; + if (backend.SelectPatientToRecycle(patientId, manager)) + { + response.mutable_select_patient_to_recycle()->set_found(true); + response.mutable_select_patient_to_recycle()->set_patient_id(patientId); + } + else + { + response.mutable_select_patient_to_recycle()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID: + { + int64_t patientId = -1; + if (backend.SelectPatientToRecycle(patientId, manager, request.select_patient_to_recycle_with_avoid().patient_id_to_avoid())) + { + response.mutable_select_patient_to_recycle_with_avoid()->set_found(true); + response.mutable_select_patient_to_recycle_with_avoid()->set_patient_id(patientId); + } + else + { + response.mutable_select_patient_to_recycle_with_avoid()->set_found(false); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_GLOBAL_PROPERTY: + { + backend.SetGlobalProperty(manager, request.set_global_property().server_id().c_str(), + request.set_global_property().property(), + request.set_global_property().value().c_str()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_MAIN_DICOM_TAGS: + { + backend.ClearMainDicomTags(manager, request.clear_main_dicom_tags().id()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_METADATA: + { + backend.SetMetadata(manager, request.set_metadata().id(), + request.set_metadata().metadata_type(), + request.set_metadata().value().c_str(), + request.set_metadata().revision()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_PROTECTED_PATIENT: + { + backend.SetProtectedPatient(manager, request.set_protected_patient().patient_id(), + request.set_protected_patient().protected_patient()); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_IS_DISK_SIZE_ABOVE: + { + bool above = (backend.GetTotalCompressedSize(manager) >= request.is_disk_size_above().threshold()); + response.mutable_is_disk_size_above()->set_result(above); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES: + { + ApplyLookupResources(*response.mutable_lookup_resources(), request.lookup_resources(), backend, manager); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_CREATE_INSTANCE: + { + const char* hashPatient = request.create_instance().patient().c_str(); + const char* hashStudy = request.create_instance().study().c_str(); + const char* hashSeries = request.create_instance().series().c_str(); + const char* hashInstance = request.create_instance().instance().c_str(); + + OrthancPluginCreateInstanceResult result; + + if (backend.HasCreateInstance()) + { + backend.CreateInstance(result, manager, hashPatient, hashStudy, hashSeries, hashInstance); + } + else + { + backend.CreateInstanceGeneric(result, manager, hashPatient, hashStudy, hashSeries, hashInstance); + } + + response.mutable_create_instance()->set_is_new_instance(result.isNewInstance); + response.mutable_create_instance()->set_instance_id(result.instanceId); + + if (result.isNewInstance) + { + response.mutable_create_instance()->set_is_new_patient(result.isNewPatient); + response.mutable_create_instance()->set_is_new_study(result.isNewStudy); + response.mutable_create_instance()->set_is_new_series(result.isNewSeries); + response.mutable_create_instance()->set_patient_id(result.patientId); + response.mutable_create_instance()->set_study_id(result.studyId); + response.mutable_create_instance()->set_series_id(result.seriesId); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_SET_RESOURCES_CONTENT: + { + std::vector identifierTags; + std::vector mainDicomTags; + + identifierTags.reserve(request.set_resources_content().tags().size()); + mainDicomTags.reserve(request.set_resources_content().tags().size()); + + for (int i = 0; i < request.set_resources_content().tags().size(); i++) + { + if (request.set_resources_content().tags(i).group() > 0xffffu || + request.set_resources_content().tags(i).element() > 0xffffu) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancPluginResourcesContentTags tag; + tag.resource = request.set_resources_content().tags(i).resource_id(); + tag.group = request.set_resources_content().tags(i).group(); + tag.element = request.set_resources_content().tags(i).element(); + tag.value = request.set_resources_content().tags(i).value().c_str(); + + if (request.set_resources_content().tags(i).is_identifier()) + { + identifierTags.push_back(tag); + } + else + { + mainDicomTags.push_back(tag); + } + } + + std::vector metadata; + metadata.reserve(request.set_resources_content().metadata().size()); + + for (int i = 0; i < request.set_resources_content().metadata().size(); i++) + { + OrthancPluginResourcesContentMetadata item; + item.resource = request.set_resources_content().metadata(i).resource_id(); + item.metadata = request.set_resources_content().metadata(i).metadata(); + item.value = request.set_resources_content().metadata(i).value().c_str(); + metadata.push_back(item); + } + + backend.SetResourcesContent(manager, + identifierTags.size(), (identifierTags.empty() ? NULL : &identifierTags[0]), + mainDicomTags.size(), (mainDicomTags.empty() ? NULL : &mainDicomTags[0]), + metadata.size(), (metadata.empty() ? NULL : &metadata[0])); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_METADATA: + { + std::list values; + backend.GetChildrenMetadata(values, manager, request.get_children_metadata().id(), request.get_children_metadata().metadata()); + + response.mutable_get_children_metadata()->mutable_values()->Reserve(values.size()); + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) + { + response.mutable_get_children_metadata()->add_values(*it); + } + + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE_INDEX: + { + response.mutable_get_last_change_index()->set_result(backend.GetLastChangeIndex(manager)); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE_AND_PARENT: + { + int64_t id; + OrthancPluginResourceType type; + std::string parent; + + if (backend.LookupResourceAndParent(id, type, parent, manager, request.lookup_resource_and_parent().public_id().c_str())) + { + response.mutable_lookup_resource_and_parent()->set_found(true); + response.mutable_lookup_resource_and_parent()->set_id(id); + response.mutable_lookup_resource_and_parent()->set_type(Convert(type)); + + switch (type) + { + case OrthancPluginResourceType_Study: + case OrthancPluginResourceType_Series: + case OrthancPluginResourceType_Instance: + if (parent.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + response.mutable_lookup_resource_and_parent()->set_parent_public_id(parent); + } + break; + + case OrthancPluginResourceType_Patient: + if (!parent.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + else + { + response.mutable_lookup_resource_and_parent()->set_found(false); + } + + break; + } + + default: + LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + static OrthancPluginErrorCode CallBackend(OrthancPluginMemoryBuffer64* serializedResponse, + void* rawPool, + const void* requestData, + uint64_t requestSize) + { + Orthanc::DatabasePluginMessages::Request request; + if (!request.ParseFromArray(requestData, requestSize)) + { + LOG(ERROR) << "Cannot parse message from the Orthanc core using protobuf"; + return OrthancPluginErrorCode_InternalError; + } + + if (rawPool == NULL) + { + LOG(ERROR) << "Received a NULL pointer from the database"; + return OrthancPluginErrorCode_InternalError; + } + + IndexConnectionsPool& pool = *reinterpret_cast(rawPool); + + try + { + Orthanc::DatabasePluginMessages::Response response; + + switch (request.type()) + { + case Orthanc::DatabasePluginMessages::REQUEST_DATABASE: + ProcessDatabaseOperation(*response.mutable_database_response(), request.database_request(), pool); + break; + + case Orthanc::DatabasePluginMessages::REQUEST_TRANSACTION: + { + IndexConnectionsPool::Accessor& transaction = *reinterpret_cast(request.transaction_request().transaction()); + ProcessTransactionOperation(*response.mutable_transaction_response(), request.transaction_request(), + transaction.GetBackend(), transaction.GetManager()); + break; + } + + default: + LOG(ERROR) << "Not implemented request type from protobuf: " << request.type(); + break; + } + + std::string s; + if (!response.SerializeToString(&s)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot serialize to protobuf"); + } + + if (OrthancPluginCreateMemoryBuffer64(pool.GetContext(), serializedResponse, s.size()) != OrthancPluginErrorCode_Success) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "Cannot allocate a memory buffer"); + } + + if (!s.empty()) + { + assert(serializedResponse->size == s.size()); + memcpy(serializedResponse->data, s.c_str(), s.size()); + } + + return OrthancPluginErrorCode_Success; + } + catch (::Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in database back-end: " << e.What(); + return static_cast(e.GetErrorCode()); + } + catch (::std::runtime_error& e) + { + LOG(ERROR) << "Exception in database back-end: " << e.what(); + return OrthancPluginErrorCode_DatabasePlugin; + } + catch (...) + { + LOG(ERROR) << "Native exception"; + return OrthancPluginErrorCode_DatabasePlugin; + } + } + + static void FinalizeBackend(void* rawPool) + { + if (rawPool != NULL) + { + IndexConnectionsPool* pool = reinterpret_cast(rawPool); + + if (isBackendInUse_) + { + isBackendInUse_ = false; + } + else + { + LOG(ERROR) << "More than one index backend was registered, internal error"; + } + + delete pool; + } + else + { + LOG(ERROR) << "Received a null pointer from the Orthanc core, internal error"; + } + } + + + void DatabaseBackendAdapterV4::Register(IndexBackend* backend, + size_t countConnections, + unsigned int maxDatabaseRetries) + { + std::unique_ptr pool(new IndexConnectionsPool(backend, countConnections)); + + if (isBackendInUse_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + OrthancPluginContext* context = backend->GetContext(); + + if (OrthancPluginRegisterDatabaseBackendV4(context, pool.release(), maxDatabaseRetries, + CallBackend, FinalizeBackend) != OrthancPluginErrorCode_Success) + { + delete backend; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend"); + } + + isBackendInUse_ = true; + } + + + void DatabaseBackendAdapterV4::Finalize() + { + if (isBackendInUse_) + { + LOG(ERROR) << "The Orthanc core has not destructed the index backend, internal error"; + } + } +} + +# endif +#endif diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/DatabaseBackendAdapterV4.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/DatabaseBackendAdapterV4.h Mon Apr 03 17:12:08 2023 +0200 @@ -0,0 +1,59 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + + +#pragma once + +#include "IndexBackend.h" + + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + +namespace OrthancDatabases +{ + /** + * @brief Bridge between C and C++ database engines. + * + * Class creating the bridge between the C low-level primitives for + * custom database engines, and the high-level IDatabaseBackend C++ + * interface, through ProtocolBuffers for Orthanc >= 1.12.0. + **/ + class DatabaseBackendAdapterV4 + { + private: + // This class cannot be instantiated + DatabaseBackendAdapterV4() + { + } + + public: + static void Register(IndexBackend* backend, + size_t countConnections, + unsigned int maxDatabaseRetries); + + static void Finalize(); + }; +} + +# endif +#endif diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IDatabaseBackend.h --- a/Framework/Plugins/IDatabaseBackend.h Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/IDatabaseBackend.h Mon Apr 03 17:12:08 2023 +0200 @@ -92,15 +92,15 @@ virtual void GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) = 0; + int64_t since, + uint32_t limit) = 0; /* Use GetOutput().AnswerChange() */ virtual void GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) = 0; + uint32_t limit) = 0; virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, @@ -115,7 +115,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) = 0; + uint32_t limit) = 0; /* Use GetOutput().AnswerChange() */ virtual void GetLastChange(IDatabaseBackendOutput& output, @@ -164,7 +164,14 @@ const char* date) = 0; virtual void LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) = 0; + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) = 0; /* Use GetOutput().AnswerAttachment() */ virtual bool LookupAttachment(IDatabaseBackendOutput& output, diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/IndexBackend.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -28,6 +28,7 @@ #include "../Common/Utf8StringValue.h" #include "DatabaseBackendAdapterV2.h" #include "DatabaseBackendAdapterV3.h" +#include "DatabaseBackendAdapterV4.h" #include "GlobalProperties.h" #include // For std::unique_ptr<> @@ -115,13 +116,13 @@ DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults) + uint32_t limit) { statement.Execute(args); uint32_t count = 0; - while (count < maxResults && + while (count < limit && !statement.IsDone()) { output.AnswerChange( @@ -135,7 +136,7 @@ count++; } - done = (count < maxResults || + done = (count < limit || statement.IsDone()); } @@ -144,13 +145,13 @@ bool& done, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults) + uint32_t limit) { statement.Execute(args); uint32_t count = 0; - while (count < maxResults && + while (count < limit && !statement.IsDone()) { int64_t seq = statement.ReadInteger64(0); @@ -172,7 +173,7 @@ count++; } - done = (count < maxResults || + done = (count < limit || statement.IsDone()); } @@ -518,8 +519,8 @@ void IndexBackend::GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) + int64_t since, + uint32_t limit) { std::string suffix; if (manager.GetDialect() == Dialect_MSSQL) @@ -555,7 +556,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) + uint32_t limit) { std::string suffix; if (manager.GetDialect() == Dialect_MSSQL) @@ -578,10 +579,10 @@ statement.SetParameterType("since", ValueType_Integer64); Dictionary args; - args.SetIntegerValue("limit", maxResults + 1); + args.SetIntegerValue("limit", limit + 1); args.SetIntegerValue("since", since); - ReadChangesInternal(output, done, manager, statement, args, maxResults); + ReadChangesInternal(output, done, manager, statement, args, limit); } @@ -628,7 +629,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) + uint32_t limit) { std::string suffix; if (manager.GetDialect() == Dialect_MSSQL) @@ -649,10 +650,10 @@ statement.SetParameterType("since", ValueType_Integer64); Dictionary args; - args.SetIntegerValue("limit", maxResults + 1); + args.SetIntegerValue("limit", limit + 1); args.SetIntegerValue("since", since); - ReadExportedResourcesInternal(output, done, statement, args, maxResults); + ReadExportedResourcesInternal(output, done, statement, args, limit); } @@ -1009,7 +1010,14 @@ void IndexBackend::LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) { DatabaseManager::CachedStatement statement( STATEMENT_FROM_HERE, manager, @@ -1026,14 +1034,14 @@ statement.SetParameterType("date", ValueType_Utf8String); Dictionary args; - args.SetIntegerValue("type", resource.resourceType); - args.SetUtf8Value("publicId", resource.publicId); - args.SetUtf8Value("modality", resource.modality); - args.SetUtf8Value("patient", resource.patientId); - args.SetUtf8Value("study", resource.studyInstanceUid); - args.SetUtf8Value("series", resource.seriesInstanceUid); - args.SetUtf8Value("instance", resource.sopInstanceUid); - args.SetUtf8Value("date", resource.date); + args.SetIntegerValue("type", resourceType); + args.SetUtf8Value("publicId", publicId); + args.SetUtf8Value("modality", modality); + args.SetUtf8Value("patient", patientId); + args.SetUtf8Value("study", studyInstanceUid); + args.SetUtf8Value("series", seriesInstanceUid); + args.SetUtf8Value("instance", sopInstanceUid); + args.SetUtf8Value("date", date); statement.Execute(args); } @@ -2610,26 +2618,31 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); } - bool hasLoadedV3 = false; + LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " + << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 12, 0) == 1) + { + OrthancDatabases::DatabaseBackendAdapterV4::Register(backend, countConnections, maxDatabaseRetries); + return; + } +# endif +#endif + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2) if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 9, 2) == 1) { - LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, " - << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision"; - OrthancDatabases::DatabaseBackendAdapterV3::Register(backend, countConnections, maxDatabaseRetries); - hasLoadedV3 = true; + return; } # endif #endif - if (!hasLoadedV3) - { - LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers"; - OrthancDatabases::DatabaseBackendAdapterV2::Register(backend); - } + LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers"; + OrthancDatabases::DatabaseBackendAdapterV2::Register(backend); } @@ -2679,6 +2692,12 @@ OrthancDatabases::DatabaseBackendAdapterV3::Finalize(); # endif #endif + +#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1 +# if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0) + OrthancDatabases::DatabaseBackendAdapterV4::Finalize(); +# endif +#endif } diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/IndexBackend.h Mon Apr 03 17:12:08 2023 +0200 @@ -62,13 +62,13 @@ DatabaseManager& manager, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults); + uint32_t limit); void ReadExportedResourcesInternal(IDatabaseBackendOutput& output, bool& done, DatabaseManager::CachedStatement& statement, const Dictionary& args, - uint32_t maxResults); + uint32_t limit); public: explicit IndexBackend(OrthancPluginContext* context); @@ -119,14 +119,14 @@ virtual void GetAllPublicIds(std::list& target, DatabaseManager& manager, OrthancPluginResourceType resourceType, - uint64_t since, - uint64_t limit) ORTHANC_OVERRIDE; + int64_t since, + uint32_t limit) ORTHANC_OVERRIDE; virtual void GetChanges(IDatabaseBackendOutput& output, bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) ORTHANC_OVERRIDE; + uint32_t limit) ORTHANC_OVERRIDE; virtual void GetChildrenInternalId(std::list& target /*out*/, DatabaseManager& manager, @@ -140,7 +140,7 @@ bool& done /*out*/, DatabaseManager& manager, int64_t since, - uint32_t maxResults) ORTHANC_OVERRIDE; + uint32_t limit) ORTHANC_OVERRIDE; virtual void GetLastChange(IDatabaseBackendOutput& output, DatabaseManager& manager) ORTHANC_OVERRIDE; @@ -186,7 +186,14 @@ const char* date) ORTHANC_OVERRIDE; virtual void LogExportedResource(DatabaseManager& manager, - const OrthancPluginExportedResource& resource) ORTHANC_OVERRIDE; + OrthancPluginResourceType resourceType, + const char* publicId, + const char* modality, + const char* date, + const char* patientId, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid) ORTHANC_OVERRIDE; virtual bool LookupAttachment(IDatabaseBackendOutput& output, int64_t& revision /*out*/, diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IndexConnectionsPool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IndexConnectionsPool.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -0,0 +1,173 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "IndexConnectionsPool.h" + +namespace OrthancDatabases +{ + class IndexConnectionsPool::ManagerReference : public Orthanc::IDynamicObject + { + private: + DatabaseManager* manager_; + + public: + ManagerReference(DatabaseManager& manager) : + manager_(&manager) + { + } + + DatabaseManager& GetManager() + { + assert(manager_ != NULL); + return *manager_; + } + }; + + + IndexConnectionsPool::IndexConnectionsPool(IndexBackend* backend, + size_t countConnections) : + backend_(backend), + countConnections_(countConnections) + { + if (countConnections == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "There must be a non-zero number of connections to the database"); + } + else if (backend == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + context_ = backend_->GetContext(); + } + } + + + IndexConnectionsPool::~IndexConnectionsPool() + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + } + + + void IndexConnectionsPool::OpenConnections() + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() == 0) + { + assert(backend_.get() != NULL); + + { + std::unique_ptr manager(new DatabaseManager(backend_->CreateDatabaseFactory())); + manager->GetDatabase(); // Make sure to open the database connection + + backend_->ConfigureDatabase(*manager); + connections_.push_back(manager.release()); + } + + for (size_t i = 1; i < countConnections_; i++) + { + connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory())); + connections_.back()->GetDatabase(); // Make sure to open the database connection + } + + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + availableConnections_.Enqueue(new ManagerReference(**it)); + } + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void IndexConnectionsPool::CloseConnections() + { + boost::unique_lock lock(connectionsMutex_); + + if (connections_.size() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else if (availableConnections_.GetSize() != countConnections_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core"); + } + else + { + for (std::list::iterator + it = connections_.begin(); it != connections_.end(); ++it) + { + assert(*it != NULL); + (*it)->Close(); + } + } + } + + + IndexConnectionsPool::Accessor::Accessor(IndexConnectionsPool& pool) : + lock_(pool.connectionsMutex_), + pool_(pool), + manager_(NULL) + { + for (;;) + { + std::unique_ptr manager(pool.availableConnections_.Dequeue(100)); + if (manager.get() != NULL) + { + manager_ = &dynamic_cast(*manager).GetManager(); + return; + } + } + } + + + IndexConnectionsPool::Accessor::~Accessor() + { + assert(manager_ != NULL); + pool_.availableConnections_.Enqueue(new ManagerReference(*manager_)); + } + + + IndexBackend& IndexConnectionsPool::Accessor::GetBackend() const + { + return *pool_.backend_; + } + + + DatabaseManager& IndexConnectionsPool::Accessor::GetManager() const + { + assert(manager_ != NULL); + return *manager_; + } +} diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IndexConnectionsPool.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/IndexConnectionsPool.h Mon Apr 03 17:12:08 2023 +0200 @@ -0,0 +1,77 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "IndexBackend.h" + +#include + +#include + +namespace OrthancDatabases +{ + class IndexConnectionsPool : public boost::noncopyable + { + private: + class ManagerReference; + + std::unique_ptr backend_; + OrthancPluginContext* context_; + boost::shared_mutex connectionsMutex_; + size_t countConnections_; + std::list connections_; + Orthanc::SharedMessageQueue availableConnections_; + + public: + IndexConnectionsPool(IndexBackend* backend /* takes ownership */, + size_t countConnections); + + ~IndexConnectionsPool(); + + OrthancPluginContext* GetContext() const + { + return context_; + } + + void OpenConnections(); + + void CloseConnections(); + + class Accessor : public boost::noncopyable + { + private: + boost::shared_lock lock_; + IndexConnectionsPool& pool_; + DatabaseManager* manager_; + + public: + Accessor(IndexConnectionsPool& pool); + + ~Accessor(); + + IndexBackend& GetBackend() const; + + DatabaseManager& GetManager() const; + }; + }; +} diff -r c1fe28de1bf6 -r f35b17a38301 Framework/Plugins/IndexUnitTests.h --- a/Framework/Plugins/IndexUnitTests.h Tue Mar 14 09:08:45 2023 +0100 +++ b/Framework/Plugins/IndexUnitTests.h Mon Apr 03 17:12:08 2023 +0200 @@ -510,20 +510,19 @@ ASSERT_EQ(0u, ci.size()); - OrthancPluginExportedResource exp; - exp.seq = -1; - exp.resourceType = OrthancPluginResourceType_Study; - exp.publicId = "id"; - exp.modality = "remote"; - exp.date = "date"; - exp.patientId = "patient"; - exp.studyInstanceUid = "study"; - exp.seriesInstanceUid = "series"; - exp.sopInstanceUid = "instance"; - db.LogExportedResource(*manager, exp); + db.LogExportedResource(*manager, OrthancPluginResourceType_Study, "id", "remote", "date", + "patient", "study", "series", "instance"); expectedExported.reset(new OrthancPluginExportedResource()); - *expectedExported = exp; + expectedExported->seq = -1; + expectedExported->resourceType = OrthancPluginResourceType_Study; + expectedExported->publicId = "id"; + expectedExported->modality = "remote"; + expectedExported->date = "date"; + expectedExported->patientId = "patient"; + expectedExported->studyInstanceUid = "study"; + expectedExported->seriesInstanceUid = "series"; + expectedExported->sopInstanceUid = "instance"; bool done; db.GetExportedResources(*output, done, *manager, 0, 10); diff -r c1fe28de1bf6 -r f35b17a38301 MySQL/CMakeLists.txt --- a/MySQL/CMakeLists.txt Tue Mar 14 09:08:45 2023 +0100 +++ b/MySQL/CMakeLists.txt Mon Apr 03 17:12:08 2023 +0200 @@ -24,14 +24,14 @@ set(ORTHANC_PLUGIN_VERSION "mainline") set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.0") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -81,6 +81,24 @@ MYSQL_CREATE_INSTANCE ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql ) +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + + add_custom_target( AutogeneratedTarget DEPENDS diff -r c1fe28de1bf6 -r f35b17a38301 MySQL/NEWS --- a/MySQL/NEWS Tue Mar 14 09:08:45 2023 +0100 +++ b/MySQL/NEWS Mon Apr 03 17:12:08 2023 +0200 @@ -1,6 +1,8 @@ Pending changes in the mainline =============================== +* Compatibility with Orthanc SDK 1.12.0 (communications between the + Orthanc core and the database plugin using Google Protocol Buffers) * Upgraded dependencies for static builds (notably on Windows and LSB): - openssl 3.0.1 diff -r c1fe28de1bf6 -r f35b17a38301 MySQL/Plugins/IndexPlugin.cpp --- a/MySQL/Plugins/IndexPlugin.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/MySQL/Plugins/IndexPlugin.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -28,11 +28,15 @@ #include #include +#include + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { + GOOGLE_PROTOBUF_VERIFY_VERSION; + if (!OrthancDatabases::InitializePlugin(context, "MySQL", true)) { return -1; @@ -93,6 +97,7 @@ OrthancDatabases::MySQLDatabase::GlobalFinalization(); Orthanc::HttpClient::GlobalFinalize(); Orthanc::Toolbox::FinalizeOpenSsl(); + google::protobuf::ShutdownProtobufLibrary(); } diff -r c1fe28de1bf6 -r f35b17a38301 PostgreSQL/CMakeLists.txt --- a/PostgreSQL/CMakeLists.txt Tue Mar 14 09:08:45 2023 +0100 +++ b/PostgreSQL/CMakeLists.txt Mon Apr 03 17:12:08 2023 +0200 @@ -24,14 +24,14 @@ set(ORTHANC_PLUGIN_VERSION "mainline") set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.0") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -84,6 +84,25 @@ POSTGRESQL_GET_LAST_CHANGE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/GetLastChangeIndex.sql ) + +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + + add_custom_target( AutogeneratedTarget DEPENDS diff -r c1fe28de1bf6 -r f35b17a38301 PostgreSQL/NEWS --- a/PostgreSQL/NEWS Tue Mar 14 09:08:45 2023 +0100 +++ b/PostgreSQL/NEWS Mon Apr 03 17:12:08 2023 +0200 @@ -1,6 +1,8 @@ Pending changes in the mainline =============================== +* Compatibility with Orthanc SDK 1.12.0 (communications between the + Orthanc core and the database plugin using Google Protocol Buffers) * Upgraded dependencies for static builds (notably on Windows and LSB): - openssl 3.0.1 diff -r c1fe28de1bf6 -r f35b17a38301 PostgreSQL/Plugins/IndexPlugin.cpp --- a/PostgreSQL/Plugins/IndexPlugin.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/PostgreSQL/Plugins/IndexPlugin.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -26,11 +26,15 @@ #include #include +#include + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { + GOOGLE_PROTOBUF_VERIFY_VERSION; + if (!OrthancDatabases::InitializePlugin(context, "PostgreSQL", true)) { return -1; @@ -87,6 +91,7 @@ LOG(WARNING) << "PostgreSQL index is finalizing"; OrthancDatabases::IndexBackend::Finalize(); Orthanc::Toolbox::FinalizeOpenSsl(); + google::protobuf::ShutdownProtobufLibrary(); } diff -r c1fe28de1bf6 -r f35b17a38301 Resources/CMake/DatabasesFrameworkParameters.cmake --- a/Resources/CMake/DatabasesFrameworkParameters.cmake Tue Mar 14 09:08:45 2023 +0100 +++ b/Resources/CMake/DatabasesFrameworkParameters.cmake Mon Apr 03 17:12:08 2023 +0200 @@ -52,3 +52,7 @@ set(ENABLE_ODBC_BACKEND OFF) set(ENABLE_POSTGRESQL_BACKEND OFF) set(ENABLE_SQLITE_BACKEND OFF) + +# Interfacing with Orthanc 1.12.0 +set(ENABLE_PROTOBUF ON) +set(ENABLE_PROTOBUF_COMPILER ON) diff -r c1fe28de1bf6 -r f35b17a38301 Resources/CMake/DatabasesPluginConfiguration.cmake --- a/Resources/CMake/DatabasesPluginConfiguration.cmake Tue Mar 14 09:08:45 2023 +0100 +++ b/Resources/CMake/DatabasesPluginConfiguration.cmake Mon Apr 03 17:12:08 2023 +0200 @@ -23,35 +23,47 @@ include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/AutoGeneratedCode.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/Plugins/OrthancPluginsExports.cmake) - if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK) if (ORTHANC_SDK_VERSION STREQUAL "0.9.5") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5) elseif (ORTHANC_SDK_VERSION STREQUAL "1.4.0") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0) elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.2") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2) elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.4") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4) elseif (ORTHANC_SDK_VERSION STREQUAL "1.9.2") - include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2) + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2) + elseif (ORTHANC_SDK_VERSION STREQUAL "1.12.0") + set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.12.0) elseif (ORTHANC_SDK_VERSION STREQUAL "framework") set(tmp ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include/) message(${tmp}) if (NOT EXISTS ${tmp}/orthanc/OrthancCDatabasePlugin.h) message(FATAL_ERROR "Your copy of the Orthanc framework doesn't contain the Orthanc plugin SDK") endif() - include_directories(${tmp}) + set(ORTHANC_SDK_ROOT ${tmp}) else() message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}") endif() else () - CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H) + find_path(ORTHANC_SDK_ROOT orthanc/OrthancCDatabasePlugin.h + /usr/include + /usr/local/include + ) + + if (NOT ORTHANC_SDK_ROOT) + message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") + endif() + + check_include_file(${ORTHANC_SDK_ROOT}/orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H) if (NOT HAVE_ORTHANC_H) message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK") endif() endif() +include_directories(${ORTHANC_SDK_ROOT}) + if (NOT DEFINED ORTHANC_OPTIMAL_VERSION_MAJOR) message(FATAL_ERROR "ORTHANC_OPTIMAL_VERSION_MAJOR is not defined") @@ -80,7 +92,9 @@ ${ORTHANC_CORE_SOURCES} ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV2.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV3.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV4.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexConnectionsPool.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/StorageBackend.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/DatabaseConstraint.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp diff -r c1fe28de1bf6 -r f35b17a38301 Resources/CMake/DatabasesPluginParameters.cmake --- a/Resources/CMake/DatabasesPluginParameters.cmake Tue Mar 14 09:08:45 2023 +0100 +++ b/Resources/CMake/DatabasesPluginParameters.cmake Mon Apr 03 17:12:08 2023 +0200 @@ -26,7 +26,7 @@ # Advanced parameters to fine-tune linking against system libraries set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK") -set(ORTHANC_SDK_VERSION "1.9.2" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\", \"1.4.0\", \"1.5.2\", \"1.5.4\", \"1.9.2\" or \"framework\")") +set(ORTHANC_SDK_VERSION "1.12.0" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\", \"1.4.0\", \"1.5.2\", \"1.5.4\", \"1.9.2\", \"1.12.0\" or \"framework\")") include(${CMAKE_CURRENT_LIST_DIR}/DatabasesFrameworkParameters.cmake) diff -r c1fe28de1bf6 -r f35b17a38301 SQLite/CMakeLists.txt --- a/SQLite/CMakeLists.txt Tue Mar 14 09:08:45 2023 +0100 +++ b/SQLite/CMakeLists.txt Mon Apr 03 17:12:08 2023 +0200 @@ -24,14 +24,14 @@ set(ORTHANC_PLUGIN_VERSION "mainline") set(ORTHANC_OPTIMAL_VERSION_MAJOR 1) -set(ORTHANC_OPTIMAL_VERSION_MINOR 9) -set(ORTHANC_OPTIMAL_VERSION_REVISION 2) +set(ORTHANC_OPTIMAL_VERSION_MINOR 12) +set(ORTHANC_OPTIMAL_VERSION_REVISION 0) if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline") set(ORTHANC_FRAMEWORK_VERSION "mainline") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") else() - set(ORTHANC_FRAMEWORK_VERSION "1.9.6") + set(ORTHANC_FRAMEWORK_VERSION "1.12.0") set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web") endif() @@ -45,6 +45,23 @@ SQLITE_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql ) +if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto) + add_custom_command( + COMMAND + ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/ + DEPENDS + ProtobufCompiler + ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto + OUTPUT + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h + ) + + list(APPEND AUTOGENERATED_SOURCES + ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc + ) +endif() + add_custom_target( AutogeneratedTarget DEPENDS diff -r c1fe28de1bf6 -r f35b17a38301 SQLite/Plugins/IndexPlugin.cpp --- a/SQLite/Plugins/IndexPlugin.cpp Tue Mar 14 09:08:45 2023 +0100 +++ b/SQLite/Plugins/IndexPlugin.cpp Mon Apr 03 17:12:08 2023 +0200 @@ -25,11 +25,15 @@ #include +#include + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) { + GOOGLE_PROTOBUF_VERIFY_VERSION; + if (!OrthancDatabases::InitializePlugin(context, "SQLite", true)) { return -1; @@ -84,6 +88,7 @@ { LOG(WARNING) << "SQLite index is finalizing"; OrthancDatabases::IndexBackend::Finalize(); + google::protobuf::ShutdownProtobufLibrary(); }