Mercurial > hg > orthanc
diff OrthancServer/Sources/OrthancMoveRequestHandler.cpp @ 4092:fb64d481940a
making the "framework" branch the new "default"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 30 Jun 2020 15:53:17 +0200 |
parents | 05b8fd21089c |
children | e34c89e89aac |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Tue Jun 30 15:53:17 2020 +0200 @@ -0,0 +1,379 @@ +/** + * 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 "PrecompiledHeadersServer.h" +#include "OrthancMoveRequestHandler.h" + +#include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" +#include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../OrthancFramework/Sources/Logging.h" +#include "../../OrthancFramework/Sources/MetricsRegistry.h" + +#include "OrthancConfiguration.h" +#include "ServerContext.h" +#include "ServerJobs/DicomModalityStoreJob.h" + + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + class SynchronousMove : public IMoveRequestIterator + { + private: + ServerContext& context_; + const std::string& localAet_; + std::vector<std::string> instances_; + size_t position_; + RemoteModalityParameters remote_; + std::string originatorAet_; + uint16_t originatorId_; + std::unique_ptr<DicomStoreUserConnection> connection_; + + public: + SynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::vector<std::string>& publicIds, + const std::string& originatorAet, + uint16_t originatorId) : + context_(context), + localAet_(context.GetDefaultLocalApplicationEntityTitle()), + position_(0), + originatorAet_(originatorAet), + originatorId_(originatorId) + { + { + OrthancConfiguration::ReaderLock lock; + remote_ = lock.GetConfiguration().GetModalityUsingAet(targetAet); + } + + for (size_t i = 0; i < publicIds.size(); i++) + { + LOG(INFO) << "Sending resource " << publicIds[i] << " to modality \"" + << targetAet << "\" in synchronous mode"; + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicIds[i]); + + instances_.reserve(tmp.size()); + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + instances_.push_back(*it); + } + } + } + + virtual unsigned int GetSubOperationCount() const + { + return instances_.size(); + } + + virtual Status DoNext() + { + if (position_ >= instances_.size()) + { + return Status_Failure; + } + + const std::string& id = instances_[position_++]; + + std::string dicom; + context_.ReadDicom(dicom, id); + + if (connection_.get() == NULL) + { + DicomAssociationParameters params(localAet_, remote_); + connection_.reset(new DicomStoreUserConnection(params)); + } + + std::string sopClassUid, sopInstanceUid; // Unused + + const void* data = dicom.empty() ? NULL : dicom.c_str(); + connection_->Store(sopClassUid, sopInstanceUid, data, dicom.size(), + true, originatorAet_, originatorId_); + + return Status_Success; + } + }; + + + class AsynchronousMove : public IMoveRequestIterator + { + private: + ServerContext& context_; + std::unique_ptr<DicomModalityStoreJob> job_; + size_t position_; + size_t countInstances_; + + public: + AsynchronousMove(ServerContext& context, + const std::string& targetAet, + const std::vector<std::string>& publicIds, + const std::string& originatorAet, + uint16_t originatorId) : + context_(context), + job_(new DicomModalityStoreJob(context)), + position_(0) + { + job_->SetDescription("C-MOVE"); + //job_->SetPermissive(true); // This was the behavior of Orthanc < 1.6.0 + job_->SetPermissive(false); + job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle()); + + { + OrthancConfiguration::ReaderLock lock; + job_->SetRemoteModality(lock.GetConfiguration().GetModalityUsingAet(targetAet)); + } + + if (originatorId != 0) + { + job_->SetMoveOriginator(originatorAet, originatorId); + } + + for (size_t i = 0; i < publicIds.size(); i++) + { + LOG(INFO) << "Sending resource " << publicIds[i] << " to modality \"" + << targetAet << "\" in asynchronous mode"; + + std::list<std::string> tmp; + context_.GetIndex().GetChildInstances(tmp, publicIds[i]); + + countInstances_ = tmp.size(); + + job_->Reserve(job_->GetCommandsCount() + tmp.size()); + + for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) + { + job_->AddInstance(*it); + } + } + } + + virtual unsigned int GetSubOperationCount() const + { + return countInstances_; + } + + virtual Status DoNext() + { + if (position_ >= countInstances_) + { + return Status_Failure; + } + + if (position_ == 0) + { + context_.GetJobsEngine().GetRegistry().Submit(job_.release(), 0 /* priority */); + } + + position_ ++; + return Status_Success; + } + }; + } + + + bool OrthancMoveRequestHandler::LookupIdentifiers(std::vector<std::string>& publicIds, + ResourceType level, + const DicomMap& input) + { + DicomTag tag(0, 0); // Dummy initialization + + switch (level) + { + case ResourceType_Patient: + tag = DICOM_TAG_PATIENT_ID; + break; + + case ResourceType_Study: + tag = (input.HasTag(DICOM_TAG_ACCESSION_NUMBER) ? + DICOM_TAG_ACCESSION_NUMBER : DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + tag = DICOM_TAG_SERIES_INSTANCE_UID; + break; + + case ResourceType_Instance: + tag = DICOM_TAG_SOP_INSTANCE_UID; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (!input.HasTag(tag)) + { + return false; + } + + const DicomValue& value = input.GetValue(tag); + if (value.IsNull() || + value.IsBinary()) + { + return false; + } + else + { + const std::string& content = value.GetContent(); + + /** + * This tokenization fixes issue 154 ("Matching against list of + * UID-s by C-MOVE"). + * https://bitbucket.org/sjodogne/orthanc/issues/154/ + **/ + + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, content, '\\'); + for (size_t i = 0; i < tokens.size(); i++) + { + std::vector<std::string> matches; + context_.GetIndex().LookupIdentifierExact(matches, level, tag, tokens[i]); + + // Concatenate "publicIds" with "matches" + publicIds.insert(publicIds.end(), matches.begin(), matches.end()); + } + + return true; + } + } + + + static IMoveRequestIterator* CreateIterator(ServerContext& context, + const std::string& targetAet, + const std::vector<std::string>& publicIds, + const std::string& originatorAet, + uint16_t originatorId) + { + if (publicIds.empty()) + { + throw OrthancException(ErrorCode_BadRequest, + "C-MOVE request matching no resource stored in Orthanc"); + } + + bool synchronous; + + { + OrthancConfiguration::ReaderLock lock; + synchronous = lock.GetConfiguration().GetBooleanParameter("SynchronousCMove", true); + } + + if (synchronous) + { + return new SynchronousMove(context, targetAet, publicIds, originatorAet, originatorId); + } + else + { + return new AsynchronousMove(context, targetAet, publicIds, originatorAet, originatorId); + } + } + + + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) + { + MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_move_scp_duration_ms"); + + LOG(WARNING) << "Move-SCU request received for AET \"" << targetAet << "\""; + + { + DicomArray query(input); + for (size_t i = 0; i < query.GetSize(); i++) + { + if (!query.GetElement(i).GetValue().IsNull()) + { + LOG(INFO) << " " << query.GetElement(i).GetTag() + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) + << " = " << query.GetElement(i).GetValue().GetContent(); + } + } + } + + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + + if (levelTmp == NULL || + levelTmp->IsNull() || + levelTmp->IsBinary()) + { + // The query level is not present in the C-Move request, which + // does not follow the DICOM standard. This is for instance the + // behavior of Tudor DICOM. Try and automatically deduce the + // query level: Start from the instance level, going up to the + // patient level until a valid DICOM identifier is found. + + std::vector<std::string> publicIds; + + if (LookupIdentifiers(publicIds, ResourceType_Instance, input) || + LookupIdentifiers(publicIds, ResourceType_Series, input) || + LookupIdentifiers(publicIds, ResourceType_Study, input) || + LookupIdentifiers(publicIds, ResourceType_Patient, input)) + { + return CreateIterator(context_, targetAet, publicIds, originatorAet, originatorId); + } + else + { + // No identifier is present in the request. + throw OrthancException(ErrorCode_BadRequest, "Invalid fields in a C-MOVE request"); + } + } + + assert(levelTmp != NULL); + ResourceType level = StringToResourceType(levelTmp->GetContent().c_str()); + + + /** + * Lookup for the resource to be sent. + **/ + + std::vector<std::string> publicIds; + + if (LookupIdentifiers(publicIds, level, input)) + { + return CreateIterator(context_, targetAet, publicIds, originatorAet, originatorId); + } + else + { + throw OrthancException(ErrorCode_BadRequest, "Invalid fields in a C-MOVE request"); + } + } +}