Mercurial > hg > orthanc-dicomweb
changeset 489:4cb232b42168
Possibility to store the definition of DICOMweb servers into the Orthanc database
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Fri, 22 Jan 2021 14:20:58 +0100 |
| parents | b0e9d55ee247 |
| children | a5e98f4af53d |
| files | NEWS Plugin/Configuration.cpp Plugin/Configuration.h Plugin/DicomWebFormatter.cpp Plugin/DicomWebServers.cpp Plugin/DicomWebServers.h Plugin/Plugin.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h UnitTestsSources/UnitTestsMain.cpp |
| diffstat | 10 files changed, 298 insertions(+), 54 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Jan 18 16:46:17 2021 +0100 +++ b/NEWS Fri Jan 22 14:20:58 2021 +0100 @@ -1,6 +1,8 @@ Pending changes in the mainline =============================== +* Possibility to store the definition of DICOMweb servers into the Orthanc database +* New configuration option "DicomWeb.ServersInDatabase" * Fix compliance with DICOM >= 2016c: If no "transfer-syntax" is provided in WADO-RS Retrieve Instance/Series/Study, DICOM shall be transcoded to Little Endian Explicit
--- a/Plugin/Configuration.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/Configuration.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -25,6 +25,7 @@ #include "DicomWebServers.h" #include <Compatibility.h> +#include <Logging.h> #include <Toolbox.h> #include <fstream> @@ -33,6 +34,13 @@ #include <boost/algorithm/string/predicate.hpp> +// Assume Latin-1 encoding by default (as in the Orthanc core) +static Orthanc::Encoding defaultEncoding_ = Orthanc::Encoding_Latin1; +static std::unique_ptr<OrthancPlugins::OrthancConfiguration> configuration_; +static bool serversInDatabase_ = false; +static const int32_t GLOBAL_PROPERTY_SERVERS = 5468; + + namespace OrthancPlugins { bool LookupHttpHeader(std::string& value, @@ -140,7 +148,7 @@ { OrthancPluginDictionaryEntry entry; - if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success) + if (OrthancPluginLookupDictionary(GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success) { target = Orthanc::DicomTag(entry.group, entry.element); return true; @@ -284,16 +292,11 @@ namespace Configuration { - // Assume Latin-1 encoding by default (as in the Orthanc core) - static Orthanc::Encoding defaultEncoding_ = Orthanc::Encoding_Latin1; - static std::unique_ptr<OrthancConfiguration> configuration_; - - void Initialize() { configuration_.reset(new OrthancConfiguration); - OrthancPlugins::OrthancConfiguration global; + OrthancConfiguration global; global.GetSection(*configuration_, "DicomWeb"); std::string s; @@ -302,10 +305,22 @@ defaultEncoding_ = Orthanc::StringToEncoding(s.c_str()); } - OrthancPlugins::OrthancConfiguration servers; - configuration_->GetSection(servers, "Servers"); - OrthancPlugins::DicomWebServers::GetInstance().Load(servers.GetJson()); + if (!configuration_->LookupBooleanValue(serversInDatabase_, "ServersInDatabase")) + { + serversInDatabase_ = false; + } + if (serversInDatabase_) + { + LOG(INFO) << "The DICOMweb plugin stores the DICOMweb servers in the Orthanc database"; + } + else + { + LOG(INFO) << "The DICOMweb plugin reads the DICOMweb servers from the configuration file"; + } + + DicomWebServers::GetInstance().Clear(); + // Check configuration during initialization GetMetadataMode(Orthanc::ResourceType_Study); GetMetadataMode(Orthanc::ResourceType_Series); @@ -371,7 +386,7 @@ std::string GetOrthancApiRoot() { - std::string root = OrthancPlugins::Configuration::GetDicomWebRoot(); + std::string root = Configuration::GetDicomWebRoot(); std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, root, '/'); @@ -444,10 +459,10 @@ static bool LookupHttpHeader2(std::string& value, - const OrthancPlugins::HttpClient::HttpHeaders& headers, + const HttpClient::HttpHeaders& headers, const std::string& name) { - for (OrthancPlugins::HttpClient::HttpHeaders::const_iterator + for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) { if (boost::iequals(it->first, name)) @@ -461,7 +476,7 @@ } - std::string GetBaseUrl(const OrthancPlugins::HttpClient::HttpHeaders& headers) + std::string GetBaseUrl(const HttpClient::HttpHeaders& headers) { assert(configuration_.get() != NULL); std::string host = configuration_->GetStringValue("Host", ""); @@ -521,7 +536,7 @@ std::string GetBaseUrl(const OrthancPluginHttpRequest* request) { - OrthancPlugins::HttpClient::HttpHeaders headers; + HttpClient::HttpHeaders headers; std::string value; if (LookupHttpHeader(value, request, "forwarded")) @@ -584,8 +599,8 @@ } else { - OrthancPlugins::LogError("Unsupported return MIME type: " + accept + - ", will return DICOM+JSON"); + LogError("Unsupported return MIME type: " + accept + + ", will return DICOM+JSON"); return false; } } @@ -610,7 +625,7 @@ { std::string accept; - if (OrthancPlugins::LookupHttpHeader(accept, request, "accept")) + if (LookupHttpHeader(accept, request, "accept")) { return IsXmlExpected(accept); } @@ -709,5 +724,57 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } + + + void LoadDicomWebServers() + { + if (serversInDatabase_) + { + // New in DICOMweb 1.5 + OrthancString property; + property.Assign(OrthancPluginGetGlobalProperty( + GetGlobalContext(), GLOBAL_PROPERTY_SERVERS, "{}")); + + if (property.GetContent() == NULL) + { + DicomWebServers::GetInstance().Clear(); + } + else + { + try + { + DicomWebServers::GetInstance().UnserializeGlobalProperty(property.GetContent()); + } + catch (Orthanc::OrthancException&) + { + DicomWebServers::GetInstance().Clear(); + LOG(ERROR) << "Cannot read the DICOMweb servers from the database, no server will be defined"; + } + } + } + else + { + OrthancConfiguration servers; + configuration_->GetSection(servers, "Servers"); + DicomWebServers::GetInstance().LoadGlobalConfiguration(servers.GetJson()); + } + } + + + void SaveDicomWebServers() + { + if (serversInDatabase_) + { + // New in DICOMweb 1.5 + std::string property; + DicomWebServers::GetInstance().SerializeGlobalProperty(property); + + if (!OrthancPluginSetGlobalProperty( + OrthancPlugins::GetGlobalContext(), GLOBAL_PROPERTY_SERVERS, property.c_str())) + { + LOG(ERROR) << "Cannot write the DICOMweb servers into the database"; + } + } + } } }
--- a/Plugin/Configuration.h Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/Configuration.h Fri Jan 22 14:20:58 2021 +0100 @@ -133,5 +133,9 @@ void GetExtrapolatedMetadataTags(std::set<Orthanc::DicomTag>& tags, Orthanc::ResourceType level); + + void LoadDicomWebServers(); + + void SaveDicomWebServers(); } }
--- a/Plugin/DicomWebFormatter.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/DicomWebFormatter.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -126,7 +126,7 @@ DicomWebFormatter::HttpWriter::HttpWriter(OrthancPluginRestOutput* output, bool isXml) : - context_(OrthancPlugins::GetGlobalContext()), + context_(GetGlobalContext()), output_(output), isXml_(isXml), first_(true) @@ -163,8 +163,7 @@ std::string item; - OrthancPlugins::DicomWebFormatter::Apply( - item, context_, dicom, size, isXml_, mode, bulkRoot); + DicomWebFormatter::Apply(item, context_, dicom, size, isXml_, mode, bulkRoot); if (isXml_) {
--- a/Plugin/DicomWebServers.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/DicomWebServers.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -34,12 +34,15 @@ { for (Servers::iterator it = servers_.begin(); it != servers_.end(); ++it) { + assert(it->second != NULL); delete it->second; } + + servers_.clear(); } - void DicomWebServers::Load(const Json::Value& servers) + void DicomWebServers::LoadGlobalConfiguration(const Json::Value& servers) { boost::mutex::scoped_lock lock(mutex_); @@ -68,8 +71,8 @@ } catch (Orthanc::OrthancException& e) { - OrthancPlugins::LogError("Exception while parsing the \"DicomWeb.Servers\" section " - "of the configuration file: " + std::string(e.What())); + LogError("Exception while parsing the \"DicomWeb.Servers\" section " + "of the configuration file: " + std::string(e.What())); throw; } @@ -114,6 +117,7 @@ servers.clear(); for (Servers::const_iterator it = servers_.begin(); it != servers_.end(); ++it) { + assert(it->second != NULL); servers.push_back(it->first); } } @@ -207,7 +211,7 @@ - void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */, + void CallServer(MemoryBuffer& answerBody /* out */, std::map<std::string, std::string>& answerHeaders /* out */, const Orthanc::WebServiceParameters& server, OrthancPluginHttpMethod method, @@ -271,7 +275,7 @@ bodySize = body.size(); } - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + OrthancPluginContext* context = GetGlobalContext(); uint16_t status = 0; MemoryBuffer answerHeadersTmp; @@ -370,4 +374,51 @@ } } } + + + void DicomWebServers::SerializeGlobalProperty(std::string& target) + { + boost::mutex::scoped_lock lock(mutex_); + + Json::Value json = Json::objectValue; + + for (Servers::const_iterator it = servers_.begin(); it != servers_.end(); ++it) + { + assert(it->second != NULL); + + Json::Value server; + it->second->Serialize(server, true /* advanced format */, true /* store passwords */); + json[it->first] = server; + } + + Orthanc::Toolbox::WriteFastJson(target, json); + } + + + void DicomWebServers::UnserializeGlobalProperty(const std::string& source) + { + Json::Value json; + + if (!Orthanc::Toolbox::ReadJson(json, source) || + json.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize " + "the list of DICOMweb servers from global properties"); + } + + Clear(); + + std::vector<std::string> members = json.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + + std::unique_ptr<Orthanc::WebServiceParameters> server(new Orthanc::WebServiceParameters); + server->Unserialize(json[name]); + + assert(servers_.find(name) == servers_.end()); + servers_[name] = server.release(); + } + } }
--- a/Plugin/DicomWebServers.h Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/DicomWebServers.h Fri Jan 22 14:20:58 2021 +0100 @@ -39,23 +39,23 @@ boost::mutex mutex_; Servers servers_; - void Clear(); - DicomWebServers() // Forbidden (singleton pattern) { } public: + ~DicomWebServers() + { + Clear(); + } + static void UriEncode(std::string& uri, const std::string& resource, const std::map<std::string, std::string>& getArguments); - void Load(const Json::Value& configuration); + void Clear(); - ~DicomWebServers() - { - Clear(); - } + void LoadGlobalConfiguration(const Json::Value& configuration); static DicomWebServers& GetInstance(); @@ -72,10 +72,14 @@ void SetServer(const std::string& name, const Orthanc::WebServiceParameters& parameters); + + void SerializeGlobalProperty(std::string& target); + + void UnserializeGlobalProperty(const std::string& source); }; - void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */, + void CallServer(MemoryBuffer& answerBody /* out */, std::map<std::string, std::string>& answerHeaders /* out */, const Orthanc::WebServiceParameters& server, OrthancPluginHttpMethod method,
--- a/Plugin/Plugin.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/Plugin/Plugin.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -128,6 +128,8 @@ case OrthancPluginHttpMethod_Delete: { OrthancPlugins::DicomWebServers::GetInstance().DeleteServer(request->groups[0]); + OrthancPlugins::Configuration::SaveDicomWebServers(); + std::string answer = "{}"; OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json"); break; @@ -141,6 +143,8 @@ Orthanc::WebServiceParameters parameters(body); OrthancPlugins::DicomWebServers::GetInstance().SetServer(request->groups[0], parameters); + OrthancPlugins::Configuration::SaveDicomWebServers(); + std::string answer = "{}"; OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json"); break; @@ -449,6 +453,36 @@ #endif +static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, + OrthancPluginResourceType resourceType, + const char *resourceId) +{ + try + { + switch (changeType) + { + case OrthancPluginChangeType_OrthancStarted: + OrthancPlugins::Configuration::LoadDicomWebServers(); + break; + + default: + break; + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception: " << e.What(); + } + catch (...) + { + LOG(ERROR) << "Uncatched native exception"; + } + + return OrthancPluginErrorCode_Success; +} + + + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) @@ -463,6 +497,8 @@ Orthanc::Logging::Initialize(context); #endif + Orthanc::Logging::EnableInfoLevel(true); + /* Check the version of the Orthanc core */ if (OrthancPluginCheckVersion(context) == 0) { @@ -538,6 +574,8 @@ OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true); + OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + // Extend the default Orthanc Explorer with custom JavaScript for STOW client std::string explorer;
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -313,6 +313,37 @@ } + static bool ReadJsonInternal(Json::Value& target, + const void* buffer, + size_t size, + bool collectComments) + { +#if JSONCPP_USE_DEPRECATED == 1 + Json::Reader reader; + return reader.parse(reinterpret_cast<const char*>(buffer), + reinterpret_cast<const char*>(buffer) + size, target, collectComments); +#else + Json::CharReaderBuilder builder; + builder.settings_["collectComments"] = collectComments; + + const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); + assert(reader.get() != NULL); + + JSONCPP_STRING err; + if (reader->parse(reinterpret_cast<const char*>(buffer), + reinterpret_cast<const char*>(buffer) + size, &target, &err)) + { + return true; + } + else + { + LogError("Cannot parse JSON: " + std::string(err)); + return false; + } +#endif + } + + bool ReadJson(Json::Value& target, const std::string& source) { @@ -324,29 +355,25 @@ const void* buffer, size_t size) { -#if JSONCPP_USE_DEPRECATED == 1 - Json::Reader reader; - return reader.parse(reinterpret_cast<const char*>(buffer), - reinterpret_cast<const char*>(buffer) + size, target); -#else - Json::CharReaderBuilder builder; - const std::unique_ptr<Json::CharReader> reader(builder.newCharReader()); - assert(reader.get() != NULL); - JSONCPP_STRING err; - if (reader->parse(reinterpret_cast<const char*>(buffer), - reinterpret_cast<const char*>(buffer) + size, &target, &err)) - { - return true; - } - else - { - LogError("Cannot parse JSON: " + err); - return false; - } -#endif + return ReadJsonInternal(target, buffer, size, true); } + bool ReadJsonWithoutComments(Json::Value& target, + const std::string& source) + { + return ReadJsonWithoutComments(target, source.empty() ? NULL : source.c_str(), source.size()); + } + + + bool ReadJsonWithoutComments(Json::Value& target, + const void* buffer, + size_t size) + { + return ReadJsonInternal(target, buffer, size, false); + } + + void WriteFastJson(std::string& target, const Json::Value& source) {
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Mon Jan 18 16:46:17 2021 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Fri Jan 22 14:20:58 2021 +0100 @@ -483,6 +483,13 @@ const void* buffer, size_t size); + bool ReadJsonWithoutComments(Json::Value& target, + const std::string& source); + + bool ReadJsonWithoutComments(Json::Value& target, + const void* buffer, + size_t size); + void WriteFastJson(std::string& target, const Json::Value& source);
--- a/UnitTestsSources/UnitTestsMain.cpp Mon Jan 18 16:46:17 2021 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Jan 22 14:20:58 2021 +0100 @@ -24,6 +24,7 @@ #include <boost/algorithm/string/predicate.hpp> #include "../Plugin/Configuration.h" +#include "../Plugin/DicomWebServers.h" using namespace OrthancPlugins; @@ -61,6 +62,50 @@ } +TEST(DicomWebServers, Serialization) +{ + std::list<std::string> servers; + DicomWebServers::GetInstance().ListServers(servers); + ASSERT_TRUE(servers.empty()); + + { + std::string json; + DicomWebServers::GetInstance().SerializeGlobalProperty(json); + DicomWebServers::GetInstance().UnserializeGlobalProperty(json); + ASSERT_TRUE(servers.empty()); + } + + Orthanc::WebServiceParameters p; + p.SetUrl("http://hello/"); + p.SetCredentials("user", "world"); + DicomWebServers::GetInstance().SetServer("test", p); + + std::string json; + DicomWebServers::GetInstance().SerializeGlobalProperty(json); + + p.SetUrl("http://nope/"); + p.ClearCredentials(); + DicomWebServers::GetInstance().SetServer("nope", p); + + DicomWebServers::GetInstance().ListServers(servers); + ASSERT_EQ(2u, servers.size()); + + DicomWebServers::GetInstance().UnserializeGlobalProperty(json); + + DicomWebServers::GetInstance().ListServers(servers); + ASSERT_EQ(1u, servers.size()); + + ASSERT_THROW(DicomWebServers::GetInstance().GetServer("nope"), Orthanc::OrthancException); + p = DicomWebServers::GetInstance().GetServer("test"); + ASSERT_EQ("http://hello/", p.GetUrl()); + ASSERT_EQ("user", p.GetUsername()); + + DicomWebServers::GetInstance().Clear(); + DicomWebServers::GetInstance().ListServers(servers); + ASSERT_TRUE(servers.empty()); +} + + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv);
