changeset 1447:5ba7471780ae

refactoring: HttpToolbox, DumpJson in Lua
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 01 Jul 2015 17:42:06 +0200
parents 8dc80ba768aa
children 3f7722179467
files Core/HttpServer/HttpToolbox.cpp Core/HttpServer/HttpToolbox.h Core/Lua/LuaContext.cpp Core/Lua/LuaContext.h NEWS OrthancServer/LuaScripting.cpp OrthancServer/LuaScripting.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp
diffstat 11 files changed, 392 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpServer/HttpToolbox.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/Core/HttpServer/HttpToolbox.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -194,7 +194,7 @@
   }
 
 
-  bool HttpToolbox::SimpleGet(std::string& output,
+  bool HttpToolbox::SimpleGet(std::string& result,
                               IHttpHandler& handler,
                               const std::string& uri)
   {
@@ -210,7 +210,35 @@
     if (handler.Handle(http, HttpMethod_Get, curi, headers, getArguments, 
                        NULL /* no body for GET */, 0))
     {
-      stream.GetOutput(output);
+      stream.GetOutput(result);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  static bool SimplePostOrPut(std::string& result,
+                              IHttpHandler& handler,
+                              HttpMethod method,
+                              const std::string& uri,
+                              const char* bodyData,
+                              size_t bodySize)
+  {
+    IHttpHandler::Arguments headers;  // No HTTP header
+    IHttpHandler::GetArguments getArguments;  // No GET argument for POST/PUT
+
+    UriComponents curi;
+    Toolbox::SplitUriComponents(curi, uri);
+
+    StringHttpOutput stream;
+    HttpOutput http(stream, false /* no keep alive */);
+
+    if (handler.Handle(http, method, curi, headers, getArguments, bodyData, bodySize))
+    {
+      stream.GetOutput(result);
       return true;
     }
     else
@@ -219,4 +247,40 @@
     }
   }
 
+
+  bool HttpToolbox::SimplePost(std::string& result,
+                               IHttpHandler& handler,
+                               const std::string& uri,
+                               const char* bodyData,
+                               size_t bodySize)
+  {
+    return SimplePostOrPut(result, handler, HttpMethod_Post, uri, bodyData, bodySize);
+  }
+
+
+  bool HttpToolbox::SimplePut(std::string& result,
+                              IHttpHandler& handler,
+                              const std::string& uri,
+                              const char* bodyData,
+                              size_t bodySize)
+  {
+    return SimplePostOrPut(result, handler, HttpMethod_Put, uri, bodyData, bodySize);
+  }
+
+
+  bool HttpToolbox::SimpleDelete(IHttpHandler& handler,
+                                 const std::string& uri)
+  {
+    UriComponents curi;
+    Toolbox::SplitUriComponents(curi, uri);
+
+    IHttpHandler::Arguments headers;  // No HTTP header
+    IHttpHandler::GetArguments getArguments;  // No GET argument for DELETE
+
+    StringHttpOutput stream;
+    HttpOutput http(stream, false /* no keep alive */);
+
+    return handler.Handle(http, HttpMethod_Delete, curi, headers, getArguments, 
+                          NULL /* no body for DELETE */, 0);
+  }
 }
--- a/Core/HttpServer/HttpToolbox.h	Wed Jul 01 13:16:12 2015 +0200
+++ b/Core/HttpServer/HttpToolbox.h	Wed Jul 01 17:42:06 2015 +0200
@@ -60,8 +60,23 @@
     static void CompileGetArguments(IHttpHandler::Arguments& compiled,
                                     const IHttpHandler::GetArguments& source);
 
-    static bool SimpleGet(std::string& output,
+    static bool SimpleGet(std::string& result,
                           IHttpHandler& handler,
                           const std::string& uri);
+
+    static bool SimplePost(std::string& result,
+                           IHttpHandler& handler,
+                           const std::string& uri,
+                           const char* bodyData,
+                           size_t bodySize);
+
+    static bool SimplePut(std::string& result,
+                          IHttpHandler& handler,
+                          const std::string& uri,
+                          const char* bodyData,
+                          size_t bodySize);
+
+    static bool SimpleDelete(IHttpHandler& handler,
+                             const std::string& uri);
   };
 }
