changeset 171:215782cadfc2 optimized-routes

merge default -> optimized-routes
author Alain Mazy <alain@mazy.be>
date Thu, 12 Nov 2020 10:51:18 +0100
parents 6f83b74373d3 (diff) e712ff3eede3 (current diff)
children
files
diffstat 9 files changed, 274 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- 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 <Compatibility.h>  // For std::unique_ptr<>
 #include <Logging.h>
 #include <OrthancException.h>
-
+#include <boost/algorithm/string/join.hpp>
 
 namespace OrthancDatabases
 {
@@ -1952,6 +1952,95 @@
     }
   }
 
+  void IndexBackend::GetStudyInstancesIds(std::list<std::string>& 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<std::string, std::map<int32_t, std::string>>& target /*out*/,
+                                               std::string& publicStudyId,
+                                               std::list<int32_t> metadataTypes)
+  {
+    {
+      std::string metadataSqlFilter = "";
+      if (metadataTypes.size() != 0)
+      {
+        std::list<std::string> metadataTypesStrings;
+        for (std::list<int32_t>::const_iterator m = metadataTypes.begin(); m != metadataTypes.end(); m++)
+        {
+          metadataTypesStrings.push_back(boost::lexical_cast<std::string>(*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<std::int32_t, std::string>();
+          }
+
+          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)
--- 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<std::string>& childrenPublicIds,
                              int64_t id);
 
+    // For optimized routes
+    virtual void GetStudyInstancesMetadata(std::map<std::string, std::map<int32_t, std::string>>& target /*out*/,
+                                           std::string& publicStudyId,
+                                           std::list<int32_t> metadataTypes);
+
+    // For optimized routes
+    virtual void GetStudyInstancesIds(std::list<std::string>& 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<Orthanc::DatabaseConstraint>& lookup,
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OptimizedRoutes.h"
+#include "../../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
+
+#include <string>
+#include <map>
+#include <list>
+#include <json/writer.h>
+#include <json/value.h>
+#include <boost/algorithm/string.hpp> // 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<int32_t> 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<std::string> 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<int32_t>(typesString[t]);
+          metadataTypes.push_back(metadataType);
+        }
+      }
+    }
+
+    std::map<std::string, std::map<int32_t, std::string>> result;
+    std::string orthancId = std::string(request->groups[0]);
+    backend_->GetStudyInstancesMetadata(result,
+                                        orthancId,
+                                        metadataTypes);
+
+    Json::Value response(Json::objectValue);
+    for (std::map<std::string, std::map<int32_t, std::string>>::const_iterator itInstance = result.begin(); itInstance != result.end(); itInstance++)
+    {
+      Json::Value instanceMetadatas(Json::objectValue);
+      for (std::map<int32_t, std::string>::const_iterator itMetadata = itInstance->second.begin(); itMetadata != itInstance->second.end(); itMetadata++)
+      {
+        std::string id = boost::lexical_cast<std::string>(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<std::string> result;
+    std::string orthancId = std::string(request->groups[0]);
+    backend_->GetStudyInstancesIds(result,
+                                   orthancId);
+
+    Json::Value response(Json::arrayValue);
+    for (std::list<std::string>::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);
+  }
+}
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+#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);
+  };
+
+}
--- 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);
--- 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;
   };
 }
--- 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)
 ========================
--- 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 <Compatibility.h>  // For std::unique_ptr<>
 #include <Logging.h>
 
 static std::unique_ptr<OrthancDatabases::PostgreSQLIndex> backend_;
-
+static std::unique_ptr<OrthancDatabases::OptimizedRoutes> 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)
     {
--- 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