Mercurial > hg > orthanc
diff OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | Plugins/Engine/OrthancPluginDatabase.cpp@94f4a18a79cc |
children | 05b8fd21089c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,1534 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../OrthancServer/PrecompiledHeadersServer.h" +#include "OrthancPluginDatabase.h" + +#if ORTHANC_ENABLE_PLUGINS != 1 +#error The plugin support is disabled +#endif + + +#include "../../Core/Logging.h" +#include "../../Core/OrthancException.h" +#include "PluginsEnumerations.h" + +#include <cassert> + +namespace Orthanc +{ + class OrthancPluginDatabase::Transaction : public IDatabaseWrapper::ITransaction + { + private: + OrthancPluginDatabase& that_; + + void CheckSuccess(OrthancPluginErrorCode code) const + { + if (code != OrthancPluginErrorCode_Success) + { + that_.errorDictionary_.LogError(code, true); + throw OrthancException(static_cast<ErrorCode>(code)); + } + } + + public: + Transaction(OrthancPluginDatabase& that) : + that_(that) + { + } + + virtual void Begin() + { + CheckSuccess(that_.backend_.startTransaction(that_.payload_)); + } + + virtual void Rollback() + { + CheckSuccess(that_.backend_.rollbackTransaction(that_.payload_)); + } + + virtual void Commit(int64_t diskSizeDelta) + { + if (that_.fastGetTotalSize_) + { + CheckSuccess(that_.backend_.commitTransaction(that_.payload_)); + } + else + { + if (static_cast<int64_t>(that_.currentDiskSize_) + diskSizeDelta < 0) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + uint64_t newDiskSize = (that_.currentDiskSize_ + diskSizeDelta); + + assert(newDiskSize == that_.GetTotalCompressedSize()); + + CheckSuccess(that_.backend_.commitTransaction(that_.payload_)); + + // The transaction has succeeded, we can commit the new disk size + that_.currentDiskSize_ = newDiskSize; + } + } + }; + + + static FileInfo Convert(const OrthancPluginAttachment& attachment) + { + return FileInfo(attachment.uuid, + static_cast<FileContentType>(attachment.contentType), + attachment.uncompressedSize, + attachment.uncompressedHash, + static_cast<CompressionType>(attachment.compressionType), + attachment.compressedSize, + attachment.compressedHash); + } + + + void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code) + { + if (code != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast<ErrorCode>(code)); + } + } + + + void OrthancPluginDatabase::ResetAnswers() + { + type_ = _OrthancPluginDatabaseAnswerType_None; + + answerDicomMap_ = NULL; + answerChanges_ = NULL; + answerExportedResources_ = NULL; + answerDone_ = NULL; + answerMatchingResources_ = NULL; + answerMatchingInstances_ = NULL; + answerMetadata_ = NULL; + } + + + void OrthancPluginDatabase::ForwardAnswers(std::list<int64_t>& target) + { + if (type_ != _OrthancPluginDatabaseAnswerType_None && + type_ != _OrthancPluginDatabaseAnswerType_Int64) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + target.clear(); + + if (type_ == _OrthancPluginDatabaseAnswerType_Int64) + { + for (std::list<int64_t>::const_iterator + it = answerInt64_.begin(); it != answerInt64_.end(); ++it) + { + target.push_back(*it); + } + } + } + + + void OrthancPluginDatabase::ForwardAnswers(std::list<std::string>& target) + { + if (type_ != _OrthancPluginDatabaseAnswerType_None && + type_ != _OrthancPluginDatabaseAnswerType_String) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + target.clear(); + + if (type_ == _OrthancPluginDatabaseAnswerType_String) + { + for (std::list<std::string>::const_iterator + it = answerStrings_.begin(); it != answerStrings_.end(); ++it) + { + target.push_back(*it); + } + } + } + + + bool OrthancPluginDatabase::ForwardSingleAnswer(std::string& target) + { + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + return false; + } + else if (type_ == _OrthancPluginDatabaseAnswerType_String && + answerStrings_.size() == 1) + { + target = answerStrings_.front(); + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + bool OrthancPluginDatabase::ForwardSingleAnswer(int64_t& target) + { + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + return false; + } + else if (type_ == _OrthancPluginDatabaseAnswerType_Int64 && + answerInt64_.size() == 1) + { + target = answerInt64_.front(); + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + OrthancPluginDatabase::OrthancPluginDatabase(SharedLibrary& library, + PluginsErrorDictionary& errorDictionary, + const OrthancPluginDatabaseBackend& backend, + const OrthancPluginDatabaseExtensions* extensions, + size_t extensionsSize, + void *payload) : + library_(library), + errorDictionary_(errorDictionary), + backend_(backend), + payload_(payload), + listener_(NULL) + { + static const char* const MISSING = " Missing extension in database index plugin: "; + + ResetAnswers(); + + memset(&extensions_, 0, sizeof(extensions_)); + + size_t size = sizeof(extensions_); + if (extensionsSize < size) + { + size = extensionsSize; // Not all the extensions are available + } + + memcpy(&extensions_, extensions, size); + + bool isOptimal = true; + + if (extensions_.lookupResources == NULL) + { + LOG(INFO) << MISSING << "LookupIdentifierRange()"; + isOptimal = false; + } + + if (extensions_.createInstance == NULL) + { + LOG(INFO) << MISSING << "CreateInstance()"; + isOptimal = false; + } + + if (extensions_.setResourcesContent == NULL) + { + LOG(INFO) << MISSING << "SetResourcesContent()"; + isOptimal = false; + } + + if (extensions_.getChildrenMetadata == NULL) + { + LOG(INFO) << MISSING << "GetChildrenMetadata()"; + isOptimal = false; + } + + if (extensions_.getAllMetadata == NULL) + { + LOG(INFO) << MISSING << "GetAllMetadata()"; + isOptimal = false; + } + + if (extensions_.lookupResourceAndParent == NULL) + { + LOG(INFO) << MISSING << "LookupResourceAndParent()"; + isOptimal = false; + } + + if (isOptimal) + { + LOG(INFO) << "The performance of the database index plugin " + << "is optimal for this version of Orthanc"; + } + else + { + LOG(WARNING) << "Performance warning in the database index: " + << "Some extensions are missing in the plugin"; + } + + if (extensions_.getLastChangeIndex == NULL) + { + LOG(WARNING) << "The database extension GetLastChangeIndex() is missing"; + } + + if (extensions_.tagMostRecentPatient == NULL) + { + LOG(WARNING) << "The database extension TagMostRecentPatient() is missing " + << "(affected by issue 58)"; + } + } + + + void OrthancPluginDatabase::Open() + { + CheckSuccess(backend_.open(payload_)); + + { + Transaction transaction(*this); + transaction.Begin(); + + std::string tmp; + fastGetTotalSize_ = + (LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) && + tmp == "1"); + + if (fastGetTotalSize_) + { + currentDiskSize_ = 0; // Unused + } + else + { + // This is the case of database plugins using Orthanc SDK <= 1.5.2 + LOG(WARNING) << "Your database index plugin is not compatible with multiple Orthanc writers"; + currentDiskSize_ = GetTotalCompressedSize(); + } + + transaction.Commit(0); + } + } + + + void OrthancPluginDatabase::AddAttachment(int64_t id, + const FileInfo& attachment) + { + OrthancPluginAttachment tmp; + tmp.uuid = attachment.GetUuid().c_str(); + tmp.contentType = static_cast<int32_t>(attachment.GetContentType()); + tmp.uncompressedSize = attachment.GetUncompressedSize(); + tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str(); + tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType()); + tmp.compressedSize = attachment.GetCompressedSize(); + tmp.compressedHash = attachment.GetCompressedMD5().c_str(); + + CheckSuccess(backend_.addAttachment(payload_, id, &tmp)); + } + + + void OrthancPluginDatabase::AttachChild(int64_t parent, + int64_t child) + { + CheckSuccess(backend_.attachChild(payload_, parent, child)); + } + + + void OrthancPluginDatabase::ClearChanges() + { + CheckSuccess(backend_.clearChanges(payload_)); + } + + + void OrthancPluginDatabase::ClearExportedResources() + { + CheckSuccess(backend_.clearExportedResources(payload_)); + } + + + int64_t OrthancPluginDatabase::CreateResource(const std::string& publicId, + ResourceType type) + { + int64_t id; + CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type))); + return id; + } + + + void OrthancPluginDatabase::DeleteAttachment(int64_t id, + FileContentType attachment) + { + CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment))); + } + + + void OrthancPluginDatabase::DeleteMetadata(int64_t id, + MetadataType type) + { + CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type))); + } + + + void OrthancPluginDatabase::DeleteResource(int64_t id) + { + CheckSuccess(backend_.deleteResource(payload_, id)); + } + + + void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target, + int64_t id) + { + if (extensions_.getAllMetadata == NULL) + { + // Fallback implementation if extension is missing + target.clear(); + + ResetAnswers(); + CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id)); + + if (type_ != _OrthancPluginDatabaseAnswerType_None && + type_ != _OrthancPluginDatabaseAnswerType_Int32) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + target.clear(); + + if (type_ == _OrthancPluginDatabaseAnswerType_Int32) + { + for (std::list<int32_t>::const_iterator + it = answerInt32_.begin(); it != answerInt32_.end(); ++it) + { + MetadataType type = static_cast<MetadataType>(*it); + + std::string value; + if (LookupMetadata(value, id, type)) + { + target[type] = value; + } + } + } + } + else + { + ResetAnswers(); + + answerMetadata_ = ⌖ + target.clear(); + + CheckSuccess(extensions_.getAllMetadata(GetContext(), payload_, id)); + + if (type_ != _OrthancPluginDatabaseAnswerType_None && + type_ != _OrthancPluginDatabaseAnswerType_Metadata) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + } + + + void OrthancPluginDatabase::GetAllInternalIds(std::list<int64_t>& target, + ResourceType resourceType) + { + if (extensions_.getAllInternalIds == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin, + "The database plugin does not implement the mandatory GetAllInternalIds() extension"); + } + + ResetAnswers(); + CheckSuccess(extensions_.getAllInternalIds(GetContext(), payload_, Plugins::Convert(resourceType))); + ForwardAnswers(target); + } + + + void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType) + { + ResetAnswers(); + CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType))); + ForwardAnswers(target); + } + + + void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target, + ResourceType resourceType, + size_t since, + size_t limit) + { + if (extensions_.getAllPublicIdsWithLimit != NULL) + { + // This extension is available since Orthanc 0.9.4 + ResetAnswers(); + CheckSuccess(extensions_.getAllPublicIdsWithLimit + (GetContext(), payload_, Plugins::Convert(resourceType), since, limit)); + ForwardAnswers(target); + } + else + { + // The extension is not available in the database plugin, use a + // fallback implementation + target.clear(); + + if (limit == 0) + { + return; + } + + std::list<std::string> tmp; + GetAllPublicIds(tmp, resourceType); + + if (tmp.size() <= since) + { + // Not enough results => empty answer + return; + } + + std::list<std::string>::iterator current = tmp.begin(); + std::advance(current, since); + + while (limit > 0 && current != tmp.end()) + { + target.push_back(*current); + --limit; + ++current; + } + } + } + + + + void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) + { + ResetAnswers(); + answerChanges_ = ⌖ + answerDone_ = &done; + done = false; + + CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults)); + } + + + void OrthancPluginDatabase::GetChildrenInternalId(std::list<int64_t>& target, + int64_t id) + { + ResetAnswers(); + CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id)); + ForwardAnswers(target); + } + + + void OrthancPluginDatabase::GetChildrenPublicId(std::list<std::string>& target, + int64_t id) + { + ResetAnswers(); + CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id)); + ForwardAnswers(target); + } + + + void OrthancPluginDatabase::GetExportedResources(std::list<ExportedResource>& target /*out*/, + bool& done /*out*/, + int64_t since, + uint32_t maxResults) + { + ResetAnswers(); + answerExportedResources_ = ⌖ + answerDone_ = &done; + done = false; + + CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults)); + } + + + void OrthancPluginDatabase::GetLastChange(std::list<ServerIndexChange>& target /*out*/) + { + bool ignored = false; + + ResetAnswers(); + answerChanges_ = ⌖ + answerDone_ = &ignored; + + CheckSuccess(backend_.getLastChange(GetContext(), payload_)); + } + + + void OrthancPluginDatabase::GetLastExportedResource(std::list<ExportedResource>& target /*out*/) + { + bool ignored = false; + + ResetAnswers(); + answerExportedResources_ = ⌖ + answerDone_ = &ignored; + + CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_)); + } + + + void OrthancPluginDatabase::GetMainDicomTags(DicomMap& map, + int64_t id) + { + ResetAnswers(); + answerDicomMap_ = ↦ + + CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id)); + } + + + std::string OrthancPluginDatabase::GetPublicId(int64_t resourceId) + { + ResetAnswers(); + std::string s; + + CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId)); + + if (!ForwardSingleAnswer(s)) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + return s; + } + + + uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType) + { + uint64_t count; + CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType))); + return count; + } + + + ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId) + { + OrthancPluginResourceType type; + CheckSuccess(backend_.getResourceType(&type, payload_, resourceId)); + return Plugins::Convert(type); + } + + + uint64_t OrthancPluginDatabase::GetTotalCompressedSize() + { + uint64_t size; + CheckSuccess(backend_.getTotalCompressedSize(&size, payload_)); + return size; + } + + + uint64_t OrthancPluginDatabase::GetTotalUncompressedSize() + { + uint64_t size; + CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_)); + return size; + } + + + bool OrthancPluginDatabase::IsExistingResource(int64_t internalId) + { + int32_t existing; + CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId)); + return (existing != 0); + } + + + bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId) + { + int32_t isProtected; + CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId)); + return (isProtected != 0); + } + + + void OrthancPluginDatabase::ListAvailableAttachments(std::list<FileContentType>& target, + int64_t id) + { + ResetAnswers(); + + CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id)); + + if (type_ != _OrthancPluginDatabaseAnswerType_None && + type_ != _OrthancPluginDatabaseAnswerType_Int32) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + target.clear(); + + if (type_ == _OrthancPluginDatabaseAnswerType_Int32) + { + for (std::list<int32_t>::const_iterator + it = answerInt32_.begin(); it != answerInt32_.end(); ++it) + { + target.push_back(static_cast<FileContentType>(*it)); + } + } + } + + + void OrthancPluginDatabase::LogChange(int64_t internalId, + const ServerIndexChange& change) + { + OrthancPluginChange tmp; + tmp.seq = change.GetSeq(); + tmp.changeType = static_cast<int32_t>(change.GetChangeType()); + tmp.resourceType = Plugins::Convert(change.GetResourceType()); + tmp.publicId = change.GetPublicId().c_str(); + tmp.date = change.GetDate().c_str(); + + CheckSuccess(backend_.logChange(payload_, &tmp)); + } + + + void OrthancPluginDatabase::LogExportedResource(const ExportedResource& resource) + { + OrthancPluginExportedResource tmp; + tmp.seq = resource.GetSeq(); + tmp.resourceType = Plugins::Convert(resource.GetResourceType()); + tmp.publicId = resource.GetPublicId().c_str(); + tmp.modality = resource.GetModality().c_str(); + tmp.date = resource.GetDate().c_str(); + tmp.patientId = resource.GetPatientId().c_str(); + tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str(); + tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str(); + tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str(); + + CheckSuccess(backend_.logExportedResource(payload_, &tmp)); + } + + + bool OrthancPluginDatabase::LookupAttachment(FileInfo& attachment, + int64_t id, + FileContentType contentType) + { + ResetAnswers(); + + CheckSuccess(backend_.lookupAttachment + (GetContext(), payload_, id, static_cast<int32_t>(contentType))); + + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + return false; + } + else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment && + answerAttachments_.size() == 1) + { + attachment = answerAttachments_.front(); + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + bool OrthancPluginDatabase::LookupGlobalProperty(std::string& target, + GlobalProperty property) + { + ResetAnswers(); + + CheckSuccess(backend_.lookupGlobalProperty + (GetContext(), payload_, static_cast<int32_t>(property))); + + return ForwardSingleAnswer(target); + } + + + bool OrthancPluginDatabase::LookupMetadata(std::string& target, + int64_t id, + MetadataType type) + { + ResetAnswers(); + CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type))); + return ForwardSingleAnswer(target); + } + + + bool OrthancPluginDatabase::LookupParent(int64_t& parentId, + int64_t resourceId) + { + ResetAnswers(); + CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId)); + return ForwardSingleAnswer(parentId); + } + + + bool OrthancPluginDatabase::LookupResource(int64_t& id, + ResourceType& type, + const std::string& publicId) + { + ResetAnswers(); + + CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str())); + + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + return false; + } + else if (type_ == _OrthancPluginDatabaseAnswerType_Resource && + answerResources_.size() == 1) + { + id = answerResources_.front().first; + type = answerResources_.front().second; + return true; + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId) + { + ResetAnswers(); + CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_)); + return ForwardSingleAnswer(internalId); + } + + + bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId, + int64_t patientIdToAvoid) + { + ResetAnswers(); + CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid)); + return ForwardSingleAnswer(internalId); + } + + + void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property, + const std::string& value) + { + CheckSuccess(backend_.setGlobalProperty + (payload_, static_cast<int32_t>(property), value.c_str())); + } + + + void OrthancPluginDatabase::ClearMainDicomTags(int64_t id) + { + if (extensions_.clearMainDicomTags == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin, + "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension"); + } + + CheckSuccess(extensions_.clearMainDicomTags(payload_, id)); + } + + + void OrthancPluginDatabase::SetMainDicomTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + OrthancPluginDicomTag tmp; + tmp.group = tag.GetGroup(); + tmp.element = tag.GetElement(); + tmp.value = value.c_str(); + + CheckSuccess(backend_.setMainDicomTag(payload_, id, &tmp)); + } + + + void OrthancPluginDatabase::SetIdentifierTag(int64_t id, + const DicomTag& tag, + const std::string& value) + { + OrthancPluginDicomTag tmp; + tmp.group = tag.GetGroup(); + tmp.element = tag.GetElement(); + tmp.value = value.c_str(); + + CheckSuccess(backend_.setIdentifierTag(payload_, id, &tmp)); + } + + + void OrthancPluginDatabase::SetMetadata(int64_t id, + MetadataType type, + const std::string& value) + { + CheckSuccess(backend_.setMetadata + (payload_, id, static_cast<int32_t>(type), value.c_str())); + } + + + void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, + bool isProtected) + { + CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected)); + } + + + IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction() + { + return new Transaction(*this); + } + + + static void ProcessEvent(IDatabaseListener& listener, + const _OrthancPluginDatabaseAnswer& answer) + { + switch (answer.type) + { + case _OrthancPluginDatabaseAnswerType_DeletedAttachment: + { + const OrthancPluginAttachment& attachment = + *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric); + listener.SignalFileDeleted(Convert(attachment)); + break; + } + + case _OrthancPluginDatabaseAnswerType_RemainingAncestor: + { + ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32)); + listener.SignalRemainingAncestor(type, answer.valueString); + break; + } + + case _OrthancPluginDatabaseAnswerType_DeletedResource: + { + ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32)); + ServerIndexChange change(ChangeType_Deleted, type, answer.valueString); + listener.SignalChange(change); + break; + } + + default: + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + + + unsigned int OrthancPluginDatabase::GetDatabaseVersion() + { + if (extensions_.getDatabaseVersion != NULL) + { + uint32_t version; + CheckSuccess(extensions_.getDatabaseVersion(&version, payload_)); + return version; + } + else + { + // Before adding the "GetDatabaseVersion()" extension in plugins + // (OrthancPostgreSQL <= 1.2), the only supported DB schema was + // version 5. + return 5; + } + } + + + void OrthancPluginDatabase::Upgrade(unsigned int targetVersion, + IStorageArea& storageArea) + { + if (extensions_.upgradeDatabase != NULL) + { + Transaction transaction(*this); + transaction.Begin(); + + OrthancPluginErrorCode code = extensions_.upgradeDatabase( + payload_, targetVersion, + reinterpret_cast<OrthancPluginStorageArea*>(&storageArea)); + + if (code == OrthancPluginErrorCode_Success) + { + transaction.Commit(0); + } + else + { + transaction.Rollback(); + errorDictionary_.LogError(code, true); + throw OrthancException(static_cast<ErrorCode>(code)); + } + } + } + + + void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer) + { + if (answer.type == _OrthancPluginDatabaseAnswerType_None) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + if (answer.type == _OrthancPluginDatabaseAnswerType_DeletedAttachment || + answer.type == _OrthancPluginDatabaseAnswerType_DeletedResource || + answer.type == _OrthancPluginDatabaseAnswerType_RemainingAncestor) + { + assert(listener_ != NULL); + ProcessEvent(*listener_, answer); + return; + } + + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + type_ = answer.type; + + switch (type_) + { + case _OrthancPluginDatabaseAnswerType_Int32: + answerInt32_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Int64: + answerInt64_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Resource: + answerResources_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Attachment: + answerAttachments_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_String: + answerStrings_.clear(); + break; + + case _OrthancPluginDatabaseAnswerType_DicomTag: + assert(answerDicomMap_ != NULL); + answerDicomMap_->Clear(); + break; + + case _OrthancPluginDatabaseAnswerType_Change: + assert(answerChanges_ != NULL); + answerChanges_->clear(); + break; + + case _OrthancPluginDatabaseAnswerType_ExportedResource: + assert(answerExportedResources_ != NULL); + answerExportedResources_->clear(); + break; + + case _OrthancPluginDatabaseAnswerType_MatchingResource: + assert(answerMatchingResources_ != NULL); + answerMatchingResources_->clear(); + + if (answerMatchingInstances_ != NULL) + { + answerMatchingInstances_->clear(); + } + + break; + + case _OrthancPluginDatabaseAnswerType_Metadata: + assert(answerMetadata_ != NULL); + answerMetadata_->clear(); + break; + + default: + throw OrthancException(ErrorCode_DatabasePlugin, + "Unhandled type of answer for custom index plugin: " + + boost::lexical_cast<std::string>(answer.type)); + } + } + else if (type_ != answer.type) + { + throw OrthancException(ErrorCode_DatabasePlugin, + "Error in the plugin protocol: Cannot change the answer type"); + } + + switch (answer.type) + { + case _OrthancPluginDatabaseAnswerType_Int32: + { + answerInt32_.push_back(answer.valueInt32); + break; + } + + case _OrthancPluginDatabaseAnswerType_Int64: + { + answerInt64_.push_back(answer.valueInt64); + break; + } + + case _OrthancPluginDatabaseAnswerType_Resource: + { + OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(answer.valueInt32); + answerResources_.push_back(std::make_pair(answer.valueInt64, Plugins::Convert(type))); + break; + } + + case _OrthancPluginDatabaseAnswerType_Attachment: + { + const OrthancPluginAttachment& attachment = + *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric); + + answerAttachments_.push_back(Convert(attachment)); + break; + } + + case _OrthancPluginDatabaseAnswerType_DicomTag: + { + const OrthancPluginDicomTag& tag = *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric); + assert(answerDicomMap_ != NULL); + answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value), false); + break; + } + + case _OrthancPluginDatabaseAnswerType_String: + { + if (answer.valueString == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + if (type_ == _OrthancPluginDatabaseAnswerType_None) + { + type_ = _OrthancPluginDatabaseAnswerType_String; + answerStrings_.clear(); + } + else if (type_ != _OrthancPluginDatabaseAnswerType_String) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + answerStrings_.push_back(std::string(answer.valueString)); + break; + } + + case _OrthancPluginDatabaseAnswerType_Change: + { + assert(answerDone_ != NULL); + if (answer.valueUint32 == 1) + { + *answerDone_ = true; + } + else if (*answerDone_) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + const OrthancPluginChange& change = + *reinterpret_cast<const OrthancPluginChange*>(answer.valueGeneric); + assert(answerChanges_ != NULL); + answerChanges_->push_back + (ServerIndexChange(change.seq, + static_cast<ChangeType>(change.changeType), + Plugins::Convert(change.resourceType), + change.publicId, + change.date)); + } + + break; + } + + case _OrthancPluginDatabaseAnswerType_ExportedResource: + { + assert(answerDone_ != NULL); + if (answer.valueUint32 == 1) + { + *answerDone_ = true; + } + else if (*answerDone_) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + else + { + const OrthancPluginExportedResource& exported = + *reinterpret_cast<const OrthancPluginExportedResource*>(answer.valueGeneric); + assert(answerExportedResources_ != NULL); + answerExportedResources_->push_back + (ExportedResource(exported.seq, + Plugins::Convert(exported.resourceType), + exported.publicId, + exported.modality, + exported.date, + exported.patientId, + exported.studyInstanceUid, + exported.seriesInstanceUid, + exported.sopInstanceUid)); + } + + break; + } + + case _OrthancPluginDatabaseAnswerType_MatchingResource: + { + const OrthancPluginMatchingResource& match = + *reinterpret_cast<const OrthancPluginMatchingResource*>(answer.valueGeneric); + + if (match.resourceId == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + assert(answerMatchingResources_ != NULL); + answerMatchingResources_->push_back(match.resourceId); + + if (answerMatchingInstances_ != NULL) + { + if (match.someInstanceId == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + answerMatchingInstances_->push_back(match.someInstanceId); + } + + break; + } + + case _OrthancPluginDatabaseAnswerType_Metadata: + { + const OrthancPluginResourcesContentMetadata& metadata = + *reinterpret_cast<const OrthancPluginResourcesContentMetadata*>(answer.valueGeneric); + + MetadataType type = static_cast<MetadataType>(metadata.metadata); + + if (metadata.value == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + assert(answerMetadata_ != NULL && + answerMetadata_->find(type) == answerMetadata_->end()); + (*answerMetadata_) [type] = metadata.value; + break; + } + + default: + throw OrthancException(ErrorCode_DatabasePlugin, + "Unhandled type of answer for custom index plugin: " + + boost::lexical_cast<std::string>(answer.type)); + } + } + + + bool OrthancPluginDatabase::IsDiskSizeAbove(uint64_t threshold) + { + if (fastGetTotalSize_) + { + return GetTotalCompressedSize() > threshold; + } + else + { + assert(GetTotalCompressedSize() == currentDiskSize_); + return currentDiskSize_ > threshold; + } + } + + + void OrthancPluginDatabase::ApplyLookupResources(std::list<std::string>& resourcesId, + std::list<std::string>* instancesId, + const std::vector<DatabaseConstraint>& lookup, + ResourceType queryLevel, + size_t limit) + { + if (extensions_.lookupResources == NULL) + { + // Fallback to compatibility mode + ILookupResources::Apply + (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit); + } + else + { + std::vector<OrthancPluginDatabaseConstraint> constraints; + std::vector< std::vector<const char*> > constraintsValues; + + constraints.resize(lookup.size()); + constraintsValues.resize(lookup.size()); + + for (size_t i = 0; i < lookup.size(); i++) + { + lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]); + } + + ResetAnswers(); + answerMatchingResources_ = &resourcesId; + answerMatchingInstances_ = instancesId; + + CheckSuccess(extensions_.lookupResources(GetContext(), payload_, lookup.size(), + (lookup.empty() ? NULL : &constraints[0]), + Plugins::Convert(queryLevel), + limit, (instancesId == NULL ? 0 : 1))); + } + } + + + bool OrthancPluginDatabase::CreateInstance( + IDatabaseWrapper::CreateInstanceResult& result, + int64_t& instanceId, + const std::string& patient, + const std::string& study, + const std::string& series, + const std::string& instance) + { + if (extensions_.createInstance == NULL) + { + // Fallback to compatibility mode + return ICreateInstance::Apply + (*this, result, instanceId, patient, study, series, instance); + } + else + { + OrthancPluginCreateInstanceResult output; + memset(&output, 0, sizeof(output)); + + CheckSuccess(extensions_.createInstance(&output, payload_, patient.c_str(), + study.c_str(), series.c_str(), instance.c_str())); + + instanceId = output.instanceId; + + if (output.isNewInstance) + { + result.isNewPatient_ = output.isNewPatient; + result.isNewStudy_ = output.isNewStudy; + result.isNewSeries_ = output.isNewSeries; + result.patientId_ = output.patientId; + result.studyId_ = output.studyId; + result.seriesId_ = output.seriesId; + return true; + } + else + { + return false; + } + } + } + + + void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + Compatibility::IdentifierConstraintType type, + const std::string& value) + { + if (extensions_.lookupIdentifier3 == NULL) + { + throw OrthancException(ErrorCode_DatabasePlugin, + "The database plugin does not implement the mandatory LookupIdentifier3() extension"); + } + + OrthancPluginDicomTag tmp; + tmp.group = tag.GetGroup(); + tmp.element = tag.GetElement(); + tmp.value = value.c_str(); + + ResetAnswers(); + CheckSuccess(extensions_.lookupIdentifier3(GetContext(), payload_, Plugins::Convert(level), + &tmp, Compatibility::Convert(type))); + ForwardAnswers(result); + } + + + void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result, + ResourceType level, + const DicomTag& tag, + const std::string& start, + const std::string& end) + { + if (extensions_.lookupIdentifierRange == NULL) + { + // Default implementation, for plugins using Orthanc SDK <= 1.3.2 + + LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start); + + std::list<int64_t> b; + LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end); + + result.splice(result.end(), b); + } + else + { + ResetAnswers(); + CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level), + tag.GetGroup(), tag.GetElement(), + start.c_str(), end.c_str())); + ForwardAnswers(result); + } + } + + + void OrthancPluginDatabase::SetResourcesContent(const Orthanc::ResourcesContent& content) + { + if (extensions_.setResourcesContent == NULL) + { + ISetResourcesContent::Apply(*this, content); + } + else + { + std::vector<OrthancPluginResourcesContentTags> identifierTags; + std::vector<OrthancPluginResourcesContentTags> mainDicomTags; + std::vector<OrthancPluginResourcesContentMetadata> metadata; + + identifierTags.reserve(content.GetListTags().size()); + mainDicomTags.reserve(content.GetListTags().size()); + metadata.reserve(content.GetListMetadata().size()); + + for (ResourcesContent::ListTags::const_iterator + it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it) + { + OrthancPluginResourcesContentTags tmp; + tmp.resource = it->resourceId_; + tmp.group = it->tag_.GetGroup(); + tmp.element = it->tag_.GetElement(); + tmp.value = it->value_.c_str(); + + if (it->isIdentifier_) + { + identifierTags.push_back(tmp); + } + else + { + mainDicomTags.push_back(tmp); + } + } + + for (ResourcesContent::ListMetadata::const_iterator + it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it) + { + OrthancPluginResourcesContentMetadata tmp; + tmp.resource = it->resourceId_; + tmp.metadata = it->metadata_; + tmp.value = it->value_.c_str(); + metadata.push_back(tmp); + } + + assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() && + metadata.size() == content.GetListMetadata().size()); + + CheckSuccess(extensions_.setResourcesContent( + payload_, + identifierTags.size(), + (identifierTags.empty() ? NULL : &identifierTags[0]), + mainDicomTags.size(), + (mainDicomTags.empty() ? NULL : &mainDicomTags[0]), + metadata.size(), + (metadata.empty() ? NULL : &metadata[0]))); + } + } + + + + void OrthancPluginDatabase::GetChildrenMetadata(std::list<std::string>& target, + int64_t resourceId, + MetadataType metadata) + { + if (extensions_.getChildrenMetadata == NULL) + { + IGetChildrenMetadata::Apply(*this, target, resourceId, metadata); + } + else + { + ResetAnswers(); + CheckSuccess(extensions_.getChildrenMetadata + (GetContext(), payload_, resourceId, static_cast<int32_t>(metadata))); + ForwardAnswers(target); + } + } + + + int64_t OrthancPluginDatabase::GetLastChangeIndex() + { + if (extensions_.getLastChangeIndex == NULL) + { + // This was the default behavior in Orthanc <= 1.5.1 + // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ + return 0; + } + else + { + int64_t result = 0; + CheckSuccess(extensions_.getLastChangeIndex(&result, payload_)); + return result; + } + } + + + void OrthancPluginDatabase::TagMostRecentPatient(int64_t patient) + { + if (extensions_.tagMostRecentPatient != NULL) + { + CheckSuccess(extensions_.tagMostRecentPatient(payload_, patient)); + } + } + + + bool OrthancPluginDatabase::LookupResourceAndParent(int64_t& id, + ResourceType& type, + std::string& parentPublicId, + const std::string& publicId) + { + if (extensions_.lookupResourceAndParent == NULL) + { + return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId); + } + else + { + std::list<std::string> parent; + + uint8_t isExisting; + OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient; + + ResetAnswers(); + CheckSuccess(extensions_.lookupResourceAndParent + (GetContext(), &isExisting, &id, &pluginType, payload_, publicId.c_str())); + ForwardAnswers(parent); + + if (isExisting) + { + type = Plugins::Convert(pluginType); + + if (parent.empty()) + { + if (type != ResourceType_Patient) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + } + else if (parent.size() == 1) + { + if ((type != ResourceType_Study && + type != ResourceType_Series && + type != ResourceType_Instance) || + parent.front().empty()) + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + parentPublicId = parent.front(); + } + else + { + throw OrthancException(ErrorCode_DatabasePlugin); + } + + return true; + } + else + { + return false; + } + } + } +}