--- a/Core/Lua/LuaContext.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/Core/Lua/LuaContext.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -33,8 +33,10 @@
 #include "../PrecompiledHeaders.h"
 #include "LuaContext.h"
 
+#include <set>
 #include <glog/logging.h>
 #include <cassert>
+#include <boost/lexical_cast.hpp>
 
 extern "C" 
 {
@@ -90,7 +92,7 @@
   }
 
 
-  int LuaContext::ParseJsonString(lua_State *state)
+  int LuaContext::ParseJson(lua_State *state)
   {
     LuaContext& that = GetLuaContext(state);
 
@@ -119,6 +121,32 @@
   }
 
 
+  int LuaContext::DumpJson(lua_State *state)
+  {
+    int nArgs = lua_gettop(state);
+    if (nArgs != 1)
+    {
+      lua_pushnil(state);
+      return 1;
+    }
+
+    Json::Value json;
+    if (GetJson(json, state, 1))
+    {
+      Json::FastWriter writer;
+      std::string s = writer.write(json);
+      lua_pushstring(state, s.c_str());
+    }
+    else
+    {
+      LOG(ERROR) << "Lua: Unable to convert a JSON variable to a string";
+      lua_pushnil(state);
+    }
+
+    return 1;
+  }
+
+
   int LuaContext::SetHttpCredentials(lua_State *state)
   {
     LuaContext& that = GetLuaContext(state);
@@ -348,6 +376,135 @@
   }
 
 
+  static bool CompactObjectToArray(Json::Value& result,
+                                   const Json::Value& source)
+  {
+    Json::Value::Members members = source.getMemberNames();
+
+    std::set<size_t> keys;
+    for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
+    {
+      try
+      {
+        size_t key = boost::lexical_cast<size_t>(members[i]);
+        keys.insert(key);
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return false;
+      }
+    }
+
+    if (keys.size() != members.size())
+    {
+      return false;
+    }
+
+    for (size_t i = 1; i <= members.size(); i++)
+    {
+      if (keys.find(i) == keys.end())
+      {
+        return false;
+      }
+    }
+
+    result = Json::arrayValue;
+    result.resize(members.size());
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      Json::Value::ArrayIndex key = boost::lexical_cast<Json::Value::ArrayIndex>(members[i]);
+      assert(key > 0);
+      result[key - 1] = source[members[i]];
+    }
+
+    return true;
+  }
+
+
+  bool LuaContext::GetJson(Json::Value& result,
+                           lua_State *state,
+                           int i)
+  {
+    // Caution: The order of the calls below is important, otherwise
+    // Lua considers everything as a string.
+
+    if (lua_isnil(state, i))
+    {
+      result = Json::nullValue;
+      return true;
+    }
+
+    if (lua_isboolean(state, i))
+    {
+      result = lua_toboolean(state, i) ? true : false;
+      return true;
+    }
+
+    if (lua_isnumber(state, i))
+    {
+      result = lua_tonumber(state, i);
+      return true;
+    }
+
+    if (lua_isstring(state, i))
+    {
+      result = lua_tostring(state, i);
+      return true;
+    }
+
+    if (lua_istable(state, i))
+    {
+      result = Json::objectValue;
+
+      // http://stackoverflow.com/a/6142700/881731
+      // Push another reference to the table on top of the stack (so we know
+      // where it is, and this function can work for negative, positive and
+      // pseudo indices
+      lua_pushvalue(state, i);
+      // stack now contains: -1 => table
+      lua_pushnil(state);
+      // stack now contains: -1 => nil; -2 => table
+      while (lua_next(state, -2))
+      {
+        // stack now contains: -1 => value; -2 => key; -3 => table
+        // copy the key so that lua_tostring does not modify the original
+        lua_pushvalue(state, -2);
+        // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
+        const char *key = lua_tostring(state, -1);
+
+        Json::Value item;
+        if (!GetJson(item, state, -2))
+        {
+          lua_pop(state, 3);  // TODO IS THIS CORRECT?
+          return false;
+        }
+
+        result[key] = item;
+
+        // pop value + copy of key, leaving original key
+        lua_pop(state, 2);
+        // stack now contains: -1 => key; -2 => table
+      }
+      // stack now contains: -1 => table (when lua_next returns 0 it pops the key
+      // but does not push anything.)
+      // Pop table
+      lua_pop(state, 1);
+
+      // Stack is now the same as it was on entry to this function
+
+      Json::Value array;
+      if (CompactObjectToArray(array, result))
+      {
+        result = array;
+      }
+
+      return true;
+    }
+
+    return false;
+  }
+
+
   LuaContext::LuaContext()
   {
     lua_ = luaL_newstate();
@@ -358,7 +515,8 @@
 
     luaL_openlibs(lua_);
     lua_register(lua_, "print", PrintToLog);
-    lua_register(lua_, "ParseJson", ParseJsonString);
+    lua_register(lua_, "ParseJson", ParseJson);
+    lua_register(lua_, "DumpJson", DumpJson);
     lua_register(lua_, "HttpGet", CallHttpGet);
     lua_register(lua_, "HttpPost", CallHttpPost);
     lua_register(lua_, "HttpPut", CallHttpPut);
--- a/Core/Lua/LuaContext.h	Wed Jul 01 13:16:12 2015 +0200
+++ b/Core/Lua/LuaContext.h	Wed Jul 01 17:42:06 2015 +0200
@@ -57,7 +57,8 @@
     static LuaContext& GetLuaContext(lua_State *state);
 
     static int PrintToLog(lua_State *state);
-    static int ParseJsonString(lua_State *state);
+    static int ParseJson(lua_State *state);
+    static int DumpJson(lua_State *state);
 
     static int SetHttpCredentials(lua_State *state);
 
@@ -74,6 +75,10 @@
                          const std::string& command);
 
     void PushJson(const Json::Value& value);
