# HG changeset patch # User Sebastien Jodogne # Date 1681465764 -7200 # Node ID 7cb1b851f5c8fa141fdc40bb36d3f42b4acf007f # Parent a45e8b6115f60b58bff9b61012ec8dfe62a5545a Added a sample plugin bringing multitenant DICOM support through labels diff -r a45e8b6115f6 -r 7cb1b851f5c8 NEWS --- a/NEWS Thu Apr 13 21:17:55 2023 +0200 +++ b/NEWS Fri Apr 14 11:49:24 2023 +0200 @@ -5,6 +5,7 @@ ------- * Support for labels associated with patients, studies, series, and instances +* Added a sample plugin bringing multitenant DICOM support through labels REST API -------- diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Apr 13 21:17:55 2023 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -93,7 +93,9 @@ #endif #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 -# include +# if !defined(ORTHANC_FRAMEWORK_INCLUDE_RESOURCES) || (ORTHANC_FRAMEWORK_INCLUDE_RESOURCES == 1) +# include +# endif #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancFramework/Sources/Toolbox.cpp --- a/OrthancFramework/Sources/Toolbox.cpp Thu Apr 13 21:17:55 2023 +0200 +++ b/OrthancFramework/Sources/Toolbox.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -149,7 +149,9 @@ #if defined(ORTHANC_STATIC_ICU) # if (ORTHANC_STATIC_ICU == 1) && (ORTHANC_ENABLE_ICU == 1) -# include +# if !defined(ORTHANC_FRAMEWORK_INCLUDE_RESOURCES) || (ORTHANC_FRAMEWORK_INCLUDE_RESOURCES == 1) +# include +# endif # endif # if (ORTHANC_STATIC_ICU == 1 && ORTHANC_ENABLE_LOCALE == 1) diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/CMakeLists.txt --- a/OrthancServer/CMakeLists.txt Thu Apr 13 21:17:55 2023 +0200 +++ b/OrthancServer/CMakeLists.txt Fri Apr 14 11:49:24 2023 +0200 @@ -61,6 +61,7 @@ SET(BUILD_CONNECTIVITY_CHECKS ON CACHE BOOL "Whether to build the ConnectivityChecks plugin") SET(BUILD_HOUSEKEEPER ON CACHE BOOL "Whether to build the Housekeeper plugin") SET(BUILD_DELAYED_DELETION ON CACHE BOOL "Whether to build the DelayedDeletion plugin") +SET(BUILD_MULTITENANT_DICOM ON CACHE BOOL "Whether to build the MultitenantDicom plugin") SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins") SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests") @@ -466,12 +467,12 @@ ##################################################################### -## Build a static library to share code between the plugins +## Static library to share third-party libraries between the plugins ##################################################################### if (ENABLE_PLUGINS AND (BUILD_SERVE_FOLDERS OR BUILD_MODALITY_WORKLISTS OR BUILD_HOUSEKEEPER OR - BUILD_DELAYED_DELETION)) + BUILD_DELAYED_DELETION OR BUILD_MULTITENANT_DICOM)) set(PLUGINS_DEPENDENCIES_SOURCES ${BOOST_SOURCES} ${JSONCPP_SOURCES} @@ -488,8 +489,16 @@ if (BUILD_DELAYED_DELETION) list(APPEND PLUGINS_DEPENDENCIES_SOURCES + ${SQLITE_SOURCES} + ) + endif() + + if (BUILD_MULTITENANT_DICOM) + list(APPEND PLUGINS_DEPENDENCIES_SOURCES ${DCMTK_SOURCES} - ${SQLITE_SOURCES} + ${OPENSSL_SOURCES} + ${LIBJPEG_SOURCES} + ${LIBPNG_SOURCES} ) endif() @@ -642,6 +651,7 @@ add_library(ConnectivityChecks SHARED ${AUTOGENERATED_DIR}/ConnectivityChecksResources.cpp ${CMAKE_SOURCE_DIR}/Plugins/Samples/ConnectivityChecks/Plugin.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/ConnectivityChecks/OrthancFrameworkDependencies.cpp ${CONNECTIVITY_CHECKS_RESOURCES} ) @@ -693,11 +703,12 @@ add_library(DelayedDeletion SHARED ${CMAKE_SOURCE_DIR}/Plugins/Samples/DelayedDeletion/PendingDeletionsDatabase.cpp ${CMAKE_SOURCE_DIR}/Plugins/Samples/DelayedDeletion/Plugin.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/DelayedDeletion/OrthancFrameworkDependencies.cpp ${DELAYED_DELETION_RESOURCES} ) - target_link_libraries(DelayedDeletion PluginsDependencies ${DCMTK_LIBRARIES}) + target_link_libraries(DelayedDeletion PluginsDependencies) set_target_properties( DelayedDeletion PROPERTIES @@ -761,6 +772,71 @@ ##################################################################### +## Build the "MultitenantDicom" plugin +##################################################################### + +if (ENABLE_PLUGINS AND BUILD_MULTITENANT_DICOM) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../OrthancFramework/Resources/WindowsResources.py + ${ORTHANC_VERSION} MultitenantDicom MultitenantDicom.dll "Orthanc plugin to provide a multitenant DICOM server" + ERROR_VARIABLE Failure + OUTPUT_FILE ${AUTOGENERATED_DIR}/MultitenantDicom.rc + ) + + if (Failure) + message(FATAL_ERROR "Error while computing the version information: ${Failure}") + endif() + + list(APPEND MULTITENANT_DICOM_RESOURCES ${AUTOGENERATED_DIR}/MultitenantDicom.rc) + endif() + + EmbedResources( + --target=MultitenantDicomResources + --namespace=Orthanc.FrameworkResources + --framework-path=${CMAKE_SOURCE_DIR}/../OrthancFramework/Sources + ${DCMTK_DICTIONARIES} + ) + + set_source_files_properties( + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/Plugin.cpp + PROPERTIES COMPILE_DEFINITIONS "ORTHANC_PLUGIN_VERSION=\"${ORTHANC_VERSION}\"" + ) + + # The "OrthancFrameworkDependencies.cpp" file is used to bypass the + # precompiled headers if compiling with Visual Studio + add_library(MultitenantDicom SHARED + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/DicomFilter.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/Plugin.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp + + ${CMAKE_SOURCE_DIR}/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp + ${AUTOGENERATED_DIR}/MultitenantDicomResources.cpp + ${MULTITENANT_DICOM_RESOURCES} + ) + + target_link_libraries(MultitenantDicom PluginsDependencies ${DCMTK_LIBRARIES}) + + set_target_properties( + MultitenantDicom PROPERTIES + VERSION ${ORTHANC_VERSION} + SOVERSION ${ORTHANC_VERSION} + ) + + install( + TARGETS MultitenantDicom + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) +endif() + + +##################################################################### ## Build the companion tool to recover files compressed using Orthanc ##################################################################### diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/CMakeLists.txt Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,71 @@ +# 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 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 . + + +cmake_minimum_required(VERSION 2.8) + +project(MultitenantDicom) + +SET(ORTHANC_PLUGIN_VERSION "0.0" CACHE STRING "Version of the plugin") +SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)") +SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages") + +include(${CMAKE_SOURCE_DIR}/../Common/OrthancPlugins.cmake) +include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake) + +set(ENABLE_LOCALE ON CACHE INTERNAL "") +set(ENABLE_DCMTK ON CACHE INTERNAL "") +set(ENABLE_DCMTK_NETWORKING ON CACHE INTERNAL "") +set(ENABLE_DCMTK_TRANSCODING OFF CACHE INTERNAL "") + +set(ENABLE_MODULE_DICOM ON CACHE INTERNAL "") +set(ENABLE_MODULE_IMAGES ON CACHE INTERNAL "") +set(ENABLE_MODULE_JOBS OFF CACHE INTERNAL "") + +include(${CMAKE_SOURCE_DIR}/../../../../OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake) + +add_library(MultitenantDicom SHARED + DicomFilter.cpp + FindRequestHandler.cpp + MoveRequestHandler.cpp + MultitenantDicomServer.cpp + Plugin.cpp + PluginToolbox.cpp + StoreRequestHandler.cpp + + ${CMAKE_SOURCE_DIR}/../Common/OrthancPluginCppWrapper.cpp + ${ORTHANC_CORE_SOURCES} + ${ORTHANC_DICOM_SOURCES} + ${AUTOGENERATED_SOURCES} + ) + +target_link_libraries(MultitenantDicom ${DCMTK_LIBRARIES}) + +message("Setting the version of the plugin to ${ORTHANC_PLUGIN_VERSION}") +add_definitions(-DORTHANC_PLUGIN_VERSION="${ORTHANC_PLUGIN_VERSION}") + +set_target_properties(MultitenantDicom PROPERTIES + VERSION ${ORTHANC_PLUGIN_VERSION} + SOVERSION ${ORTHANC_PLUGIN_VERSION}) + +install( + TARGETS MultitenantDicom + RUNTIME DESTINATION lib # Destination for Windows + LIBRARY DESTINATION share/orthanc/plugins # Destination for Linux + ) diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,209 @@ +/** + * 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 + * . + **/ + + +#include "DicomFilter.h" + +#include "PluginToolbox.h" + +#include "../../../../OrthancFramework/Sources/Logging.h" +#include "../../../../OrthancFramework/Sources/OrthancException.h" + +#include "../Common/OrthancPluginCppWrapper.h" + + +DicomFilter::DicomFilter() : + hasAcceptedTransferSyntaxes_(false) +{ + { + OrthancPlugins::OrthancConfiguration config; + alwaysAllowEcho_ = config.GetBooleanValue("DicomAlwaysAllowEcho", true); + alwaysAllowFind_ = config.GetBooleanValue("DicomAlwaysAllowFind", false); + alwaysAllowMove_ = config.GetBooleanValue("DicomAlwaysAllowMove", false); + alwaysAllowStore_ = config.GetBooleanValue("DicomAlwaysAllowStore", true); + unknownSopClassAccepted_ = config.GetBooleanValue("UnknownSopClassAccepted", false); + isStrict_ = config.GetBooleanValue("StrictAetComparison", false); + checkModalityHost_ = config.GetBooleanValue("DicomCheckModalityHost", false); + } +} + + +bool DicomFilter::IsAllowedConnection(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) +{ + boost::shared_lock lock(mutex_); + + LOG(INFO) << "Incoming connection from AET " << remoteAet + << " on IP " << remoteIp << ", calling AET " << calledAet; + + if (alwaysAllowEcho_ || + alwaysAllowFind_ || + alwaysAllowMove_ || + alwaysAllowStore_) + { + return true; + } + else + { + std::string name; + Orthanc::RemoteModalityParameters parameters; + + if (!PluginToolbox::LookupAETitle(name, parameters, isStrict_, remoteAet)) + { + LOG(WARNING) << "Modality \"" << remoteAet + << "\" is not listed in the \"DicomModalities\" configuration option"; + return false; + } + else if (!checkModalityHost_ || + remoteIp == parameters.GetHost()) + { + return true; + } + else + { + LOG(WARNING) << "Forbidding access from AET \"" << remoteAet + << "\" given its hostname (" << remoteIp << ") does not match " + << "the \"DicomModalities\" configuration option (" + << parameters.GetHost() << " was expected)"; + return false; + } + } +} + + +bool DicomFilter::IsAllowedRequest(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + Orthanc::DicomRequestType type) +{ + boost::shared_lock lock(mutex_); + + LOG(INFO) << "Incoming " << EnumerationToString(type) << " request from AET " + << remoteAet << " on IP " << remoteIp << ", calling AET " << calledAet; + + if (type == Orthanc::DicomRequestType_Echo && + alwaysAllowEcho_) + { + // Incoming C-Echo requests are always accepted, even from unknown AET + return true; + } + else if (type == Orthanc::DicomRequestType_Find && + alwaysAllowFind_) + { + // Incoming C-Find requests are always accepted, even from unknown AET + return true; + } + else if (type == Orthanc::DicomRequestType_Store && + alwaysAllowStore_) + { + // Incoming C-Store requests are always accepted, even from unknown AET + return true; + } + else if (type == Orthanc::DicomRequestType_Move && + alwaysAllowMove_) + { + // Incoming C-Move requests are always accepted, even from unknown AET + return true; + } + else + { + std::string name; + Orthanc::RemoteModalityParameters parameters; + + if (!PluginToolbox::LookupAETitle(name, parameters, isStrict_, remoteAet)) + { + LOG(WARNING) << "DICOM authorization rejected for AET " << remoteAet + << " on IP " << remoteIp << ": This AET is not listed in " + << "configuration option \"DicomModalities\""; + return false; + } + else + { + if (parameters.IsRequestAllowed(type)) + { + return true; + } + else + { + LOG(WARNING) << "DICOM authorization rejected for AET " << remoteAet + << " on IP " << remoteIp << ": The DICOM command " + << EnumerationToString(type) << " is not allowed for this modality " + << "according to configuration option \"DicomModalities\""; + return false; + } + } + } +} + + +void DicomFilter::GetAcceptedTransferSyntaxes(std::set& target, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) +{ + boost::unique_lock lock(mutex_); + + if (!hasAcceptedTransferSyntaxes_) + { + Json::Value syntaxes; + + if (!OrthancPlugins::RestApiGet(syntaxes, "/tools/accepted-transfer-syntaxes", false) || + syntaxes.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < syntaxes.size(); i++) + { + Orthanc::DicomTransferSyntax syntax; + + if (syntaxes[i].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else if (Orthanc::LookupTransferSyntax(syntax, syntaxes[i].asString())) + { + acceptedTransferSyntaxes_.insert(syntax); + } + else + { + LOG(WARNING) << "Unknown transfer syntax: " << syntaxes[i].asString(); + } + } + } + + hasAcceptedTransferSyntaxes_ = true; + } + + target = acceptedTransferSyntaxes_; +} + + +bool DicomFilter::IsUnknownSopClassAccepted(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) +{ + boost::shared_lock lock(mutex_); + return unknownSopClassAccepted_; +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/DicomFilter.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,68 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "../../../../OrthancFramework/Sources/Compatibility.h" +#include "../../../../OrthancFramework/Sources/DicomNetworking/IApplicationEntityFilter.h" + +#include + + +class DicomFilter : public Orthanc::IApplicationEntityFilter +{ +private: + boost::shared_mutex mutex_; + + bool alwaysAllowEcho_; + bool alwaysAllowFind_; + bool alwaysAllowMove_; + bool alwaysAllowStore_; + bool unknownSopClassAccepted_; + bool isStrict_; + bool checkModalityHost_; + + bool hasAcceptedTransferSyntaxes_; + std::set acceptedTransferSyntaxes_; + +public: + DicomFilter(); + + virtual bool IsAllowedConnection(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; + + virtual bool IsAllowedRequest(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + Orthanc::DicomRequestType type) ORTHANC_OVERRIDE; + + virtual void GetAcceptedTransferSyntaxes(std::set& target, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; + + virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; +}; diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,124 @@ +/** + * 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 + * . + **/ + + +#include "FindRequestHandler.h" + +#include "PluginToolbox.h" + +#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../../../OrthancFramework/Sources/OrthancException.h" + +#include "../Common/OrthancPluginCppWrapper.h" + + +void FindRequestHandler::Handle(Orthanc::DicomFindAnswers& answers, + const Orthanc::DicomMap& input, + const std::list& sequencesToReturn, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + Orthanc::ModalityManufacturer manufacturer) +{ + std::set tags; + input.GetTags(tags); + + Json::Value request = Json::objectValue; + request["Expand"] = true; + PluginToolbox::AddLabelsToFindRequest(request, labels_, constraint_); + + Json::Value query = Json::objectValue; + std::string level; + + for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + std::string s; + + if (input.LookupStringValue(s, *it, false) && + !s.empty()) + { + if (*it == Orthanc::DICOM_TAG_QUERY_RETRIEVE_LEVEL) + { + level = s; + } + else + { + query[it->Format()] = s; + } + } + } + + if (level.empty()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Missing QueryRetrieveLevel in DICOM C-FIND request"); + } + + request["Level"] = EnumerationToString(PluginToolbox::ParseQueryRetrieveLevel(level)); + request["Query"] = query; + + Json::Value response; + if (!OrthancPlugins::RestApiPost(response, "/tools/find", request, false) || + response.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Invalid DICOM C-FIND request"); + } + + for (Json::Value::ArrayIndex i = 0; i < response.size(); i++) + { + if (response[i].type() != Json::objectValue || + !response[i].isMember(KEY_MAIN_DICOM_TAGS)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (response[i].isMember(KEY_PATIENT_MAIN_DICOM_TAGS) && + response[i][KEY_PATIENT_MAIN_DICOM_TAGS].type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + Orthanc::DicomMap m; + + for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + const std::string tag = Orthanc::FromDcmtkBridge::GetTagName(*it, ""); + + if (response[i][KEY_MAIN_DICOM_TAGS].isMember(tag) && + response[i][KEY_MAIN_DICOM_TAGS][tag].type() == Json::stringValue) + { + m.SetValue(*it, response[i][KEY_MAIN_DICOM_TAGS][tag].asString(), false); + } + else if (response[i].isMember(KEY_PATIENT_MAIN_DICOM_TAGS) && + response[i][KEY_PATIENT_MAIN_DICOM_TAGS].isMember(tag) && + response[i][KEY_PATIENT_MAIN_DICOM_TAGS][tag].type() == Json::stringValue) + { + m.SetValue(*it, response[i][KEY_PATIENT_MAIN_DICOM_TAGS][tag].asString(), false); + } + } + + m.SetValue(Orthanc::DICOM_TAG_QUERY_RETRIEVE_LEVEL, level, false); + m.SetValue(Orthanc::DICOM_TAG_RETRIEVE_AE_TITLE, retrieveAet_, false); + answers.Add(m); + } + + answers.SetComplete(true); +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,56 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "PluginEnumerations.h" + +#include "../../../../OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h" + + +class FindRequestHandler : public Orthanc::IFindRequestHandler +{ +private: + // Everything is constant, so no need for a mutex + const std::string retrieveAet_; + const std::set labels_; + const LabelsConstraint constraint_; + +public: + FindRequestHandler(const std::string& retrieveAet, + const std::set& labels, + LabelsConstraint constraint) : + retrieveAet_(retrieveAet), + labels_(labels), + constraint_(constraint) + { + } + + virtual void Handle(Orthanc::DicomFindAnswers& answers, + const Orthanc::DicomMap& input, + const std::list& sequencesToReturn, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + Orthanc::ModalityManufacturer manufacturer) ORTHANC_OVERRIDE; +}; diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp --- /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 + * . + **/ + + +#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& publicIds, + Orthanc::ResourceType level, + const Orthanc::DicomTag& tag, + const std::string& value) const +{ + std::vector 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& 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 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::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); +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,69 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "PluginEnumerations.h" + +#include "../../../../OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h" + + +class MoveRequestHandler : public Orthanc::IMoveRequestHandler +{ +private: + class Iterator; + + // Everything is constant, so no need for a mutex + const std::set labels_; + const LabelsConstraint constraint_; + const bool isStrictAet_; + const bool isSynchronous_; + + void ExecuteLookup(std::set& publicIds, + Orthanc::ResourceType level, + const Orthanc::DicomTag& tag, + const std::string& value) const; + + void LookupIdentifiers(std::set& publicIds, + Orthanc::ResourceType level, + const Orthanc::DicomMap& input) const; + +public: + MoveRequestHandler(const std::set& labels, + LabelsConstraint constraint, + bool isStrictAet, + bool isSynchronous) : + labels_(labels), + constraint_(constraint), + isStrictAet_(isStrictAet), + isSynchronous_(isSynchronous) + { + } + + virtual Orthanc::IMoveRequestIterator* Handle(const std::string& targetAet, + const Orthanc::DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) ORTHANC_OVERRIDE; +}; diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,146 @@ +/** + * 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 + * . + **/ + + +#include "MultitenantDicomServer.h" + +#include "FindRequestHandler.h" +#include "MoveRequestHandler.h" +#include "PluginToolbox.h" +#include "StoreRequestHandler.h" + +#include "../../../../OrthancFramework/Sources/Logging.h" +#include "../../../../OrthancFramework/Sources/SerializationToolbox.h" + +#include "../Common/OrthancPluginCppWrapper.h" + + +bool MultitenantDicomServer::IsSameAETitle(const std::string& aet1, + const std::string& aet2) +{ + boost::mutex::scoped_lock lock(mutex_); + return PluginToolbox::IsSameAETitle(isStrictAet_, aet1, aet2); +} + + +bool MultitenantDicomServer::LookupAETitle(Orthanc::RemoteModalityParameters& parameters, + const std::string& aet) +{ + boost::mutex::scoped_lock lock(mutex_); + + std::string name; + return PluginToolbox::LookupAETitle(name, parameters, isStrictAet_, server_->GetApplicationEntityTitle()); +} + + +Orthanc::IFindRequestHandler* MultitenantDicomServer::ConstructFindRequestHandler() +{ + boost::mutex::scoped_lock lock(mutex_); + return new FindRequestHandler(server_->GetApplicationEntityTitle(), labels_, labelsConstraint_); +} + + +Orthanc::IMoveRequestHandler* MultitenantDicomServer::ConstructMoveRequestHandler() +{ + boost::mutex::scoped_lock lock(mutex_); + return new MoveRequestHandler(labels_, labelsConstraint_, isStrictAet_, isSynchronousCMove_); +} + + +Orthanc::IStoreRequestHandler* MultitenantDicomServer::ConstructStoreRequestHandler() +{ + boost::mutex::scoped_lock lock(mutex_); + return new StoreRequestHandler(labels_, labelsStoreLevels_); +} + + +MultitenantDicomServer::MultitenantDicomServer(const Json::Value& serverConfig) +{ + PluginToolbox::ParseLabels(labels_, labelsConstraint_, serverConfig); + + if (serverConfig.isMember(KEY_LABELS_STORE_LEVELS)) + { + std::set levels; + Orthanc::SerializationToolbox::ReadSetOfStrings(levels, serverConfig, KEY_LABELS_STORE_LEVELS); + for (std::set::const_iterator it = levels.begin(); it != levels.end(); ++it) + { + labelsStoreLevels_.insert(Orthanc::StringToResourceType(it->c_str())); + } + } + else + { + labelsStoreLevels_.insert(Orthanc::ResourceType_Study); + labelsStoreLevels_.insert(Orthanc::ResourceType_Series); + labelsStoreLevels_.insert(Orthanc::ResourceType_Instance); + } + + server_.reset(new Orthanc::DicomServer); + + { + OrthancPlugins::OrthancConfiguration globalConfig; + isSynchronousCMove_ = globalConfig.GetBooleanValue(KEY_SYNCHRONOUS_C_MOVE, true); + isStrictAet_ = globalConfig.GetBooleanValue(KEY_STRICT_AET_COMPARISON, false); + + server_->SetCalledApplicationEntityTitleCheck(globalConfig.GetBooleanValue("DicomCheckCalledAet", false)); + server_->SetAssociationTimeout(globalConfig.GetUnsignedIntegerValue("DicomScpTimeout", 30)); + server_->SetThreadsCount(globalConfig.GetUnsignedIntegerValue("DicomThreadsCount", 1)); + server_->SetMaximumPduLength(globalConfig.GetUnsignedIntegerValue("MaximumPduLength", 16384)); + } + + server_->SetRemoteModalities(*this); + server_->SetApplicationEntityFilter(filter_); + server_->SetPortNumber(Orthanc::SerializationToolbox::ReadUnsignedInteger(serverConfig, "Port")); + server_->SetApplicationEntityTitle(Orthanc::SerializationToolbox::ReadString(serverConfig, KEY_AET)); + server_->SetFindRequestHandlerFactory(*this); + server_->SetMoveRequestHandlerFactory(*this); + server_->SetStoreRequestHandlerFactory(*this); +} + + +void MultitenantDicomServer::Start() +{ + boost::mutex::scoped_lock lock(mutex_); + + if (server_->GetPortNumber() < 1024) + { + LOG(WARNING) << "The DICOM port is privileged (" + << server_->GetPortNumber() << " is below 1024), " + << "make sure you run Orthanc as root/administrator"; + } + + server_->Start(); + LOG(WARNING) << "Started multitenant DICOM server listening with AET " << server_->GetApplicationEntityTitle() + << " on port: " << server_->GetPortNumber(); +} + + +void MultitenantDicomServer::Stop() +{ + boost::mutex::scoped_lock lock(mutex_); + + if (server_.get() != NULL) + { + LOG(WARNING) << "Stopping multitenant DICOM server listening with AET " << server_->GetApplicationEntityTitle() + << " on port: " << server_->GetPortNumber(); + server_->Stop(); + } +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MultitenantDicomServer.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,69 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "DicomFilter.h" +#include "PluginEnumerations.h" + +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomServer.h" + +#include + + +class MultitenantDicomServer : + private Orthanc::DicomServer::IRemoteModalities, + private Orthanc::IFindRequestHandlerFactory, + private Orthanc::IMoveRequestHandlerFactory, + private Orthanc::IStoreRequestHandlerFactory +{ +private: + virtual bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) ORTHANC_OVERRIDE; + + virtual bool LookupAETitle(Orthanc::RemoteModalityParameters& parameters, + const std::string& aet) ORTHANC_OVERRIDE; + + virtual Orthanc::IFindRequestHandler* ConstructFindRequestHandler() ORTHANC_OVERRIDE; + + virtual Orthanc::IMoveRequestHandler* ConstructMoveRequestHandler() ORTHANC_OVERRIDE; + + virtual Orthanc::IStoreRequestHandler* ConstructStoreRequestHandler() ORTHANC_OVERRIDE; + + boost::mutex mutex_; + + std::set labels_; + LabelsConstraint labelsConstraint_; + std::set labelsStoreLevels_; + bool isSynchronousCMove_; + bool isStrictAet_; + DicomFilter filter_; + std::unique_ptr server_; + +public: + MultitenantDicomServer(const Json::Value& serverConfig); + + void Start(); + + void Stop(); +}; diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/NOTES.txt Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,8 @@ + +Linux Standard Base (LSB) +========================= + +$ mkdir lsb +$ cd lsb +$ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../../../../OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_BOOST=ON -G Ninja +$ ninja diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,85 @@ +/** + * 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 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 . + **/ + + +/** + * Remove the dependency upon ICU in plugins, as this greatly increase + * the size of the resulting binaries, since they must embed the ICU + * dictionary. + **/ + +#define ORTHANC_ENABLE_ICU 0 +#define ORTHANC_FRAMEWORK_INCLUDE_RESOURCES 0 + +#include + +#include "../../../../OrthancFramework/Sources/ChunkedBuffer.cpp" +#include "../../../../OrthancFramework/Sources/Compression/DeflateBaseCompressor.cpp" +#include "../../../../OrthancFramework/Sources/Compression/GzipCompressor.cpp" +#include "../../../../OrthancFramework/Sources/Compression/ZlibCompressor.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomArray.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomElement.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomImageInformation.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomInstanceHasher.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomIntegerPixelAccessor.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomPath.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomTag.cpp" +#include "../../../../OrthancFramework/Sources/DicomFormat/DicomValue.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomServer.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/GetScp.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/ToDcmtkBridge.cpp" +#include "../../../../OrthancFramework/Sources/Enumerations.cpp" +#include "../../../../OrthancFramework/Sources/HttpServer/HttpOutput.cpp" +#include "../../../../OrthancFramework/Sources/Images/IImageWriter.cpp" +#include "../../../../OrthancFramework/Sources/Images/Image.cpp" +#include "../../../../OrthancFramework/Sources/Images/ImageAccessor.cpp" +#include "../../../../OrthancFramework/Sources/Images/ImageBuffer.cpp" +#include "../../../../OrthancFramework/Sources/Images/ImageProcessing.cpp" +#include "../../../../OrthancFramework/Sources/Images/JpegErrorManager.cpp" +#include "../../../../OrthancFramework/Sources/Images/JpegReader.cpp" +#include "../../../../OrthancFramework/Sources/Images/JpegWriter.cpp" +#include "../../../../OrthancFramework/Sources/Images/PamReader.cpp" +#include "../../../../OrthancFramework/Sources/Images/PamWriter.cpp" +#include "../../../../OrthancFramework/Sources/Images/PngReader.cpp" +#include "../../../../OrthancFramework/Sources/Images/PngWriter.cpp" +#include "../../../../OrthancFramework/Sources/Logging.cpp" +#include "../../../../OrthancFramework/Sources/MultiThreading/RunnableWorkersPool.cpp" +#include "../../../../OrthancFramework/Sources/MultiThreading/SharedMessageQueue.cpp" +#include "../../../../OrthancFramework/Sources/OrthancException.cpp" +#include "../../../../OrthancFramework/Sources/RestApi/RestApiOutput.cpp" +#include "../../../../OrthancFramework/Sources/SerializationToolbox.cpp" +#include "../../../../OrthancFramework/Sources/SystemToolbox.cpp" +#include "../../../../OrthancFramework/Sources/Toolbox.cpp" +#include "../../../../OrthancFramework/Sources/TemporaryFile.cpp" diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/Plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/Plugin.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,195 @@ +/** + * 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 + * . + **/ + + +#include "MultitenantDicomServer.h" + +#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../../../OrthancFramework/Sources/Logging.h" +#include "../../../../OrthancFramework/Sources/OrthancException.h" + +#include "../Common/OrthancPluginCppWrapper.h" + + +typedef std::list DicomServers; + +static DicomServers dicomServers_; + + +static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char* resourceId) +{ + switch (changeType) + { + case OrthancPluginChangeType_OrthancStarted: + { + for (DicomServers::iterator it = dicomServers_.begin(); it != dicomServers_.end(); ++it) + { + if (*it != NULL) + { + try + { + (*it)->Start(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while stopping the multitenant DICOM server: " << e.What(); + } + } + } + + break; + } + + case OrthancPluginChangeType_OrthancStopped: + { + for (DicomServers::iterator it = dicomServers_.begin(); it != dicomServers_.end(); ++it) + { + if (*it != NULL) + { + try + { + (*it)->Stop(); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while stopping the multitenant DICOM server: " << e.What(); + } + } + } + + break; + } + + default: + break; + } + + return OrthancPluginErrorCode_Success; +} + + +extern "C" +{ + ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) + { + OrthancPlugins::SetGlobalContext(context); + + /* Check the version of the Orthanc core */ + if (OrthancPluginCheckVersion(OrthancPlugins::GetGlobalContext()) == 0) + { + char info[1024]; + sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin", + OrthancPlugins::GetGlobalContext()->orthancVersion, + ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER, + ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER); + OrthancPluginLogError(OrthancPlugins::GetGlobalContext(), info); + return -1; + } + +#if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 7, 2) + Orthanc::Logging::InitializePluginContext(context); +#else + Orthanc::Logging::Initialize(context); +#endif + + if (!OrthancPlugins::CheckMinimalOrthancVersion(1, 12, 0)) + { + OrthancPlugins::ReportMinimalOrthancVersion(1, 12, 0); + return -1; + } + + Orthanc::FromDcmtkBridge::InitializeDictionary(false /* loadPrivateDictionary */); + /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ + dcmDisableGethostbyaddr.set(OFTrue); + + OrthancPluginSetDescription(context, "Multitenant plugin for Orthanc."); + + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + + try + { + OrthancPlugins::OrthancConfiguration globalConfig; + + OrthancPlugins::OrthancConfiguration pluginConfig; + globalConfig.GetSection(pluginConfig, KEY_MULTITENANT_DICOM); + + if (pluginConfig.GetJson().isMember(KEY_SERVERS)) + { + const Json::Value& servers = pluginConfig.GetJson() [KEY_SERVERS]; + + if (servers.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType, "Configuration option \"" + + std::string(KEY_MULTITENANT_DICOM) + "." + std::string(KEY_SERVERS) + "\" must be an array"); + } + else + { + for (Json::Value::ArrayIndex i = 0; i < servers.size(); i++) + { + dicomServers_.push_back(new MultitenantDicomServer(servers[i])); + } + } + } + + return 0; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while starting the multitenant DICOM server: " << e.What(); + return -1; + } + } + + + ORTHANC_PLUGINS_API void OrthancPluginFinalize() + { + for (DicomServers::iterator it = dicomServers_.begin(); it != dicomServers_.end(); ++it) + { + if (*it != NULL) + { + try + { + delete *it; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while destroying the multitenant DICOM server: " << e.What(); + } + } + } + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetName() + { + return "multitenant-dicom"; + } + + + ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() + { + return ORTHANC_PLUGIN_VERSION; + } +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/PluginEnumerations.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginEnumerations.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,48 @@ +/** + * 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 + * . + **/ + + +#pragma once + + +enum LabelsConstraint +{ + LabelsConstraint_All, + LabelsConstraint_Any, + LabelsConstraint_None +}; + + +#define KEY_AET "AET" +#define KEY_ALL "All" +#define KEY_ANY "Any" +#define KEY_LABELS "Labels" +#define KEY_LABELS_CONSTRAINT "LabelsConstraint" +#define KEY_LABELS_STORE_LEVELS "LabelsStoreLevels" +#define KEY_MAIN_DICOM_TAGS "MainDicomTags" +#define KEY_MULTITENANT_DICOM "MultitenantDicom" +#define KEY_NONE "None" +#define KEY_PATIENT_MAIN_DICOM_TAGS "PatientMainDicomTags" +#define KEY_QUERY "Query" +#define KEY_SERVERS "Servers" +#define KEY_STRICT_AET_COMPARISON "StrictAetComparison" +#define KEY_SYNCHRONOUS_C_MOVE "SynchronousCMove" diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,207 @@ +/** + * 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 + * . + **/ + + +#include "PluginToolbox.h" + +#include "../../../../OrthancFramework/Sources/OrthancException.h" +#include "../../../../OrthancFramework/Sources/SerializationToolbox.h" +#include "../../../../OrthancFramework/Sources/Toolbox.h" + +#include "../Common/OrthancPluginCppWrapper.h" + + +namespace PluginToolbox +{ + bool IsValidLabel(const std::string& label) + { + if (label.empty()) + { + return false; + } + + if (label.size() > 64) + { + // This limitation is for MySQL, which cannot use a TEXT + // column of undefined length as a primary key + return false; + } + + for (size_t i = 0; i < label.size(); i++) + { + if (!(label[i] == '_' || + label[i] == '-' || + (label[i] >= 'a' && label[i] <= 'z') || + (label[i] >= 'A' && label[i] <= 'Z') || + (label[i] >= '0' && label[i] <= '9'))) + { + return false; + } + } + + return true; + } + + + Orthanc::ResourceType ParseQueryRetrieveLevel(const std::string& level) + { + if (level == "PATIENT") + { + return Orthanc::ResourceType_Patient; + } + else if (level == "STUDY") + { + return Orthanc::ResourceType_Study; + } + else if (level == "SERIES") + { + return Orthanc::ResourceType_Series; + } + else if (level == "INSTANCE") + { + return Orthanc::ResourceType_Instance; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, "Bad value for QueryRetrieveLevel in DICOM C-FIND: " + level); + } + } + + + bool IsSameAETitle(bool isStrict, + const std::string& aet1, + const std::string& aet2) + { + if (isStrict) + { + // Case-sensitive matching + return aet1 == aet2; + } + else + { + // Case-insensitive matching (default) + std::string tmp1, tmp2; + Orthanc::Toolbox::ToLowerCase(tmp1, aet1); + Orthanc::Toolbox::ToLowerCase(tmp2, aet2); + return tmp1 == tmp2; + } + } + + + bool LookupAETitle(std::string& name, + Orthanc::RemoteModalityParameters& parameters, + bool isStrict, + const std::string& aet) + { + Json::Value modalities; + if (!OrthancPlugins::RestApiGet(modalities, "/modalities?expand", false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to obtain the list of the remote modalities"); + } + + std::vector names = modalities.getMemberNames(); + for (size_t i = 0; i < names.size(); i++) + { + parameters = Orthanc::RemoteModalityParameters(modalities[names[i]]); + + if (IsSameAETitle(isStrict, parameters.GetApplicationEntityTitle(), aet)) + { + name = names[i]; + return true; + } + } + + return false; + } + + + void ParseLabels(std::set& targetLabels, + LabelsConstraint& targetConstraint, + const Json::Value& serverConfig) + { + Orthanc::SerializationToolbox::ReadSetOfStrings(targetLabels, serverConfig, KEY_LABELS); + + for (std::set::const_iterator it = targetLabels.begin(); it != targetLabels.end(); ++it) + { + if (!PluginToolbox::IsValidLabel(*it)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Invalid label: " + *it); + } + } + + std::string s = Orthanc::SerializationToolbox::ReadString(serverConfig, KEY_LABELS_CONSTRAINT, KEY_ALL); + targetConstraint = PluginToolbox::StringToLabelsConstraint(s); + } + + + void AddLabelsToFindRequest(Json::Value& request, + const std::set& labels, + LabelsConstraint constraint) + { + Json::Value items = Json::arrayValue; + for (std::set::const_iterator it = labels.begin(); it != labels.end(); ++it) + { + items.append(*it); + } + + request[KEY_LABELS] = items; + + switch (constraint) + { + case LabelsConstraint_All: + request[KEY_LABELS_CONSTRAINT] = KEY_ALL; + break; + + case LabelsConstraint_Any: + request[KEY_LABELS_CONSTRAINT] = KEY_ANY; + break; + + case LabelsConstraint_None: + request[KEY_LABELS_CONSTRAINT] = KEY_NONE; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + + LabelsConstraint StringToLabelsConstraint(const std::string& s) + { + if (s == KEY_ALL) + { + return LabelsConstraint_All; + } + else if (s == KEY_ANY) + { + return LabelsConstraint_Any; + } + else if (s == KEY_NONE) + { + return LabelsConstraint_None; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Bad value for constraint of labels: " + s); + } + } +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/PluginToolbox.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,56 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "PluginEnumerations.h" + +#include "../../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h" + +#include + +namespace PluginToolbox +{ + bool IsValidLabel(const std::string& label); + + Orthanc::ResourceType ParseQueryRetrieveLevel(const std::string& level); + + bool IsSameAETitle(bool isStrict, + const std::string& aet1, + const std::string& aet2); + + bool LookupAETitle(std::string& name, + Orthanc::RemoteModalityParameters& parameters, + bool isStrict, + const std::string& aet); + + void ParseLabels(std::set& targetLabels, + LabelsConstraint& targetConstraint, + const Json::Value& serverConfig); + + void AddLabelsToFindRequest(Json::Value& request, + const std::set& labels, + LabelsConstraint constraint); + + LabelsConstraint StringToLabelsConstraint(const std::string& s); +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.cpp Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,92 @@ +/** + * 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 + * . + **/ + + +#include "StoreRequestHandler.h" + +#include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../../../OrthancFramework/Sources/Logging.h" +#include "../../../../OrthancFramework/Sources/OrthancException.h" +#include "../../../../OrthancFramework/Sources/SerializationToolbox.h" + +#include "../Common/OrthancPluginCppWrapper.h" + +#include + + +uint16_t StoreRequestHandler::Handle(DcmDataset& dicom, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) +{ + std::string buffer; + + if (!Orthanc::FromDcmtkBridge::SaveToMemoryBuffer(buffer, dicom)) + { + LOG(ERROR) << "Cannot write DICOM file to memory"; + return STATUS_STORE_Error_CannotUnderstand; + } + + Json::Value info; + if (!OrthancPlugins::RestApiPost(info, "/instances", buffer, false)) + { + LOG(ERROR) << "Cannot store the DICOM file"; + return STATUS_STORE_Refused_OutOfResources; + } + + for (std::set::const_iterator level = levels_.begin(); level != levels_.end(); ++level) + { + for (std::set::const_iterator label = labels_.begin(); label != labels_.end(); ++label) + { + std::string uri; + switch (*level) + { + case Orthanc::ResourceType_Patient: + uri = "/patients/" + Orthanc::SerializationToolbox::ReadString(info, "ParentPatient") + "/labels/" + *label; + break; + + case Orthanc::ResourceType_Study: + uri = "/studies/" + Orthanc::SerializationToolbox::ReadString(info, "ParentStudy") + "/labels/" + *label; + break; + + case Orthanc::ResourceType_Series: + uri = "/series/" + Orthanc::SerializationToolbox::ReadString(info, "ParentSeries") + "/labels/" + *label; + break; + + case Orthanc::ResourceType_Instance: + uri = "/instances/" + Orthanc::SerializationToolbox::ReadString(info, "ID") + "/labels/" + *label; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + Json::Value tmp; + if (!OrthancPlugins::RestApiPut(tmp, uri, std::string(""), false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot set label"); + } + } + } + + return STATUS_Success; +} diff -r a45e8b6115f6 -r 7cb1b851f5c8 OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/StoreRequestHandler.h Fri Apr 14 11:49:24 2023 +0200 @@ -0,0 +1,48 @@ +/** + * 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 + * . + **/ + + +#pragma once + +#include "../../../../OrthancFramework/Sources/DicomNetworking/IStoreRequestHandler.h" + + +class StoreRequestHandler : public Orthanc::IStoreRequestHandler +{ +private: + // Everything is constant, so no need for a mutex + const std::set labels_; + const std::set levels_; + +public: + StoreRequestHandler(const std::set& labels, + const std::set& levels) : + labels_(labels), + levels_(levels) + { + } + + virtual uint16_t Handle(DcmDataset& dicom, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) ORTHANC_OVERRIDE; +};