Mercurial > hg > orthanc-dicomweb
changeset 294:ca7f14c91abf refactoring
change in orthanc sdk api
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 11 Jun 2019 19:44:29 +0200 |
parents | 7e3989d2ea5a |
children | a0e72a40f5d5 |
files | CMakeLists.txt Plugin/Plugin.cpp Plugin/StowRs.cpp Plugin/StowRs.h |
diffstat | 4 files changed, 236 insertions(+), 209 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sat Jun 08 18:10:32 2019 +0200 +++ b/CMakeLists.txt Tue Jun 11 19:44:29 2019 +0200 @@ -113,11 +113,7 @@ add_definitions( -DHAS_ORTHANC_EXCEPTION=1 - -DHAS_ORTHANC_FRAMEWORK=1 -DORTHANC_ENABLE_LOGGING_PLUGIN=1 - -DORTHANC_FRAMEWORK_VERSION_MAJOR=${ORTHANC_FRAMEWORK_MAJOR} - -DORTHANC_FRAMEWORK_VERSION_MINOR=${ORTHANC_FRAMEWORK_MINOR} - -DORTHANC_FRAMEWORK_VERSION_REVISION=${ORTHANC_FRAMEWORK_REVISION} ) set(CORE_SOURCES
--- a/Plugin/Plugin.cpp Sat Jun 08 18:10:32 2019 +0200 +++ b/Plugin/Plugin.cpp Tue Jun 11 19:44:29 2019 +0200 @@ -31,47 +31,56 @@ #include <Core/Toolbox.h> -void SwitchStudies(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPlugins::IChunkedRequestReader* SwitchStudies(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { switch (request->method) { case OrthancPluginHttpMethod_Get: // This is QIDO-RS SearchForStudies(output, url, request); - break; + return NULL; case OrthancPluginHttpMethod_Post: - // This is STOW-RS: This should have been processed by the MultipartRestCallback + // This is STOW-RS + return OrthancPlugins::StowServer::PostCallback(url, request); default: - OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST"); - break; + if (output != NULL) + { + OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST"); // TODO + } + return NULL; } } -void SwitchIndividualStudy(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) +OrthancPlugins::IChunkedRequestReader* SwitchIndividualStudy(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) { switch (request->method) { case OrthancPluginHttpMethod_Get: // This is WADO-RS RetrieveDicomStudy(output, url, request); - break; + return NULL; case OrthancPluginHttpMethod_Post: - // This is STOW-RS: This should have been processed by the MultipartRestCallback + // This is STOW-RS + return OrthancPlugins::StowServer::PostCallback(url, request); default: - OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST"); - break; + if (output != NULL) + { + OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET,POST"); // TODO + } + return NULL; } } + bool RequestHasKey(const OrthancPluginHttpRequest* request, const char* key) { for (uint32_t i = 0; i < request->getCount; i++) @@ -167,9 +176,6 @@ -static OrthancPlugins::StowServer stowServer_; - - #include <boost/filesystem.hpp> #include <Core/SystemToolbox.h> @@ -319,13 +325,11 @@ OrthancPlugins::LogWarning("URI to the DICOMweb REST API: " + root); - stowServer_.Register(root + "studies"); - stowServer_.Register(root + "studies/([^/]*)"); + OrthancPlugins::RegisterChunkedRestCallback<SwitchStudies>(root + "studies"); + OrthancPlugins::RegisterChunkedRestCallback<SwitchIndividualStudy>(root + "studies/([^/]*)"); OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "instances", true); OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "series", true); - OrthancPlugins::RegisterRestCallback<SwitchStudies>(root + "studies", true); - OrthancPlugins::RegisterRestCallback<SwitchIndividualStudy>(root + "studies/([^/]*)", true); OrthancPlugins::RegisterRestCallback<SearchForInstances>(root + "studies/([^/]*)/instances", true); OrthancPlugins::RegisterRestCallback<RetrieveStudyMetadata>(root + "studies/([^/]*)/metadata", true); OrthancPlugins::RegisterRestCallback<SearchForSeries>(root + "studies/([^/]*)/series", true);
--- a/Plugin/StowRs.cpp Sat Jun 08 18:10:32 2019 +0200 +++ b/Plugin/StowRs.cpp Tue Jun 11 19:44:29 2019 +0200 @@ -27,181 +27,24 @@ namespace OrthancPlugins { - class StowServer::Handler : public IHandler - { - private: - OrthancPluginContext* context_; - bool xml_; - std::string wadoBase_; - std::string expectedStudy_; - bool isFirst_; - Json::Value result_; - Json::Value success_; - Json::Value failed_; - - public: - Handler(OrthancPluginContext* context, - bool xml, - const std::string& wadoBase, - const std::string& expectedStudy) : - context_(context), - xml_(xml), - wadoBase_(wadoBase), - expectedStudy_(expectedStudy), - isFirst_(true), - result_(Json::objectValue), - success_(Json::arrayValue), - failed_(Json::arrayValue) - { - } - - virtual OrthancPluginErrorCode AddPart(const std::string& contentType, - const std::map<std::string, std::string>& headers, - const void* data, - size_t size) + StowServer::StowServer(OrthancPluginContext* context, + const std::map<std::string, std::string>& headers, + const std::string& expectedStudy) : + context_(context), + xml_(Configuration::IsXmlExpected(headers)), + wadoBase_(Configuration::GetBaseUrl(headers)), + expectedStudy_(expectedStudy), + isFirst_(true), + result_(Json::objectValue), + success_(Json::arrayValue), + failed_(Json::arrayValue) + { + std::string tmp, contentType, subType, boundary; + if (!Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers) || + !Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp)) { - if (contentType != "application/dicom") - { - throw Orthanc::OrthancException( - Orthanc::ErrorCode_UnsupportedMediaType, - "The STOW-RS request contains a part that is not " - "\"application/dicom\" (it is: \"" + contentType + "\")"); - } - - Json::Value dicom; - - try - { - OrthancString s; - s.Assign(OrthancPluginDicomBufferToJson(context_, data, size, - OrthancPluginDicomToJsonFormat_Short, - OrthancPluginDicomToJsonFlags_None, 256)); - s.ToJson(dicom); - } - catch (Orthanc::OrthancException&) - { - // Bad DICOM file => TODO add to error - LogWarning("STOW-RS cannot parse an incoming DICOM file"); - return OrthancPluginErrorCode_Success; - } - - if (dicom.type() != Json::objectValue || - !dicom.isMember(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()) || - !dicom.isMember(Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()) || - !dicom.isMember(Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()) || - !dicom.isMember(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()) || - dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].type() != Json::stringValue || - dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].type() != Json::stringValue || - dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue || - dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue) - { - LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file"); - return OrthancPluginErrorCode_Success; - } - - const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString(); - const std::string sopClassUid = dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].asString(); - const std::string sopInstanceUid = dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].asString(); - const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString(); - - Json::Value item = Json::objectValue; - item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid; - item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid; - - if (!expectedStudy_.empty() && - studyInstanceUid != expectedStudy_) - { - LogInfo("STOW-RS request restricted to study [" + expectedStudy_ + - "]: Ignoring instance from study [" + studyInstanceUid + "]"); - - /*item[DICOM_TAG_WARNING_REASON.Format()] = - boost::lexical_cast<std::string>(0xB006); // Elements discarded - success.append(item);*/ - } - else - { - if (isFirst_) - { - std::string url = wadoBase_ + "studies/" + studyInstanceUid; - result_[DICOM_TAG_RETRIEVE_URL.Format()] = url; - isFirst_ = false; - } - - MemoryBuffer tmp; - bool ok = tmp.RestApiPost("/instances", data, size, false); - tmp.Clear(); - - if (ok) - { - std::string url = (wadoBase_ + - "studies/" + studyInstanceUid + - "/series/" + seriesInstanceUid + - "/instances/" + sopInstanceUid); - - item[DICOM_TAG_RETRIEVE_URL.Format()] = url; - success_.append(item); - } - else - { - LogError("Orthanc was unable to store one instance in a STOW-RS request"); - item[DICOM_TAG_FAILURE_REASON.Format()] = - boost::lexical_cast<std::string>(0x0110); // Processing failure - failed_.append(item); - } - } - - return OrthancPluginErrorCode_Success; - } - - virtual OrthancPluginErrorCode Execute(OrthancPluginRestOutput* output) - { - result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_; - result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_; - - std::string answer; - - { - DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, ""); - locker.Apply(answer, context_, result_, xml_); - } - - OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(), - xml_ ? "application/dicom+xml" : "application/dicom+json"); - - return OrthancPluginErrorCode_Success; - } - }; - - - StowServer::IHandler* StowServer::CreateHandler(OrthancPluginHttpMethod method, - const std::string& url, - const std::string& contentType, - const std::string& subType, - const std::vector<std::string>& groups, - const std::map<std::string, std::string>& headers) - { - OrthancPluginContext* context = GetGlobalContext(); - - if (method != OrthancPluginHttpMethod_Post) - { - return NULL; - } - - const std::string wadoBase = Configuration::GetBaseUrl(headers); - - std::string expectedStudy; - if (groups.size() == 1) - { - expectedStudy = groups[0]; - } - - if (expectedStudy.empty()) - { - LogInfo("STOW-RS request without study"); - } - else - { - LogInfo("STOW-RS request restricted to study UID " + expectedStudy); + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType, + "The STOW-RS server expects a multipart body in its request"); } if (contentType != "multipart/related") @@ -216,6 +59,169 @@ "The STOW-RS plugin currently only supports \"application/dicom\" subtype"); } - return new Handler(context, Configuration::IsXmlExpected(headers), wadoBase, expectedStudy); + parser_.reset(new Orthanc::MultipartStreamReader(boundary)); + parser_->SetHandler(*this); + } + + + void StowServer::HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers, + const void* part, + size_t size) + { + std::string contentType; + + if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers) || + contentType != "application/dicom") + { + throw Orthanc::OrthancException( + Orthanc::ErrorCode_UnsupportedMediaType, + "The STOW-RS request contains a part that is not " + "\"application/dicom\" (it is: \"" + contentType + "\")"); + } + + Json::Value dicom; + + try + { + OrthancString s; + s.Assign(OrthancPluginDicomBufferToJson(context_, part, size, + OrthancPluginDicomToJsonFormat_Short, + OrthancPluginDicomToJsonFlags_None, 256)); + s.ToJson(dicom); + } + catch (Orthanc::OrthancException&) + { + // Bad DICOM file => TODO add to error + LogWarning("STOW-RS cannot parse an incoming DICOM file"); + return; + } + + if (dicom.type() != Json::objectValue || + !dicom.isMember(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()) || + !dicom.isMember(Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()) || + !dicom.isMember(Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()) || + !dicom.isMember(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()) || + dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].type() != Json::stringValue || + dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].type() != Json::stringValue || + dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue || + dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue) + { + LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file"); + return; + } + + const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString(); + const std::string sopClassUid = dicom[Orthanc::DICOM_TAG_SOP_CLASS_UID.Format()].asString(); + const std::string sopInstanceUid = dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].asString(); + const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString(); + + Json::Value item = Json::objectValue; + item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid; + item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid; + + if (!expectedStudy_.empty() && + studyInstanceUid != expectedStudy_) + { + LogInfo("STOW-RS request restricted to study [" + expectedStudy_ + + "]: Ignoring instance from study [" + studyInstanceUid + "]"); + + /*item[DICOM_TAG_WARNING_REASON.Format()] = + boost::lexical_cast<std::string>(0xB006); // Elements discarded + success.append(item);*/ + } + else + { + if (isFirst_) + { + std::string url = wadoBase_ + "studies/" + studyInstanceUid; + result_[DICOM_TAG_RETRIEVE_URL.Format()] = url; + isFirst_ = false; + } + + MemoryBuffer tmp; + bool ok = tmp.RestApiPost("/instances", part, size, false); + tmp.Clear(); + + if (ok) + { + std::string url = (wadoBase_ + + "studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + + "/instances/" + sopInstanceUid); + + item[DICOM_TAG_RETRIEVE_URL.Format()] = url; + success_.append(item); + } + else + { + LogError("Orthanc was unable to store one instance in a STOW-RS request"); + item[DICOM_TAG_FAILURE_REASON.Format()] = + boost::lexical_cast<std::string>(0x0110); // Processing failure + failed_.append(item); + } + } + } + + + void StowServer::AddChunk(const void* data, + size_t size) + { + assert(parser_.get() != NULL); + parser_->AddChunk(data, size); + } + + + void StowServer::Execute(OrthancPluginRestOutput* output) + { + assert(parser_.get() != NULL); + parser_->CloseStream(); + + result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_; + result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_; + + std::string answer; + + { + DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, ""); + locker.Apply(answer, context_, result_, xml_); + } + + OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(), + xml_ ? "application/dicom+xml" : "application/dicom+json"); + }; + + + IChunkedRequestReader* StowServer::PostCallback(const char* url, + const OrthancPluginHttpRequest* request) + { + OrthancPluginContext* context = GetGlobalContext(); + + if (request->method != OrthancPluginHttpMethod_Post) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + std::map<std::string, std::string> headers; + for (uint32_t i = 0; i < request->headersCount; i++) + { + headers[request->headersKeys[i]] = request->headersValues[i]; + } + + std::string expectedStudy; + if (request->groupsCount == 1) + { + expectedStudy = request->groups[0]; + } + + if (expectedStudy.empty()) + { + LogInfo("STOW-RS request without study"); + } + else + { + LogInfo("STOW-RS request restricted to study UID " + expectedStudy); + } + + return new StowServer(context, headers, expectedStudy); } }
--- a/Plugin/StowRs.h Sat Jun 08 18:10:32 2019 +0200 +++ b/Plugin/StowRs.h Tue Jun 11 19:44:29 2019 +0200 @@ -21,21 +21,42 @@ #pragma once +#include <Core/HttpServer/MultipartStreamReader.h> #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h> namespace OrthancPlugins { - class StowServer : public MultipartRestCallback + class StowServer : + public IChunkedRequestReader, + private Orthanc::MultipartStreamReader::IHandler { private: - class Handler; - + OrthancPluginContext* context_; + bool xml_; + std::string wadoBase_; + std::string expectedStudy_; + bool isFirst_; + Json::Value result_; + Json::Value success_; + Json::Value failed_; + + std::auto_ptr<Orthanc::MultipartStreamReader> parser_; + + virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers, + const void* part, + size_t size); + public: - virtual IHandler* CreateHandler(OrthancPluginHttpMethod method, - const std::string& url, - const std::string& contentType, - const std::string& subType, - const std::vector<std::string>& groups, - const std::map<std::string, std::string>& headers); + StowServer(OrthancPluginContext* context, + const std::map<std::string, std::string>& headers, + const std::string& expectedStudy); + + virtual void AddChunk(const void* data, + size_t size); + + virtual void Execute(OrthancPluginRestOutput* output); + + static IChunkedRequestReader* PostCallback(const char* url, + const OrthancPluginHttpRequest* request); }; }