+
+    static bool GetJson(Json::Value& result,
+                        lua_State *state,
+                        int index);
     
   public:
     LuaContext();
--- a/NEWS	Wed Jul 01 13:16:12 2015 +0200
+++ b/NEWS	Wed Jul 01 17:42:06 2015 +0200
@@ -14,6 +14,7 @@
 Fixes
 -----
 
+* Many code refactorings
 * Fix compatibility issues for C-Find SCU to Siemens Syngo.Via modalities SCP
 * Fix issue 35 (Characters in PatientID string are not protected for C-Find)
 * Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges)
--- a/OrthancServer/LuaScripting.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/OrthancServer/LuaScripting.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -58,6 +58,7 @@
   }
 
 
+  // Syntax in Lua: RestApiGet(uri, builtin)
   int LuaScripting::RestApiGet(lua_State *state)
   {
     ServerContext* serverContext = GetServerContext(state);
@@ -89,7 +90,107 @@
     }
     else
     {
-      LOG(ERROR) << "Lua: Error in RestApiGet() for URI " << uri;
+      LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri;
+      lua_pushnil(state);
+    }
+
+    return 1;
+  }
+
+
+  int LuaScripting::RestApiPostOrPut(lua_State *state,
+                                     bool isPost)
+  {
+    ServerContext* serverContext = GetServerContext(state);
+    if (serverContext == NULL)
+    {
+      LOG(ERROR) << "Lua: The Orthanc API is unavailable";
+      lua_pushnil(state);
+      return 1;
+    }
+
+    // Check the types of the arguments
+    int nArgs = lua_gettop(state);
+    if ((nArgs != 2 && nArgs != 3) || 
+        !lua_isstring(state, 1) ||                 // URI
+        !lua_isstring(state, 2) ||                 // Body
+        (nArgs == 3 && !lua_isboolean(state, 3)))  // Restrict to built-in API?
+    {
+      LOG(ERROR) << "Lua: Bad parameters to " << (isPost ? "RestApiPost()" : "RestApiPut()");
+      lua_pushnil(state);
+      return 1;
+    }
+
+    const char* uri = lua_tostring(state, 1);
+    size_t bodySize = 0;
+    const char* bodyData = lua_tolstring(state, 2, &bodySize);
+    bool builtin = (nArgs == 3 ? lua_toboolean(state, 3) : false);
+
+    std::string result;
+    if (isPost ?
+        HttpToolbox::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                uri, bodyData, bodySize) :
+        HttpToolbox::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                               uri, bodyData, bodySize))
+    {
+      lua_pushstring(state, result.c_str());
+    }
+    else
+    {
+      LOG(ERROR) << "Lua: Error in " << (isPost ? "RestApiPost()" : "RestApiPut()") << " for URI: " << uri;
+      lua_pushnil(state);
+    }
+
+    return 1;
+  }
+
+
+  // Syntax in Lua: RestApiPost(uri, body, builtin)
+  int LuaScripting::RestApiPost(lua_State *state)
+  {
+    return RestApiPostOrPut(state, true);
+  }
+
+
+  // Syntax in Lua: RestApiPut(uri, body, builtin)
+  int LuaScripting::RestApiPut(lua_State *state)
+  {
+    return RestApiPostOrPut(state, false);
+  }
+
+
+  // Syntax in Lua: RestApiDelete(uri, builtin)
+  int LuaScripting::RestApiDelete(lua_State *state)
+  {
+    ServerContext* serverContext = GetServerContext(state);
+    if (serverContext == NULL)
+    {
+      LOG(ERROR) << "Lua: The Orthanc API is unavailable";
+      lua_pushnil(state);
+      return 1;
+    }
+
+    // Check the types of the arguments
+    int nArgs = lua_gettop(state);
+    if ((nArgs != 1 && nArgs != 2) || 
+        !lua_isstring(state, 1) ||                 // URI
+        (nArgs == 2 && !lua_isboolean(state, 2)))  // Restrict to built-in API?
+    {
+      LOG(ERROR) << "Lua: Bad parameters to RestApiGet()";
+      lua_pushnil(state);
+      return 1;
+    }
+
+    const char* uri = lua_tostring(state, 1);
+    bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) : false);
+
+    if (HttpToolbox::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), uri))
+    {
+      lua_pushboolean(state, 1);
+    }
+    else
+    {
+      LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri;
       lua_pushnil(state);
     }
 
@@ -251,6 +352,9 @@
   {
     lua_.SetGlobalVariable("_ServerContext", &context);
     lua_.RegisterFunction("RestApiGet", RestApiGet);
+    lua_.RegisterFunction("RestApiPost", RestApiPost);
+    lua_.RegisterFunction("RestApiPut", RestApiPut);
+    lua_.RegisterFunction("RestApiDelete", RestApiDelete);
 
     lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
     lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
@@ -329,8 +433,9 @@
     }
 
 
-    Json::Value tags;
-    if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()))
+    Json::Value tags, metadata;
+    if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()) &&
+        context_.GetIndex().GetMetadata(metadata, change.GetPublicId()))
     {
       boost::mutex::scoped_lock lock(mutex_);
 
@@ -341,6 +446,7 @@
         LuaFunctionCall call(lua_, name);
         call.PushString(change.GetPublicId());
         call.PushJson(tags["MainDicomTags"]);
+        call.PushJson(metadata);
         call.Execute();
 
         SubmitJob(std::string("Lua script: ") + name);
--- a/OrthancServer/LuaScripting.h	Wed Jul 01 13:16:12 2015 +0200
+++ b/OrthancServer/LuaScripting.h	Wed Jul 01 17:42:06 2015 +0200
@@ -45,7 +45,12 @@
   private:
     static ServerContext* GetServerContext(lua_State *state);
 
+    static int RestApiPostOrPut(lua_State *state,
+                                bool isPost);
     static int RestApiGet(lua_State *state);
+    static int RestApiPost(lua_State *state);
+    static int RestApiPut(lua_State *state);
+    static int RestApiDelete(lua_State *state);
 
     void ApplyOnStoredInstance(const std::string& instanceId,
                                const Json::Value& simplifiedDicom,
--- a/OrthancServer/ServerContext.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -71,7 +71,7 @@
   {
     while (!that->done_)
     {
-      std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(500));
+      std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(100));
         
       if (obj.get() != NULL)
       {
@@ -119,11 +119,20 @@
   
   ServerContext::~ServerContext()
   {
-    done_ = true;
+    Stop();
+  }
+
 
-    if (changeThread_.joinable())
+  void ServerContext::Stop()
+  {
+    if (!done_)
     {
-      changeThread_.join();
+      done_ = true;
+
+      if (changeThread_.joinable())
+      {
+        changeThread_.join();
+      }
     }
   }
 
--- a/OrthancServer/ServerContext.h	Wed Jul 01 13:16:12 2015 +0200
+++ b/OrthancServer/ServerContext.h	Wed Jul 01 17:42:06 2015 +0200
@@ -237,6 +237,8 @@
       return httpHandler_;
     }
 
+    void Stop();
+
 
     /**
      * Management of the plugins
--- a/OrthancServer/main.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/OrthancServer/main.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -545,6 +545,8 @@
     // We're done
     LOG(WARNING) << "Orthanc is stopping";
 
+    context->Stop();
+
 #if ENABLE_PLUGINS == 1
     context->ResetPlugins();
     plugins.ResetOrthancRestApi();
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Jul 01 13:16:12 2015 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Jul 01 17:42:06 2015 +0200
@@ -642,38 +642,17 @@
     const _OrthancPluginRestApiPostPut& p = 
       *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
 
-    // TODO : Use "HttpToolbox::SimplePost()"
-
-    IHttpHandler::Arguments headers;  // No HTTP header
-    IHttpHandler::GetArguments getArguments;  // No GET argument for POST/PUT
+    LOG(INFO) << "Plugin making REST " << EnumerationToString(isPost ? HttpMethod_Post : HttpMethod_Put)
+              << " call on URI " << p.uri << (afterPlugins ? " (after plugins)" : " (built-in API)");
 
-    UriComponents uri;
-    Toolbox::SplitUriComponents(uri, p.uri);
-
-    StringHttpOutput stream;
-    HttpOutput http(stream, false /* no keep alive */);
-
-    HttpMethod method = (isPost ? HttpMethod_Post : HttpMethod_Put);
-    LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri
-              << (afterPlugins ? " (after plugins)" : " (built-in API)");
+    CheckContextAvailable();
+    IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
 
-    bool ok = false;
     std::string result;
-
-    if (afterPlugins)
+    if (isPost ? 
+        HttpToolbox::SimplePost(result, handler, p.uri, p.body, p.bodySize) :
+        HttpToolbox::SimplePut (result, handler, p.uri, p.body, p.bodySize))
     {
-      ok = Handle(http, method, uri, headers, getArguments, p.body, p.bodySize);
-    }
-    
-    if (!ok)
-    {
-      ok = (pimpl_->restApi_ != NULL &&
-            pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, p.body, p.bodySize));
-    }
-
-    if (ok)
-    {
-      stream.GetOutput(result);
       CopyToMemoryBuffer(*p.target, result);
     }
     else
