# HG changeset patch # User Alain Mazy # Date 1594380407 -7200 # Node ID 2ccde9c7311bb4361ee2ea0edd6091a70253d41c # Parent 84b6fc6f6b9b3df7b3c3d8cd7bc44476c2435a14 added new optimized REST routes. this is a temporary work to try to speed up some routes (used by LRO). This way, we avoid another app to access the Orthanc DB and we skip the plugin SDK update for a very specific route diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Tue Jul 07 20:38:40 2020 +0200 +++ b/Framework/Plugins/IndexBackend.cpp Fri Jul 10 13:26:47 2020 +0200 @@ -30,7 +30,7 @@ #include // For std::unique_ptr<> #include #include - +#include namespace OrthancDatabases { @@ -1952,6 +1952,88 @@ } } + void IndexBackend::GetStudyInstancesIds(std::list& target /*out*/, + std::string& publicStudyId) + { + DatabaseManager::CachedStatement statement( + STATEMENT_FROM_HERE, manager_, + "SELECT instances.publicid FROM resources instances" + " INNER JOIN resources series ON instances.parentid = series.internalid" + " INNER JOIN resources studies ON series.parentid = studies.internalid" + " WHERE studies.publicId = ${id}" + ); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Utf8String); + + Dictionary args; + args.SetUtf8Value("id", publicStudyId); + + ReadListOfStrings(target, statement, args); + } + + + void IndexBackend::GetStudyInstancesMetadata(std::map>& target /*out*/, + std::string& publicStudyId, + std::list metadataTypes) + { + { + std::string sql = "SELECT instances.publicid, metadata.type, metadata.value FROM resources instances " + "LEFT JOIN metadata ON metadata.id = instances.internalid " + "INNER JOIN resources series ON instances.parentid = series.internalid " + "INNER JOIN resources studies ON series.parentid = studies.internalid " + " WHERE studies.publicId = ${id} "; + + + if (metadataTypes.size() != 0) + { + std::list metadataTypesStrings; + for (std::list::const_iterator m = metadataTypes.begin(); m != metadataTypes.end(); m++) + { + metadataTypesStrings.push_back(boost::lexical_cast(*m)); + } + + std::string metadataTypesFilter = boost::algorithm::join(metadataTypesStrings, ","); + sql = sql + " AND metadata.type IN (" + metadataTypesFilter + ")"; + } + + DatabaseManager::StandaloneStatement statement(manager_, sql); + + statement.SetReadOnly(true); + statement.SetParameterType("id", ValueType_Utf8String); + + Dictionary args; + args.SetUtf8Value("id", publicStudyId); + + statement.Execute(args); + + target.clear(); + + if (!statement.IsDone()) + { + if (statement.GetResultFieldsCount() != 3) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + while (!statement.IsDone()) + { + std::string instanceId = ReadString(statement, 0); + int32_t type = ReadInteger32(statement, 1); + std::string value = ReadString(statement, 2); + + if (target.find(instanceId) == target.end()) + { + target[instanceId] = std::map(); + } + target[instanceId][type] = value; + + statement.Next(); + } + } + } + } + #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in 1.3.1 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 4) diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Tue Jul 07 20:38:40 2020 +0200 +++ b/Framework/Plugins/IndexBackend.h Fri Jul 10 13:26:47 2020 +0200 @@ -258,6 +258,15 @@ virtual void GetChildren(std::list& childrenPublicIds, int64_t id); + // For optimized routes + virtual void GetStudyInstancesMetadata(std::map>& target /*out*/, + std::string& publicStudyId, + std::list metadataTypes); + + // For optimized routes + virtual void GetStudyInstancesIds(std::list& target /*out*/, + std::string& publicStudyId); + #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1 // New primitive since Orthanc 1.5.2 virtual void LookupResources(const std::vector& lookup, diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/Plugins/OptimizedRoutes.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/OptimizedRoutes.cpp Fri Jul 10 13:26:47 2020 +0200 @@ -0,0 +1,120 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "OptimizedRoutes.h" +#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include +#include +#include +#include +#include +#include // for boost::algorithm::split & boost::starts_with + +static OrthancDatabases::IndexBackend* backend_; +static OrthancPluginContext* context_; + +extern "C" +{ + + // handles url like /optimized-routes/studies/{id)/instances-metadata?types=1,10 + OrthancPluginErrorCode RestGetStudiesInstancesMetadata(OrthancPluginRestOutput* output, + const char* /* url */, + const OrthancPluginHttpRequest* request) + { + std::list metadataTypes; + for (uint32_t i = 0; i < request->getCount; i++) + { + if (strcmp(request->getKeys[i], "types") == 0) + { + std::string getValue(request->getValues[i]); + std::vector typesString; + boost::algorithm::split(typesString, getValue, boost::is_any_of(",")); + + for (size_t t = 0; t < typesString.size(); t++) + { + int32_t metadataType = boost::lexical_cast(typesString[t]); + metadataTypes.push_back(metadataType); + } + } + } + + std::map> result; + std::string orthancId = std::string(request->groups[0]); + backend_->GetStudyInstancesMetadata(result, + orthancId, + metadataTypes); + + Json::Value response(Json::objectValue); + for (std::map>::const_iterator itInstance = result.begin(); itInstance != result.end(); itInstance++) + { + Json::Value instanceMetadatas; + for (std::map::const_iterator itMetadata = itInstance->second.begin(); itMetadata != itInstance->second.end(); itMetadata++) + { + std::string id = boost::lexical_cast(itMetadata->first); + instanceMetadatas[id] = itMetadata->second; + } + + response[itInstance->first] = instanceMetadatas; + } + + Json::StyledWriter writer; + std::string outputStr = writer.write(response); + OrthancPluginAnswerBuffer(context_, output, outputStr.c_str(), outputStr.size(), "application/json"); + + return OrthancPluginErrorCode_Success; + } + + // handles url like /optimized-routes/studies/{id)/instances-ids + OrthancPluginErrorCode RestGetStudiesInstancesIds(OrthancPluginRestOutput* output, + const char* /*url*/, + const OrthancPluginHttpRequest* request) + { + std::list result; + std::string orthancId = std::string(request->groups[0]); + backend_->GetStudyInstancesIds(result, + orthancId); + + Json::Value response(Json::arrayValue); + for (std::list::const_iterator itInstance = result.begin(); itInstance != result.end(); itInstance++) + { + response.append(*itInstance); + } + + Json::StyledWriter writer; + std::string outputStr = writer.write(response); + OrthancPluginAnswerBuffer(context_, output, outputStr.c_str(), outputStr.size(), "application/json"); + + return OrthancPluginErrorCode_Success; + } +} + +namespace OrthancDatabases { + + void OptimizedRoutes::EnableOptimizedRoutes(IndexBackend *backend, OrthancPluginContext *context) + { + backend_ = backend; + context_ = context; + // Register optimized rest routes (temporary) + OrthancPluginRegisterRestCallbackNoLock(context_, "/optimized-routes/studies/(.*)/instances-metadata", RestGetStudiesInstancesMetadata); + OrthancPluginRegisterRestCallbackNoLock(context_, "/optimized-routes/studies/(.*)/instances-ids", RestGetStudiesInstancesIds); + } +} diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/Plugins/OptimizedRoutes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/OptimizedRoutes.h Fri Jul 10 13:26:47 2020 +0200 @@ -0,0 +1,38 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include "IndexBackend.h" + +namespace OrthancDatabases +{ + + // this class exposes Rest API routes that have been optimized for speed + // and that are usually very specific to a particular use case + class OptimizedRoutes + { + public: + static void EnableOptimizedRoutes(IndexBackend* backend, OrthancPluginContext* context); + }; + +} diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/PostgreSQL/PostgreSQLParameters.cpp --- a/Framework/PostgreSQL/PostgreSQLParameters.cpp Tue Jul 07 20:38:40 2020 +0200 +++ b/Framework/PostgreSQL/PostgreSQLParameters.cpp Fri Jul 10 13:26:47 2020 +0200 @@ -41,6 +41,7 @@ lock_ = true; maxConnectionRetries_ = 10; connectionRetryInterval_ = 5; + enabledOptimizedRoutes_ = false; } @@ -92,6 +93,7 @@ } lock_ = configuration.GetBooleanValue("Lock", true); // Use locking by default + enabledOptimizedRoutes_ = configuration.GetBooleanValue("EnableOptimizedRoutes", false); maxConnectionRetries_ = configuration.GetUnsignedIntegerValue("MaximumConnectionRetries", 10); connectionRetryInterval_ = configuration.GetUnsignedIntegerValue("ConnectionRetryInterval", 5); diff -r 84b6fc6f6b9b -r 2ccde9c7311b Framework/PostgreSQL/PostgreSQLParameters.h --- a/Framework/PostgreSQL/PostgreSQLParameters.h Tue Jul 07 20:38:40 2020 +0200 +++ b/Framework/PostgreSQL/PostgreSQLParameters.h Fri Jul 10 13:26:47 2020 +0200 @@ -42,6 +42,7 @@ bool lock_; unsigned int maxConnectionRetries_; unsigned int connectionRetryInterval_; + bool enabledOptimizedRoutes_; void Reset(); @@ -124,6 +125,11 @@ return connectionRetryInterval_; } + bool GetEnabledOptimizedRoutes() const + { + return enabledOptimizedRoutes_; + } + void Format(std::string& target) const; }; } diff -r 84b6fc6f6b9b -r 2ccde9c7311b PostgreSQL/NEWS --- a/PostgreSQL/NEWS Tue Jul 07 20:38:40 2020 +0200 +++ b/PostgreSQL/NEWS Fri Jul 10 13:26:47 2020 +0200 @@ -5,7 +5,7 @@ * Added "MaximumConnectionRetries" & "ConnectionRetryInterval" to configure the retries when connecting to the DB at startup * Support of dynamic linking against the system-wide Orthanc framework library - +* Added new optimized Rest API routes (work-in-progress) Release 3.2 (2019-03-01) ======================== diff -r 84b6fc6f6b9b -r 2ccde9c7311b PostgreSQL/Plugins/IndexPlugin.cpp --- a/PostgreSQL/Plugins/IndexPlugin.cpp Tue Jul 07 20:38:40 2020 +0200 +++ b/PostgreSQL/Plugins/IndexPlugin.cpp Fri Jul 10 13:26:47 2020 +0200 @@ -21,12 +21,13 @@ #include "PostgreSQLIndex.h" #include "../../Framework/Plugins/PluginInitialization.h" +#include "../../Framework/Plugins/OptimizedRoutes.h" #include // For std::unique_ptr<> #include static std::unique_ptr backend_; - +static std::unique_ptr optimizedRoutes_; extern "C" { @@ -66,6 +67,11 @@ /* Register the PostgreSQL index into Orthanc */ OrthancPlugins::DatabaseBackendAdapter::Register(context, *backend_); + + if (parameters.GetEnabledOptimizedRoutes()) + { + OrthancDatabases::OptimizedRoutes::EnableOptimizedRoutes(backend_.get(), context); + } } catch (Orthanc::OrthancException& e) { diff -r 84b6fc6f6b9b -r 2ccde9c7311b Resources/CMake/DatabasesPluginConfiguration.cmake --- a/Resources/CMake/DatabasesPluginConfiguration.cmake Tue Jul 07 20:38:40 2020 +0200 +++ b/Resources/CMake/DatabasesPluginConfiguration.cmake Fri Jul 10 13:26:47 2020 +0200 @@ -70,6 +70,7 @@ ${ORTHANC_CORE_SOURCES} ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/GlobalProperties.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp + ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/OptimizedRoutes.cpp ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/StorageBackend.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/DatabaseConstraint.cpp ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp