Mercurial > hg > orthanc-databases
view Framework/Plugins/DatabaseBackendAdapterV4.cpp @ 375:824d70ce85ff db-protobuf
implemented database operations
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 28 Mar 2023 18:11:27 +0200 |
parents | be7de633695c |
children | 59bba5fbb425 |
line wrap: on
line source
/** * 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 class Output : public IDatabaseBackendOutput { private: Orthanc::DatabasePluginMessages::DeleteAttachment::Response* deleteAttachment_; void Clear() { deleteAttachment_ = NULL; } public: Output(Orthanc::DatabasePluginMessages::DeleteAttachment::Response& deleteAttachment) { Clear(); deleteAttachment_ = &deleteAttachment; } 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 { 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 { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } virtual void SignalRemainingAncestor(const std::string& ancestorId, OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE { 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 { 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 { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } virtual void AnswerDicomTag(uint16_t group, uint16_t element, const std::string& value) ORTHANC_OVERRIDE { 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 { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } virtual void AnswerMatchingResource(const std::string& resourceId, const std::string& someInstanceId) ORTHANC_OVERRIDE { 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; } default: LOG(ERROR) << "Not implemented database operation from protobuf: " << request.operation(); throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } static void ProcessTransactionOperation(Orthanc::DatabasePluginMessages::TransactionResponse& response, const Orthanc::DatabasePluginMessages::TransactionRequest& request, IndexConnectionsPool::Accessor& transaction) { switch (request.operation()) { case Orthanc::DatabasePluginMessages::OPERATION_ROLLBACK: { transaction.GetManager().RollbackTransaction(); break; } case Orthanc::DatabasePluginMessages::OPERATION_COMMIT: { transaction.GetManager().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(); transaction.GetBackend().AddAttachment(transaction.GetManager(), request.add_attachment().id(), attachment, request.add_attachment().revision()); break; } case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_CHANGES: { transaction.GetBackend().ClearChanges(transaction.GetManager()); break; } case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES: { transaction.GetBackend().ClearExportedResources(transaction.GetManager()); break; } case Orthanc::DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT: { Output output(*response.mutable_delete_attachment()); transaction.GetBackend().DeleteAttachment( output, transaction.GetManager(), request.delete_attachment().id(), request.delete_attachment().type()); break; } case Orthanc::DatabasePluginMessages::OPERATION_DELETE_METADATA: { transaction.GetBackend().DeleteMetadata( transaction.GetManager(), request.delete_metadata().id(), request.delete_metadata().type()); 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); 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