@@ -686,38 +665,14 @@
   void OrthancPlugins::RestApiDelete(const void* parameters,
                                      bool afterPlugins)
   {
-    // The "parameters" point to the URI
-    UriComponents uri;
-    Toolbox::SplitUriComponents(uri, reinterpret_cast<const char*>(parameters));
-
-    // TODO : Use "HttpToolbox::SimpleDelete()"
-
-    IHttpHandler::Arguments headers;  // No HTTP header
-    IHttpHandler::GetArguments getArguments;  // No GET argument for POST/PUT
-
-    StringHttpOutput stream;
-    HttpOutput http(stream, false /* no keep alive */);
-
-    LOG(INFO) << "Plugin making REST DELETE call on URI " 
-              << reinterpret_cast<const char*>(parameters)
+    const char* uri = reinterpret_cast<const char*>(parameters);
+    LOG(INFO) << "Plugin making REST DELETE call on URI " << uri
               << (afterPlugins ? " (after plugins)" : " (built-in API)");
 
-    bool ok = false;
-
-    if (afterPlugins)
-    {
-      ok = Handle(http, HttpMethod_Delete, uri, headers, getArguments, 
-                  NULL /* no body for DELETE */, 0);
-    }
+    CheckContextAvailable();
+    IHttpHandler& handler = pimpl_->context_->GetHttpHandler().RestrictToOrthancRestApi(!afterPlugins);
 
-    if (!ok)
-    {
-      ok = (pimpl_->restApi_ != NULL &&
-            pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, 
-                                     NULL /* no body for DELETE */, 0));
-    }
-
-    if (!ok)
+    if (!HttpToolbox::SimpleDelete(handler, uri))
     {
       throw OrthancException(ErrorCode_BadRequest);
     }