Mercurial > hg > orthanc-databases
diff Framework/Plugins/DatabaseBackendAdapterV4.cpp @ 387:f35b17a38301
integration db-protobuf->mainline
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 03 Apr 2023 17:12:08 +0200 |
parents | e236be67e5f9 |
children | 3d6886f3e5b3 |
line wrap: on
line diff
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <OrthancDatabasePlugin.pb.h> // Include protobuf messages + +#include <Logging.h> +#include <OrthancException.h> + +#include <stdexcept> +#include <list> +#include <string> +#include <cassert> + + +#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<IndexConnectionsPool::Accessor> 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<intptr_t>(transaction.release())); + break; + } + + case Orthanc::DatabasePluginMessages::OPERATION_UPGRADE: + { + IndexConnectionsPool::Accessor accessor(pool); + OrthancPluginStorageArea* storageArea = reinterpret_cast<OrthancPluginStorageArea*>(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<IndexConnectionsPool::Accessor*>(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<Orthanc::DatabaseConstraint> 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<const char*> 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<int32_t, std::string> 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<std::string> 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<std::string>::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<std::string> 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<std::string>::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<int64_t> 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<int64_t>::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<std::string> 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<std::string>::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<int32_t> values; + backend.ListAvailableAttachments(values, manager, request.list_available_attachments().id()); + + response.mutable_list_available_attachments()->mutable_attachments()->Reserve(values.size()); + for (std::list<int32_t>::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<OrthancPluginResourcesContentTags> identifierTags; + std::vector<OrthancPluginResourcesContentTags> 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<OrthancPluginResourcesContentMetadata> 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<std::string> 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<std::string>::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<IndexConnectionsPool*>(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<IndexConnectionsPool::Accessor*>(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<OrthancPluginErrorCode>(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<IndexConnectionsPool*>(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<IndexConnectionsPool> 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