diff OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp @ 5273:7cb1b851f5c8

Added a sample plugin bringing multitenant DICOM support through labels
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 14 Apr 2023 11:49:24 +0200
parents
children 49477780e25a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp	Fri Apr 14 11:49:24 2023 +0200
@@ -0,0 +1,231 @@
+/**
+ * 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) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "MoveRequestHandler.h"
+
+#include "PluginToolbox.h"
+
+#include "../../../../OrthancFramework/Sources/OrthancException.h"
+#include "../../../../OrthancFramework/Sources/Toolbox.h"
+
+#include "../Common/OrthancPluginCppWrapper.h"
+
+
+class MoveRequestHandler::Iterator : public Orthanc::IMoveRequestIterator
+{
+private:
+  std::string  targetModality_;
+  Json::Value  body_;
+  bool         done_;
+
+public:
+  Iterator(const std::string& targetModality,
+           const Json::Value& body) :
+    targetModality_(targetModality),
+    body_(body),
+    done_(false)
+  {
+  }
+
+  unsigned int GetSubOperationCount() const
+  {
+    return 1;
+  }
+
+  Status DoNext()
+  {
+    Json::Value answer;
+
+    if (done_)
+    {
+      return Status_Failure;
+    }
+    else if (OrthancPlugins::RestApiPost(answer, "/modalities/" + targetModality_ + "/store", body_, false))
+    {
+      done_ = true;
+      return Status_Success;
+    }
+    else
+    {
+      done_ = true;
+      return Status_Failure;
+    }
+  }
+};
+
+
+void MoveRequestHandler::ExecuteLookup(std::set<std::string>& publicIds,
+                                       Orthanc::ResourceType level,
+                                       const Orthanc::DicomTag& tag,
+                                       const std::string& value) const
+{
+  std::vector<std::string> tokens;
+  Orthanc::Toolbox::TokenizeString(tokens, value, '\\');
+
+  for (size_t i = 0; i < tokens.size(); i++)
+  {
+    if (!tokens[i].empty())
+    {
+      Json::Value request = Json::objectValue;
+      request["Level"] = Orthanc::EnumerationToString(level);
+      request["Query"][tag.Format()] = tokens[i];
+      PluginToolbox::AddLabelsToFindRequest(request, labels_, constraint_);
+
+      Json::Value response;
+      if (OrthancPlugins::RestApiPost(response, "/tools/find", request, false) &&
+          response.type() == Json::arrayValue)
+      {
+        for (Json::Value::ArrayIndex i = 0; i < response.size(); i++)
+        {
+          if (response[i].type() != Json::stringValue)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+          }
+          else
+          {
+            publicIds.insert(response[i].asString());
+          }
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+}
+
+
+void MoveRequestHandler::LookupIdentifiers(std::set<std::string>& publicIds,
+                                           Orthanc::ResourceType level,
+                                           const Orthanc::DicomMap& input) const
+{
+  std::string value;
+
+  switch (level)
+  {
+    case Orthanc::ResourceType_Patient:
+      if (input.LookupStringValue(value, Orthanc::DICOM_TAG_PATIENT_ID, false) &&
+          !value.empty())
+      {
+        ExecuteLookup(publicIds, level, Orthanc::DICOM_TAG_PATIENT_ID, value);
+      }
+      break;
+
+    case Orthanc::ResourceType_Study:
+      if (input.LookupStringValue(value, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
+          !value.empty())
+      {
+        ExecuteLookup(publicIds, level, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, value);
+      }
+      else if (input.LookupStringValue(value, Orthanc::DICOM_TAG_ACCESSION_NUMBER, false) &&
+               !value.empty())
+      {
+        ExecuteLookup(publicIds, level, Orthanc::DICOM_TAG_ACCESSION_NUMBER, value);
+      }
+      break;
+
+    case Orthanc::ResourceType_Series:
+      if (input.LookupStringValue(value, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) &&
+          !value.empty())
+      {
+        ExecuteLookup(publicIds, level, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, value);
+      }
+      break;
+
+    case Orthanc::ResourceType_Instance:
+      if (input.LookupStringValue(value, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) &&
+          !value.empty())
+      {
+        ExecuteLookup(publicIds, level, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, value);
+      }
+      break;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+}
+
+
+Orthanc::IMoveRequestIterator* MoveRequestHandler::Handle(const std::string& targetAet,
+                                                          const Orthanc::DicomMap& input,
+                                                          const std::string& originatorIp,
+                                                          const std::string& originatorAet,
+                                                          const std::string& calledAet,
+                                                          uint16_t originatorId)
+{
+  std::set<std::string> publicIds;
+
+  std::string s;
+  if (input.LookupStringValue(s, Orthanc::DICOM_TAG_QUERY_RETRIEVE_LEVEL, false) &&
+      !s.empty())
+  {
+    LookupIdentifiers(publicIds, PluginToolbox::ParseQueryRetrieveLevel(s), input);
+  }
+  else
+  {
+    // 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.
+    LookupIdentifiers(publicIds, Orthanc::ResourceType_Instance, input);
+
+    if (publicIds.empty())
+    {
+      LookupIdentifiers(publicIds, Orthanc::ResourceType_Series, input);
+    }
+
+    if (publicIds.empty())
+    {
+      LookupIdentifiers(publicIds, Orthanc::ResourceType_Study, input);
+    }
+
+    if (publicIds.empty())
+    {
+      LookupIdentifiers(publicIds, Orthanc::ResourceType_Patient, input);
+    }
+  }
+
+  Json::Value resources = Json::arrayValue;
+  for (std::set<std::string>::const_iterator it = publicIds.begin(); it != publicIds.end(); ++it)
+  {
+    resources.append(*it);
+  }
+
+  std::string targetName;
+  Orthanc::RemoteModalityParameters targetParameters;
+  if (!PluginToolbox::LookupAETitle(targetName, targetParameters, isStrictAet_, targetAet))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Unknown target AET: " + targetAet);
+  }
+
+  Json::Value body;
+  body["CalledAet"] = calledAet;
+  body["MoveOriginatorAet"] = originatorAet;
+  body["MoveOriginatorID"] = originatorId;
+  body["Resources"] = resources;
+  body["Synchronous"] = isSynchronous_;
+
+  return new Iterator(targetName, body);
+}