# HG changeset patch # User Alain Mazy # Date 1605174678 -3600 # Node ID 215782cadfc2d47e15230f1840bc8bbde3a086aa # Parent 6f83b74373d340b180f4a03b5bf7a34b83630d71# Parent e712ff3eede3726f8894ccac8e5dc0e72b499e60 merge default -> optimized-routes diff -r e712ff3eede3 -r 215782cadfc2 Framework/Plugins/IndexBackend.cpp --- a/Framework/Plugins/IndexBackend.cpp Fri Nov 06 17:51:01 2020 +0100 +++ b/Framework/Plugins/IndexBackend.cpp Thu Nov 12 10:51:18 2020 +0100 @@ -30,7 +30,7 @@ #include // For std::unique_ptr<> #include #include - +#include namespace OrthancDatabases { @@ -1952,6 +1952,95 @@ } } + 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 metadataSqlFilter = ""; + 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, ","); + metadataSqlFilter = "WHERE metadata.type IN (" + metadataTypesFilter + ")"; + } + + std::string sql = "SELECT instances.publicid, metadata.type, metadata.value " + "FROM resources instances " + " LEFT JOIN (select * from metadata " + metadataSqlFilter + + " ) AS 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} "; + + 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); + + if (target.find(instanceId) == target.end()) + { + target[instanceId] = std::map(); + } + + if (statement.GetResultField(1).GetType() != ValueType_Null) + { + int32_t type = ReadInteger32(statement, 1); + std::string value = ReadString(statement, 2); + + 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 e712ff3eede3 -r 215782cadfc2 Framework/Plugins/IndexBackend.h --- a/Framework/Plugins/IndexBackend.h Fri Nov 06 17:51:01 2020 +0100 +++ b/Framework/Plugins/IndexBackend.h Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 Framework/Plugins/OptimizedRoutes.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/OptimizedRoutes.cpp Thu Nov 12 10:51:18 2020 +0100 @@ -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(Json::objectValue); + 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 e712ff3eede3 -r 215782cadfc2 Framework/Plugins/OptimizedRoutes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Plugins/OptimizedRoutes.h Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 Framework/PostgreSQL/PostgreSQLParameters.cpp --- a/Framework/PostgreSQL/PostgreSQLParameters.cpp Fri Nov 06 17:51:01 2020 +0100 +++ b/Framework/PostgreSQL/PostgreSQLParameters.cpp Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 Framework/PostgreSQL/PostgreSQLParameters.h --- a/Framework/PostgreSQL/PostgreSQLParameters.h Fri Nov 06 17:51:01 2020 +0100 +++ b/Framework/PostgreSQL/PostgreSQLParameters.h Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 PostgreSQL/NEWS --- a/PostgreSQL/NEWS Fri Nov 06 17:51:01 2020 +0100 +++ b/PostgreSQL/NEWS Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 PostgreSQL/Plugins/IndexPlugin.cpp --- a/PostgreSQL/Plugins/IndexPlugin.cpp Fri Nov 06 17:51:01 2020 +0100 +++ b/PostgreSQL/Plugins/IndexPlugin.cpp Thu Nov 12 10:51:18 2020 +0100 @@ -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 e712ff3eede3 -r 215782cadfc2 Resources/CMake/DatabasesPluginConfiguration.cmake --- a/Resources/CMake/DatabasesPluginConfiguration.cmake Fri Nov 06 17:51:01 2020 +0100 +++ b/Resources/CMake/DatabasesPluginConfiguration.cmake Thu Nov 12 10:51:18 2020 +0100 @@ -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