Orthanc/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp
2025-06-23 19:07:37 +05:30

1732 lines
55 KiB
C++

/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "../../Sources/PrecompiledHeadersServer.h"
#include "OrthancPluginDatabase.h"
#if ORTHANC_ENABLE_PLUGINS != 1
# error The plugin support is disabled
#endif
#include "../../../OrthancFramework/Sources/Logging.h"
#include "../../../OrthancFramework/Sources/OrthancException.h"
#include "../../Sources/Database/Compatibility/ICreateInstance.h"
#include "../../Sources/Database/Compatibility/IGetChildrenMetadata.h"
#include "../../Sources/Database/Compatibility/ILookupResourceAndParent.h"
#include "../../Sources/Database/Compatibility/ILookupResources.h"
#include "../../Sources/Database/Compatibility/ISetResourcesContent.h"
#include "../../Sources/Database/VoidDatabaseListener.h"
#include "PluginsEnumerations.h"
#include <cassert>
namespace Orthanc
{
class OrthancPluginDatabase::Transaction :
public BaseCompatibilityTransaction,
public Compatibility::ICreateInstance,
public Compatibility::IGetChildrenMetadata,
public Compatibility::ILookupResources,
public Compatibility::ILookupResourceAndParent,
public Compatibility::ISetResourcesContent
{
private:
typedef std::pair<int64_t, ResourceType> AnswerResource;
typedef std::map<MetadataType, std::string> AnswerMetadata;
OrthancPluginDatabase& that_;
boost::recursive_mutex::scoped_lock lock_;
IDatabaseListener& listener_;
_OrthancPluginDatabaseAnswerType type_;
std::list<std::string> answerStrings_;
std::list<int32_t> answerInt32_;
std::list<int64_t> answerInt64_;
std::list<AnswerResource> answerResources_;
std::list<FileInfo> answerAttachments_;
DicomMap* answerDicomMap_;
std::list<ServerIndexChange>* answerChanges_;
std::list<ExportedResource>* answerExportedResources_;
bool* answerDone_;
bool answerDoneIgnored_;
std::list<std::string>* answerMatchingResources_;
std::list<std::string>* answerMatchingInstances_;
AnswerMetadata* answerMetadata_;
void CheckSuccess(OrthancPluginErrorCode code) const
{
that_.CheckSuccess(code);
}
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 ResetAnswers()
{
type_ = _OrthancPluginDatabaseAnswerType_None;
answerDicomMap_ = NULL;
answerChanges_ = NULL;
answerExportedResources_ = NULL;
answerDone_ = NULL;
answerMatchingResources_ = NULL;
answerMatchingInstances_ = NULL;
answerMetadata_ = NULL;
}
void 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 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 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 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);
}
}
void ProcessEvent(const _OrthancPluginDatabaseAnswer& answer)
{
switch (answer.type)
{
case _OrthancPluginDatabaseAnswerType_DeletedAttachment:
{
const OrthancPluginAttachment& attachment =
*reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
listener_.SignalAttachmentDeleted(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));
listener_.SignalResourceDeleted(type, answer.valueString);
break;
}
default:
throw OrthancException(ErrorCode_DatabasePlugin);
}
}
public:
explicit Transaction(OrthancPluginDatabase& that,
IDatabaseListener& listener) :
that_(that),
lock_(that.mutex_),
listener_(listener),
type_(_OrthancPluginDatabaseAnswerType_None),
answerDoneIgnored_(false)
{
if (that_.activeTransaction_ != NULL)
{
throw OrthancException(ErrorCode_InternalError);
}
that_.activeTransaction_ = this;
ResetAnswers();
}
virtual ~Transaction()
{
assert(that_.activeTransaction_ != NULL);
that_.activeTransaction_ = NULL;
}
IDatabaseListener& GetDatabaseListener() const
{
return listener_;
}
void Begin()
{
CheckSuccess(that_.backend_.startTransaction(that_.payload_));
}
virtual void Rollback() ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.rollbackTransaction(that_.payload_));
}
virtual void Commit(int64_t diskSizeDelta) ORTHANC_OVERRIDE
{
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 == GetTotalCompressedSize());
CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
// The transaction has succeeded, we can commit the new disk size
that_.currentDiskSize_ = newDiskSize;
}
}
void 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)
{
ProcessEvent(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));
}
}
// From the "ILookupResources" interface
virtual void LookupIdentifier(std::list<int64_t>& result,
ResourceType level,
const DicomTag& tag,
Compatibility::IdentifierConstraintType type,
const std::string& value) ORTHANC_OVERRIDE
{
if (that_.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(that_.extensions_.lookupIdentifier3(that_.GetContext(), that_.payload_, Plugins::Convert(level),
&tmp, Compatibility::Convert(type)));
ForwardAnswers(result);
}
virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
std::list<std::string>* instancesId,
const DatabaseDicomTagConstraints& lookup,
ResourceType queryLevel,
const std::set<std::string>& labels,
LabelsConstraint labelsConstraint,
uint32_t limit) ORTHANC_OVERRIDE
{
if (!labels.empty())
{
throw OrthancException(ErrorCode_InternalError); // "HasLabelsSupport()" has returned "false"
}
if (that_.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.GetSize());
constraintsValues.resize(lookup.GetSize());
for (size_t i = 0; i < lookup.GetSize(); i++)
{
lookup.GetConstraint(i).EncodeForPlugins(constraints[i], constraintsValues[i]);
}
ResetAnswers();
answerMatchingResources_ = &resourcesId;
answerMatchingInstances_ = instancesId;
CheckSuccess(that_.extensions_.lookupResources(that_.GetContext(), that_.payload_, lookup.GetSize(),
(lookup.IsEmpty() ? NULL : &constraints[0]),
Plugins::Convert(queryLevel),
limit, (instancesId == NULL ? 0 : 1)));
}
}
virtual bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result,
int64_t& instanceId,
const std::string& patient,
const std::string& study,
const std::string& series,
const std::string& instance) ORTHANC_OVERRIDE
{
if (that_.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(that_.extensions_.createInstance(&output, that_.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;
}
}
}
virtual void AddAttachment(int64_t id,
const FileInfo& attachment,
int64_t revision) ORTHANC_OVERRIDE
{
// "revision" is not used, as it was added in Orthanc 1.9.2
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(that_.backend_.addAttachment(that_.payload_, id, &tmp));
}
// From the "ICreateInstance" interface
virtual void AttachChild(int64_t parent,
int64_t child) ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.attachChild(that_.payload_, parent, child));
}
virtual void ClearChanges() ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.clearChanges(that_.payload_));
}
virtual void ClearExportedResources() ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.clearExportedResources(that_.payload_));
}
virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
{
if (that_.extensions_.clearMainDicomTags == NULL)
{
throw OrthancException(ErrorCode_DatabasePlugin,
"Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
}
CheckSuccess(that_.extensions_.clearMainDicomTags(that_.payload_, id));
}
// From the "ICreateInstance" interface
virtual int64_t CreateResource(const std::string& publicId,
ResourceType type) ORTHANC_OVERRIDE
{
int64_t id;
CheckSuccess(that_.backend_.createResource(&id, that_.payload_, publicId.c_str(), Plugins::Convert(type)));
return id;
}
virtual void DeleteAttachment(int64_t id,
FileContentType attachment) ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.deleteAttachment(that_.payload_, id, static_cast<int32_t>(attachment)));
}
virtual void DeleteMetadata(int64_t id,
MetadataType type) ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.deleteMetadata(that_.payload_, id, static_cast<int32_t>(type)));
}
virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.deleteResource(that_.payload_, id));
}
// From the "ILookupResources" interface
void GetAllInternalIds(std::list<int64_t>& target,
ResourceType resourceType) ORTHANC_OVERRIDE
{
if (that_.extensions_.getAllInternalIds == NULL)
{
throw OrthancException(ErrorCode_DatabasePlugin,
"The database plugin does not implement the mandatory GetAllInternalIds() extension");
}
ResetAnswers();
CheckSuccess(that_.extensions_.getAllInternalIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
ForwardAnswers(target);
}
virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
int64_t id) ORTHANC_OVERRIDE
{
if (that_.extensions_.getAllMetadata == NULL)
{
// Fallback implementation if extension is missing
target.clear();
ResetAnswers();
CheckSuccess(that_.backend_.listAvailableMetadata(that_.GetContext(), that_.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;
int64_t revision; // Ignored
if (LookupMetadata(value, revision, id, type))
{
target[type] = value;
}
}
}
}
else
{
ResetAnswers();
answerMetadata_ = &target;
target.clear();
CheckSuccess(that_.extensions_.getAllMetadata(that_.GetContext(), that_.payload_, id));
if (type_ != _OrthancPluginDatabaseAnswerType_None &&
type_ != _OrthancPluginDatabaseAnswerType_Metadata)
{
throw OrthancException(ErrorCode_DatabasePlugin);
}
}
}
virtual void GetAllPublicIds(std::list<std::string>& target,
ResourceType resourceType) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.getAllPublicIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
ForwardAnswers(target);
}
virtual void GetAllPublicIdsCompatibility(std::list<std::string>& target,
ResourceType resourceType,
int64_t since,
uint32_t limit) ORTHANC_OVERRIDE
{
if (that_.extensions_.getAllPublicIdsWithLimit != NULL)
{
// This extension is available since Orthanc 0.9.4
ResetAnswers();
CheckSuccess(that_.extensions_.getAllPublicIdsWithLimit
(that_.GetContext(), that_.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() <= static_cast<size_t>(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;
}
}
}
virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
bool& done /*out*/,
int64_t since,
uint32_t limit) ORTHANC_OVERRIDE
{
ResetAnswers();
answerChanges_ = &target;
answerDone_ = &done;
done = false;
CheckSuccess(that_.backend_.getChanges(that_.GetContext(), that_.payload_, since, limit));
}
virtual void GetChildrenInternalId(std::list<int64_t>& target,
int64_t id) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.getChildrenInternalId(that_.GetContext(), that_.payload_, id));
ForwardAnswers(target);
}
virtual void GetChildrenMetadata(std::list<std::string>& target,
int64_t resourceId,
MetadataType metadata) ORTHANC_OVERRIDE
{
if (that_.extensions_.getChildrenMetadata == NULL)
{
IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
}
else
{
ResetAnswers();
CheckSuccess(that_.extensions_.getChildrenMetadata
(that_.GetContext(), that_.payload_, resourceId, static_cast<int32_t>(metadata)));
ForwardAnswers(target);
}
}
virtual void GetChildrenPublicId(std::list<std::string>& target,
int64_t id) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.getChildrenPublicId(that_.GetContext(), that_.payload_, id));
ForwardAnswers(target);
}
virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
bool& done /*out*/,
int64_t since,
uint32_t limit) ORTHANC_OVERRIDE
{
ResetAnswers();
answerExportedResources_ = &target;
answerDone_ = &done;
done = false;
CheckSuccess(that_.backend_.getExportedResources(that_.GetContext(), that_.payload_, since, limit));
}
virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
{
answerDoneIgnored_ = false;
ResetAnswers();
answerChanges_ = &target;
answerDone_ = &answerDoneIgnored_;
CheckSuccess(that_.backend_.getLastChange(that_.GetContext(), that_.payload_));
}
int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
{
if (that_.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(that_.extensions_.getLastChangeIndex(&result, that_.payload_));
return result;
}
}
virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
{
answerDoneIgnored_ = false;
ResetAnswers();
answerExportedResources_ = &target;
answerDone_ = &answerDoneIgnored_;
CheckSuccess(that_.backend_.getLastExportedResource(that_.GetContext(), that_.payload_));
}
virtual void GetMainDicomTags(DicomMap& map,
int64_t id) ORTHANC_OVERRIDE
{
ResetAnswers();
answerDicomMap_ = &map;
CheckSuccess(that_.backend_.getMainDicomTags(that_.GetContext(), that_.payload_, id));
}
virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
{
ResetAnswers();
std::string s;
CheckSuccess(that_.backend_.getPublicId(that_.GetContext(), that_.payload_, resourceId));
if (!ForwardSingleAnswer(s))
{
throw OrthancException(ErrorCode_DatabasePlugin);
}
return s;
}
virtual uint64_t GetResourcesCount(ResourceType resourceType) ORTHANC_OVERRIDE
{
uint64_t count;
CheckSuccess(that_.backend_.getResourceCount(&count, that_.payload_, Plugins::Convert(resourceType)));
return count;
}
virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
{
OrthancPluginResourceType type;
CheckSuccess(that_.backend_.getResourceType(&type, that_.payload_, resourceId));
return Plugins::Convert(type);
}
virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
{
uint64_t size;
CheckSuccess(that_.backend_.getTotalCompressedSize(&size, that_.payload_));
return size;
}
virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
{
uint64_t size;
CheckSuccess(that_.backend_.getTotalUncompressedSize(&size, that_.payload_));
return size;
}
virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
{
if (that_.fastGetTotalSize_)
{
return GetTotalCompressedSize() > threshold;
}
else
{
assert(GetTotalCompressedSize() == that_.currentDiskSize_);
return that_.currentDiskSize_ > threshold;
}
}
virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
{
int32_t isProtected;
CheckSuccess(that_.backend_.isProtectedPatient(&isProtected, that_.payload_, internalId));
return (isProtected != 0);
}
virtual void ListAvailableAttachments(std::set<FileContentType>& target,
int64_t id) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.listAvailableAttachments(that_.GetContext(), that_.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.insert(static_cast<FileContentType>(*it));
}
}
}
virtual void LogChange(ChangeType changeType,
ResourceType resourceType,
int64_t internalId,
const std::string& publicId,
const std::string& date) ORTHANC_OVERRIDE
{
OrthancPluginChange tmp;
tmp.seq = -1; // Unused (it is attributed by the database engine)
tmp.changeType = static_cast<int32_t>(changeType);
tmp.resourceType = Plugins::Convert(resourceType);
tmp.publicId = publicId.c_str();
tmp.date = date.c_str();
CheckSuccess(that_.backend_.logChange(that_.payload_, &tmp));
}
virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
{
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(that_.backend_.logExportedResource(that_.payload_, &tmp));
}
virtual bool LookupAttachment(FileInfo& attachment,
int64_t& revision,
int64_t id,
FileContentType contentType) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.lookupAttachment
(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(contentType)));
revision = 0; // Dummy value, as revisions were added in Orthanc 1.9.2
if (type_ == _OrthancPluginDatabaseAnswerType_None)
{
return false;
}
else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
answerAttachments_.size() == 1)
{
attachment = answerAttachments_.front();
return true;
}
else
{
throw OrthancException(ErrorCode_DatabasePlugin);
}
}
virtual bool LookupGlobalProperty(std::string& target,
GlobalProperty property,
bool shared) ORTHANC_OVERRIDE
{
// "shared" is unused, as database plugins using Orthanc SDK <=
// 1.9.1 are not compatible with multiple readers/writers
ResetAnswers();
CheckSuccess(that_.backend_.lookupGlobalProperty
(that_.GetContext(), that_.payload_, static_cast<int32_t>(property)));
return ForwardSingleAnswer(target);
}
// From the "ILookupResources" interface
virtual void LookupIdentifierRange(std::list<int64_t>& result,
ResourceType level,
const DicomTag& tag,
const std::string& start,
const std::string& end) ORTHANC_OVERRIDE
{
if (that_.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(that_.extensions_.lookupIdentifierRange(that_.GetContext(), that_.payload_, Plugins::Convert(level),
tag.GetGroup(), tag.GetElement(),
start.c_str(), end.c_str()));
ForwardAnswers(result);
}
}
virtual bool LookupMetadata(std::string& target,
int64_t& revision,
int64_t id,
MetadataType type) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.lookupMetadata(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(type)));
revision = 0; // Dummy value, as revisions were added in Orthanc 1.9.2
return ForwardSingleAnswer(target);
}
virtual bool LookupParent(int64_t& parentId,
int64_t resourceId) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.lookupParent(that_.GetContext(), that_.payload_, resourceId));
return ForwardSingleAnswer(parentId);
}
virtual bool LookupResource(int64_t& id,
ResourceType& type,
const std::string& publicId) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.lookupResource(that_.GetContext(), that_.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);
}
}
virtual bool LookupResourceAndParent(int64_t& id,
ResourceType& type,
std::string& parentPublicId,
const std::string& publicId) ORTHANC_OVERRIDE
{
if (that_.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(that_.extensions_.lookupResourceAndParent
(that_.GetContext(), &isExisting, &id, &pluginType, that_.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;
}
}
}
virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.selectPatientToRecycle(that_.GetContext(), that_.payload_));
return ForwardSingleAnswer(internalId);
}
virtual bool SelectPatientToRecycle(int64_t& internalId,
int64_t patientIdToAvoid) ORTHANC_OVERRIDE
{
ResetAnswers();
CheckSuccess(that_.backend_.selectPatientToRecycle2(that_.GetContext(), that_.payload_, patientIdToAvoid));
return ForwardSingleAnswer(internalId);
}
virtual void SetGlobalProperty(GlobalProperty property,
bool shared,
const std::string& value) ORTHANC_OVERRIDE
{
// "shared" is unused, as database plugins using Orthanc SDK <=
// 1.9.1 are not compatible with multiple readers/writers
CheckSuccess(that_.backend_.setGlobalProperty
(that_.payload_, static_cast<int32_t>(property), value.c_str()));
}
// From the "ISetResourcesContent" interface
virtual void SetIdentifierTag(int64_t id,
const DicomTag& tag,
const std::string& value) ORTHANC_OVERRIDE
{
OrthancPluginDicomTag tmp;
tmp.group = tag.GetGroup();
tmp.element = tag.GetElement();
tmp.value = value.c_str();
CheckSuccess(that_.backend_.setIdentifierTag(that_.payload_, id, &tmp));
}
// From the "ISetResourcesContent" interface
virtual void SetMainDicomTag(int64_t id,
const DicomTag& tag,
const std::string& value) ORTHANC_OVERRIDE
{
OrthancPluginDicomTag tmp;
tmp.group = tag.GetGroup();
tmp.element = tag.GetElement();
tmp.value = value.c_str();
CheckSuccess(that_.backend_.setMainDicomTag(that_.payload_, id, &tmp));
}
virtual void SetMetadata(int64_t id,
MetadataType type,
const std::string& value,
int64_t revision) ORTHANC_OVERRIDE
{
// "revision" is not used, as it was added in Orthanc 1.9.2
CheckSuccess(that_.backend_.setMetadata
(that_.payload_, id, static_cast<int32_t>(type), value.c_str()));
}
virtual void SetProtectedPatient(int64_t internalId,
bool isProtected) ORTHANC_OVERRIDE
{
CheckSuccess(that_.backend_.setProtectedPatient(that_.payload_, internalId, isProtected));
}
// From the "ISetResourcesContent" interface
virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
{
if (that_.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->GetResourceId();
tmp.group = it->GetTag().GetGroup();
tmp.element = it->GetTag().GetElement();
tmp.value = it->GetValue().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->GetResourceId();
tmp.metadata = it->GetType();
tmp.value = it->GetValue().c_str();
metadata.push_back(tmp);
}
assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
metadata.size() == content.GetListMetadata().size());
CheckSuccess(that_.extensions_.setResourcesContent(
that_.payload_,
identifierTags.size(),
(identifierTags.empty() ? NULL : &identifierTags[0]),
mainDicomTags.size(),
(mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
metadata.size(),
(metadata.empty() ? NULL : &metadata[0])));
}
}
// From the "ICreateInstance" interface
virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
{
if (that_.extensions_.tagMostRecentPatient != NULL)
{
CheckSuccess(that_.extensions_.tagMostRecentPatient(that_.payload_, patient));
}
}
virtual void AddLabel(int64_t resource,
const std::string& label) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void RemoveLabel(int64_t resource,
const std::string& label) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void ListLabels(std::set<std::string>& target,
int64_t resource) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void ListAllLabels(std::set<std::string>& target) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void StoreKeyValue(const std::string& storeId,
const std::string& key,
const void* value,
size_t valueSize) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void DeleteKeyValue(const std::string& storeId,
const std::string& key) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual bool GetKeyValue(std::string& value,
const std::string& storeId,
const std::string& key) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void ListKeysValues(std::list<std::string>& keys,
std::list<std::string>& values,
const std::string& storeId,
bool first,
const std::string& from,
uint64_t limit) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void EnqueueValue(const std::string& queueId,
const void* value,
size_t valueSize) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual bool DequeueValue(std::string& value,
const std::string& queueId,
QueueOrigin origin) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual uint64_t GetQueueSize(const std::string& queueId) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_InternalError); // Not supported
}
virtual void GetAttachmentCustomData(std::string& customData,
const std::string& attachmentUuid) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_NotImplemented); // Not supported
}
virtual void SetAttachmentCustomData(const std::string& attachmentUuid,
const void* customData,
size_t customDataSize) ORTHANC_OVERRIDE
{
throw OrthancException(ErrorCode_NotImplemented); // Not supported
}
};
void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code)
{
if (code != OrthancPluginErrorCode_Success)
{
errorDictionary_.LogError(code, true);
throw OrthancException(static_cast<ErrorCode>(code));
}
}
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),
activeTransaction_(NULL),
fastGetTotalSize_(false),
currentDiskSize_(0)
{
static const char* const MISSING = " Missing extension in database index plugin: ";
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)
{
CLOG(INFO, PLUGINS) << MISSING << "LookupIdentifierRange()";
isOptimal = false;
}
if (extensions_.createInstance == NULL)
{
CLOG(INFO, PLUGINS) << MISSING << "CreateInstance()";
isOptimal = false;
}
if (extensions_.setResourcesContent == NULL)
{
CLOG(INFO, PLUGINS) << MISSING << "SetResourcesContent()";
isOptimal = false;
}
if (extensions_.getChildrenMetadata == NULL)
{
CLOG(INFO, PLUGINS) << MISSING << "GetChildrenMetadata()";
isOptimal = false;
}
if (extensions_.getAllMetadata == NULL)
{
CLOG(INFO, PLUGINS) << MISSING << "GetAllMetadata()";
isOptimal = false;
}
if (extensions_.lookupResourceAndParent == NULL)
{
CLOG(INFO, PLUGINS) << MISSING << "LookupResourceAndParent()";
isOptimal = false;
}
if (isOptimal)
{
CLOG(INFO, PLUGINS) << "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()
{
{
boost::recursive_mutex::scoped_lock lock(mutex_);
CheckSuccess(backend_.open(payload_));
}
VoidDatabaseListener listener;
{
Transaction transaction(*this, listener);
transaction.Begin();
std::string tmp;
fastGetTotalSize_ =
(transaction.LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast, true /* unused in old databases */) &&
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_ = transaction.GetTotalCompressedSize();
}
transaction.Commit(0);
}
}
void OrthancPluginDatabase::Close()
{
boost::recursive_mutex::scoped_lock lock(mutex_);
CheckSuccess(backend_.close(payload_));
}
IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction(TransactionType type,
IDatabaseListener& listener)
{
// TODO - Take advantage of "type"
std::unique_ptr<Transaction> transaction(new Transaction(*this, listener));
transaction->Begin();
return transaction.release();
}
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,
IPluginStorageArea& storageArea)
{
VoidDatabaseListener listener;
if (extensions_.upgradeDatabase != NULL)
{
Transaction transaction(*this, listener);
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)
{
boost::recursive_mutex::scoped_lock lock(mutex_);
if (activeTransaction_ != NULL)
{
activeTransaction_->AnswerReceived(answer);
}
else
{
LOG(WARNING) << "Received an answer from the database index plugin, but not transaction is active";
}
}
uint64_t OrthancPluginDatabase::MeasureLatency()
{
throw OrthancException(ErrorCode_NotImplemented);
}
}