changeset 407:2d269089078f

reintegration of lua scripting into mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 02 May 2013 16:49:28 +0200
parents fb1d988a978b (current diff) 941ea46e9e26 (diff)
children 5a3a4a25e568 b2c6cc90288c
files CMakeLists.txt Core/OrthancException.h OrthancServer/OrthancInitialization.cpp OrthancServer/OrthancInitialization.h OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h OrthancServer/main.cpp Resources/CMake/MongooseConfiguration.cmake Resources/CMake/OpenSslConfiguration.cmake Resources/Configuration.json
diffstat 19 files changed, 821 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Apr 30 15:26:34 2013 +0200
+++ b/CMakeLists.txt	Thu May 02 16:49:28 2013 +0200
@@ -19,6 +19,7 @@
 SET(USE_DYNAMIC_GOOGLE_TEST ON CACHE BOOL "Use the dynamic version of Google Test (not for Debian sid)")
 SET(USE_DYNAMIC_SQLITE ON CACHE BOOL "Use the dynamic version of SQLite")
 SET(USE_DYNAMIC_MONGOOSE OFF CACHE BOOL "Use the dynamic version of Mongoose")
+SET(USE_DYNAMIC_LUA OFF CACHE BOOL "Use the dynamic version of Lua")
 SET(DEBIAN_FORCE_HARDENING OFF CACHE BOOL "Force the injection of Debian hardening flags (unrecommended)")
 SET(DEBIAN_USE_GTEST_SOURCE_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (only for Debian sid)")
 SET(ONLY_CORE_LIBRARY OFF CACHE BOOL "Only build the core library")
@@ -73,12 +74,14 @@
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/JsonCppConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibCurlConfiguration.cmake)
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake)
+include(${CMAKE_SOURCE_DIR}/Resources/CMake/LuaConfiguration.cmake)
 
 
 # Prepare the embedded files
 set(EMBEDDED_FILES
   PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql
   CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json
+  LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
   )
 
 if (${STANDALONE_BUILD})
@@ -143,6 +146,8 @@
   Core/SQLite/Transaction.cpp
   Core/Toolbox.cpp
   Core/Uuid.cpp
+  Core/Lua/LuaContext.cpp
+  Core/Lua/LuaFunctionCall.cpp
 
   OrthancCppClient/HttpClient.cpp
   OrthancCppClient/HttpException.cpp
@@ -201,6 +206,7 @@
       UnitTests/ServerIndex.cpp
       UnitTests/Versions.cpp
       UnitTests/Zip.cpp
+      UnitTests/Lua.cpp
       UnitTests/main.cpp
       )
     target_link_libraries(UnitTests ServerLibrary CoreLibrary)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaContext.cpp	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,133 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LuaContext.h"
+
+#include <glog/logging.h>
+
+extern "C" 
+{
+#include <lualib.h>
+#include <lauxlib.h>
+}
+
+namespace Orthanc
+{
+  int LuaContext::PrintToLog(lua_State *L)
+  {
+    // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/
+    int nArgs = lua_gettop(L);
+    lua_getglobal(L, "tostring");
+
+    // Make sure you start at 1 *NOT* 0 for arrays in Lua.
+    std::string result;
+
+    for (int i = 1; i <= nArgs; i++)
+    {
+      const char *s;
+      lua_pushvalue(L, -1);
+      lua_pushvalue(L, i);
+      lua_call(L, 1, 1);
+      s = lua_tostring(L, -1);
+
+      if (result.size() > 0)
+        result.append(", ");
+
+      if (s == NULL)
+        result.append("<No conversion to string>");
+      else
+        result.append(s);
+ 
+      lua_pop(L, 1);
+    }
+
+    LOG(INFO) << "Lua says: " << result;         
+
+    return 0;
+  }
+
+
+  LuaContext::LuaContext()
+  {
+    lua_ = luaL_newstate();
+    if (!lua_)
+    {
+      throw LuaException("Unable to create the Lua context");
+    }
+
+    luaL_openlibs(lua_);
+    lua_register(lua_, "print", PrintToLog);
+  }
+
+
+  LuaContext::~LuaContext()
+  {
+    lua_close(lua_);
+  }
+
+
+  void LuaContext::Execute(const std::string& command)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    lua_settop(lua_, 0);
+
+    int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") ||
+                 lua_pcall(lua_, 0, 0, 0));
+
+    if (error) 
+    {
+      assert(lua_gettop(lua_) >= 1);
+
+      std::string description(lua_tostring(lua_, -1));
+      lua_pop(lua_, 1); /* pop error message from the stack */
+      throw LuaException(description);
+    }
+  }
+
+
+  void LuaContext::Execute(EmbeddedResources::FileResourceId resource)
+  {
+    std::string command;
+    EmbeddedResources::GetFileResource(command, resource);
+    Execute(command);
+  }
+
+
+  bool LuaContext::IsExistingFunction(const char* name)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    lua_settop(lua_, 0);
+    lua_getglobal(lua_, name);
+    return lua_type(lua_, -1) == LUA_TFUNCTION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaContext.h	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LuaException.h"
+
+#include <boost/thread.hpp>
+
+extern "C" 
+{
+#include <lua.h>
+}
+
+#include <EmbeddedResources.h>
+
+
+namespace Orthanc
+{
+  class LuaContext : public boost::noncopyable
+  {
+  private:
+    friend class LuaFunctionCall;
+
+    lua_State *lua_;
+    boost::mutex mutex_;
+
+    static int PrintToLog(lua_State *L);
+
+  public:
+    LuaContext();
+
+    ~LuaContext();
+
+    void Execute(const std::string& command);
+
+    void Execute(EmbeddedResources::FileResourceId resource);
+
+    bool IsExistingFunction(const char* name);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaException.h	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,52 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  class LuaException : public OrthancException
+  {
+  public:
+    LuaException(const char* explanation) : 
+      OrthancException(explanation)
+    {
+    }
+
+    LuaException(const std::string& explanation) : 
+      OrthancException(explanation)
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaFunctionCall.cpp	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,192 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "LuaFunctionCall.h"
+
+
+namespace Orthanc
+{
+  void LuaFunctionCall::CheckAlreadyExecuted()
+  {
+    if (isExecuted_)
+    {
+      throw LuaException("Arguments cannot be pushed after the function is executed");
+    }
+  }
+
+  LuaFunctionCall::LuaFunctionCall(LuaContext& context,
+                                   const char* functionName) : 
+    context_(context),
+    lock_(context.mutex_),
+    isExecuted_(false)
+  {
+    // Clear the stack to fulfill the invariant
+    lua_settop(context_.lua_, 0);
+    lua_getglobal(context_.lua_, functionName);
+  }
+
+  void LuaFunctionCall::PushString(const std::string& value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushstring(context_.lua_, value.c_str());
+  }
+
+  void LuaFunctionCall::PushBoolean(bool value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushboolean(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushInteger(int value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushinteger(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushDouble(double value)
+  {
+    CheckAlreadyExecuted();
+    lua_pushnumber(context_.lua_, value);
+  }
+
+  void LuaFunctionCall::PushJSON(const Json::Value& value)
+  {
+    CheckAlreadyExecuted();
+
+    if (value.isString())
+    {
+      lua_pushstring(context_.lua_, value.asCString());
+    }
+    else if (value.isDouble())
+    {
+      lua_pushnumber(context_.lua_, value.asDouble());
+    }
+    else if (value.isInt())
+    {
+      lua_pushinteger(context_.lua_, value.asInt());
+    }
+    else if (value.isUInt())
+    {
+      lua_pushinteger(context_.lua_, value.asUInt());
+    }
+    else if (value.isBool())
+    {
+      lua_pushboolean(context_.lua_, value.asBool());
+    }
+    else if (value.isNull())
+    {
+      lua_pushnil(context_.lua_);
+    }
+    else if (value.isArray())
+    {
+      lua_newtable(context_.lua_);
+
+      // http://lua-users.org/wiki/SimpleLuaApiExample
+      for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
+      {
+        // Push the table index (note the "+1" because of Lua conventions)
+        lua_pushnumber(context_.lua_, i + 1);
+
+        // Push the value of the cell
+        PushJSON(value[i]);
+
+        // Stores the pair in the table
+        lua_rawset(context_.lua_, -3);
+      }
+    }
+    else if (value.isObject())
+    {
+      lua_newtable(context_.lua_);
+
+      Json::Value::Members members = value.getMemberNames();
+
+      for (Json::Value::Members::const_iterator 
+             it = members.begin(); it != members.end(); it++)
+      {
+        // Push the index of the cell
+        lua_pushstring(context_.lua_, it->c_str());
+
+        // Push the value of the cell
+        PushJSON(value[*it]);
+
+        // Stores the pair in the table
+        lua_rawset(context_.lua_, -3);
+      }
+    }
+    else
+    {
+      throw LuaException("Unsupported JSON conversion");
+    }
+  }
+
+  void LuaFunctionCall::Execute(int numOutputs)
+  {
+    CheckAlreadyExecuted();
+
+    assert(lua_gettop(context_.lua_) >= 1);
+    int nargs = lua_gettop(context_.lua_) - 1;
+    int error = lua_pcall(context_.lua_, nargs, numOutputs, 0);
+
+    if (error) 
+    {
+      assert(lua_gettop(context_.lua_) >= 1);
+          
+      std::string description(lua_tostring(context_.lua_, -1));
+      lua_pop(context_.lua_, 1); /* pop error message from the stack */
+      throw LuaException(description);
+    }
+
+    if (lua_gettop(context_.lua_) < numOutputs)
+    {
+      throw LuaException("The function does not give the expected number of outputs");
+    }
+
+    isExecuted_ = true;
+  }
+
+  bool LuaFunctionCall::ExecutePredicate()
+  {
+    Execute(1);
+        
+    if (lua_gettop(context_.lua_) == 0)
+    {
+      throw LuaException("No output was provided by the function");
+    }
+
+    if (!lua_isboolean(context_.lua_, 1))
+    {
+      throw LuaException("The function is not a predicate (only true/false outputs allowed)");
+    }
+
+    return lua_toboolean(context_.lua_, 1) != 0;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Lua/LuaFunctionCall.h	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "LuaContext.h"
+
+#include <json/json.h>
+
+
+namespace Orthanc
+{
+  class LuaFunctionCall : public boost::noncopyable
+  {
+  private:
+    LuaContext& context_;
+    boost::mutex::scoped_lock lock_;
+    bool isExecuted_;
+
+    void CheckAlreadyExecuted();
+
+  public:
+    LuaFunctionCall(LuaContext& context,
+                    const char* functionName);
+
+    void PushString(const std::string& value);
+
+    void PushBoolean(bool value);
+
+    void PushInteger(int value);
+
+    void PushDouble(double value);
+
+    void PushJSON(const Json::Value& value);
+
+    void Execute(int numOutputs = 0);
+
+    bool ExecutePredicate();
+  };
+}
--- a/Core/OrthancException.h	Tue Apr 30 15:26:34 2013 +0200
+++ b/Core/OrthancException.h	Thu May 02 16:49:28 2013 +0200
@@ -39,13 +39,19 @@
 {
   class OrthancException
   {
-  private:
+  protected:
     ErrorCode error_;
     std::string custom_;
 
   public:
     static const char* GetDescription(ErrorCode error);
 
+    OrthancException(const char* custom)
+    {
+      error_ = ErrorCode_Custom;
+      custom_ = custom;
+    }
+
     OrthancException(const std::string& custom)
     {
       error_ = ErrorCode_Custom;
--- a/OrthancServer/OrthancInitialization.cpp	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Thu May 02 16:49:28 2013 +0200
@@ -47,6 +47,7 @@
 
   static boost::mutex globalMutex_;
   static std::auto_ptr<Json::Value> configuration_;
+  static boost::filesystem::path defaultDirectory_;
 
 
   static void ReadGlobalConfiguration(const char* configurationFile)
@@ -58,6 +59,7 @@
     if (configurationFile)
     {
       Toolbox::ReadFile(content, configurationFile);
+      defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path();
       LOG(INFO) << "Using the configuration from: " << configurationFile;
     }
     else
@@ -119,6 +121,7 @@
   void OrthancInitialize(const char* configurationFile)
   {
     boost::mutex::scoped_lock lock(globalMutex_);
+    defaultDirectory_ = boost::filesystem::current_path();
     ReadGlobalConfiguration(configurationFile);
     curl_global_init(CURL_GLOBAL_ALL);
   }
@@ -275,4 +278,37 @@
       httpServer.RegisterUser(username.c_str(), password.c_str());
     }
   }
+
+
+  std::string InterpretStringParameterAsPath(const std::string& parameter)
+  {
+    boost::mutex::scoped_lock lock(globalMutex_);
+    return (defaultDirectory_ / parameter).string();
+  }
+
+
+  void GetGlobalListOfStringsParameter(std::list<std::string>& target,
+                                       const std::string& key)
+  {
+    boost::mutex::scoped_lock lock(globalMutex_);
+
+    target.clear();
+  
+    if (!configuration_->isMember(key))
+    {
+      return;
+    }
+
+    const Json::Value& lst = (*configuration_) [key];
+
+    if (lst.type() != Json::arrayValue)
+    {
+      throw OrthancException("Badly formatted list of strings");
+    }
+
+    for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
+    {
+      target.push_back(lst[i].asString());
+    }    
+  }
 }
--- a/OrthancServer/OrthancInitialization.h	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/OrthancInitialization.h	Thu May 02 16:49:28 2013 +0200
@@ -61,4 +61,9 @@
   void GetListOfDicomModalities(std::set<std::string>& target);
 
   void SetupRegisteredUsers(MongooseServer& httpServer);
+
+  std::string InterpretStringParameterAsPath(const std::string& parameter);
+
+  void GetGlobalListOfStringsParameter(std::list<std::string>& target,
+                                       const std::string& key);
 }
--- a/OrthancServer/ServerContext.cpp	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu May 02 16:49:28 2013 +0200
@@ -33,11 +33,15 @@
 #include "ServerContext.h"
 
 #include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/Lua/LuaFunctionCall.h"
+#include "ServerToolbox.h"
 
 #include <glog/logging.h>
+#include <EmbeddedResources.h>
 
 #define ENABLE_DICOM_CACHE  1
 
+static const char* RECEIVED_INSTANCE_FILTER = "ReceivedInstanceFilter";
 
 static const size_t DICOM_CACHE_SIZE = 2;
 
@@ -60,6 +64,7 @@
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE)
   {
+    lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
   }
 
   void ServerContext::SetCompressionEnabled(bool enabled)
@@ -83,6 +88,23 @@
                                    const Json::Value& dicomJson,
                                    const std::string& remoteAet)
   {
+    // Test if the instance must be filtered out
+    if (lua_.IsExistingFunction(RECEIVED_INSTANCE_FILTER))
+    {
+      Json::Value simplified;
+      SimplifyTags(simplified, dicomJson);
+
+      LuaFunctionCall call(lua_, RECEIVED_INSTANCE_FILTER);
+      call.PushJSON(simplified);
+      call.PushString(remoteAet);
+
+      if (!call.ExecutePredicate())
+      {
+        LOG(INFO) << "An incoming instance has been discarded by a filter";
+        return StoreStatus_FilteredOut;
+      }
+    }
+
     if (compressionEnabled_)
     {
       accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
@@ -109,17 +131,21 @@
 
     switch (status)
     {
-    case StoreStatus_Success:
-      LOG(INFO) << "New instance stored";
-      break;
+      case StoreStatus_Success:
+        LOG(INFO) << "New instance stored";
+        break;
+
+      case StoreStatus_AlreadyStored:
+        LOG(INFO) << "Already stored";
+        break;
 
-    case StoreStatus_AlreadyStored:
-      LOG(INFO) << "Already stored";
-      break;
+      case StoreStatus_Failure:
+        LOG(ERROR) << "Store failure";
+        break;
 
-    case StoreStatus_Failure:
-      LOG(ERROR) << "Store failure";
-      break;
+      default:
+        // This should never happen
+        break;
     }
 
     return status;
--- a/OrthancServer/ServerContext.h	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/ServerContext.h	Thu May 02 16:49:28 2013 +0200
@@ -36,6 +36,7 @@
 #include "../Core/FileStorage/CompressedFileStorageAccessor.h"
 #include "../Core/FileStorage/FileStorage.h"
 #include "../Core/RestApi/RestApiOutput.h"
+#include "../Core/Lua/LuaContext.h"
 #include "ServerIndex.h"
 #include "FromDcmtkBridge.h"
 
@@ -70,6 +71,8 @@
     DicomCacheProvider provider_;
     MemoryCache dicomCache_;
 
+    LuaContext lua_;
+
   public:
     ServerContext(const boost::filesystem::path& storagePath,
                   const boost::filesystem::path& indexPath);
@@ -129,5 +132,10 @@
 
     // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD
     ParsedDicomFile& GetDicomFile(const std::string& instancePublicId);
+
+    LuaContext& GetLuaContext()
+    {
+      return lua_;
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Thu May 02 16:49:28 2013 +0200
@@ -112,6 +112,9 @@
       case StoreStatus_Failure:
         return "Failure";
 
+      case StoreStatus_FilteredOut:
+        return "FilteredOut";
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
--- a/OrthancServer/ServerEnumerations.h	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Thu May 02 16:49:28 2013 +0200
@@ -47,7 +47,8 @@
   {
     StoreStatus_Success,
     StoreStatus_AlreadyStored,
-    StoreStatus_Failure
+    StoreStatus_Failure,
+    StoreStatus_FilteredOut     // Removed by NewInstanceFilter
   };
 
 
--- a/OrthancServer/main.cpp	Tue Apr 30 15:26:34 2013 +0200
+++ b/OrthancServer/main.cpp	Thu May 02 16:49:28 2013 +0200
@@ -38,7 +38,7 @@
 
 #include "../Core/HttpServer/EmbeddedResourceHttpHandler.h"
 #include "../Core/HttpServer/FilesystemHttpHandler.h"
-#include "../Core/HttpServer/MongooseServer.h"
+#include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/DicomFormat/DicomArray.h"
 #include "DicomProtocol/DicomServer.h"
 #include "OrthancInitialization.h"
@@ -51,11 +51,11 @@
 class MyStoreRequestHandler : public IStoreRequestHandler
 {
 private:
-  ServerContext& context_;
+  ServerContext& server_;
 
 public:
   MyStoreRequestHandler(ServerContext& context) :
-    context_(context)
+    server_(context)
   {
   }
 
@@ -66,7 +66,7 @@
   {
     if (dicomFile.size() > 0)
     {
-      context_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet);
+      server_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet);
     }
   }
 };
@@ -264,8 +264,10 @@
       OrthancInitialize();
     }
 
-    boost::filesystem::path storageDirectory = GetGlobalStringParameter("StorageDirectory", "OrthancStorage");
-    boost::filesystem::path indexDirectory = GetGlobalStringParameter("IndexDirectory", storageDirectory.string());
+    boost::filesystem::path storageDirectory = 
+      InterpretStringParameterAsPath(GetGlobalStringParameter("StorageDirectory", "OrthancStorage"));
+    boost::filesystem::path indexDirectory = 
+      InterpretStringParameterAsPath(GetGlobalStringParameter("IndexDirectory", storageDirectory.string()));
     ServerContext context(storageDirectory, indexDirectory);
 
     LOG(WARNING) << "Storage directory: " << storageDirectory;
@@ -273,6 +275,19 @@
 
     context.SetCompressionEnabled(GetGlobalBoolParameter("StorageCompression", false));
 
+    std::list<std::string> luaScripts;
+    GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
+    for (std::list<std::string>::const_iterator
+           it = luaScripts.begin(); it != luaScripts.end(); it++)
+    {
+      std::string path = InterpretStringParameterAsPath(*it);
+      LOG(WARNING) << "Installing the Lua scripts from: " << path;
+      std::string script;
+      Toolbox::ReadFile(script, path);
+      context.GetLuaContext().Execute(script);
+    }
+
+
     try
     {
       context.GetIndex().SetMaximumPatientCount(GetGlobalIntegerParameter("MaximumPatientCount", 0));
@@ -315,7 +330,8 @@
 
       if (GetGlobalBoolParameter("SslEnabled", false))
       {
-        std::string certificate = GetGlobalStringParameter("SslCertificate", "certificate.pem");
+        std::string certificate = 
+          InterpretStringParameterAsPath(GetGlobalStringParameter("SslCertificate", "certificate.pem"));
         httpServer.SetSslEnabled(true);
         httpServer.SetSslCertificate(certificate.c_str());
       }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/LuaConfiguration.cmake	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,67 @@
+if (STATIC_BUILD OR NOT USE_DYNAMIC_LUA)
+  SET(LUA_SOURCES_DIR ${CMAKE_BINARY_DIR}/lua-5.1.5)
+  DownloadPackage("http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/lua-5.1.5.tar.gz" "${LUA_SOURCES_DIR}" "" "")
+
+  add_definitions(
+    #-DLUA_LIB=1
+    #-Dluaall_c=1
+    #-DLUA_COMPAT_ALL=1  # Compile a generic version of Lua
+    )
+
+  include_directories(
+    ${LUA_SOURCES_DIR}/src
+    )
+
+  set(LUA_SOURCES
+    # Core Lua
+    ${LUA_SOURCES_DIR}/src/lapi.c
+    ${LUA_SOURCES_DIR}/src/lcode.c 
+    ${LUA_SOURCES_DIR}/src/ldebug.c 
+    ${LUA_SOURCES_DIR}/src/ldo.c 
+    ${LUA_SOURCES_DIR}/src/ldump.c 
+    ${LUA_SOURCES_DIR}/src/lfunc.c 
+    ${LUA_SOURCES_DIR}/src/lgc.c
+    ${LUA_SOURCES_DIR}/src/llex.c
+    ${LUA_SOURCES_DIR}/src/lmem.c 
+    ${LUA_SOURCES_DIR}/src/lobject.c 
+    ${LUA_SOURCES_DIR}/src/lopcodes.c 
+    ${LUA_SOURCES_DIR}/src/lparser.c
+    ${LUA_SOURCES_DIR}/src/lstate.c 
+    ${LUA_SOURCES_DIR}/src/lstring.c
+    ${LUA_SOURCES_DIR}/src/ltable.c
+    ${LUA_SOURCES_DIR}/src/ltm.c
+    ${LUA_SOURCES_DIR}/src/lundump.c 
+    ${LUA_SOURCES_DIR}/src/lvm.c 
+    ${LUA_SOURCES_DIR}/src/lzio.c
+
+    # Base Lua modules
+    ${LUA_SOURCES_DIR}/src/lauxlib.c
+    ${LUA_SOURCES_DIR}/src/lbaselib.c
+    ${LUA_SOURCES_DIR}/src/ldblib.c
+    ${LUA_SOURCES_DIR}/src/liolib.c
+    ${LUA_SOURCES_DIR}/src/lmathlib.c
+    ${LUA_SOURCES_DIR}/src/loslib.c
+    ${LUA_SOURCES_DIR}/src/ltablib.c
+    ${LUA_SOURCES_DIR}/src/lstrlib.c
+    ${LUA_SOURCES_DIR}/src/loadlib.c
+    ${LUA_SOURCES_DIR}/src/linit.c
+    )
+
+  add_library(Lua STATIC ${LUA_SOURCES})
+  link_libraries(Lua)
+
+  source_group(ThirdParty\\Lua REGULAR_EXPRESSION ${LUA_SOURCES_DIR}/.*)
+
+else()
+  CHECK_INCLUDE_FILE_CXX(lua.h HAVE_LUA_H)
+  if (NOT HAVE_LUA_H)
+    message(FATAL_ERROR "Please install the liblua-dev package")
+  endif()
+
+  CHECK_LIBRARY_EXISTS(lua "lua_pcall" HAVE_LUA_LIB)
+  if (NOT HAVE_LUA_LIB)
+    message(FATAL_ERROR "Please install the liblua-dev package")
+  endif()
+
+  link_libraries(lua)
+endif()
--- a/Resources/Configuration.json	Tue Apr 30 15:26:34 2013 +0200
+++ b/Resources/Configuration.json	Thu May 02 16:49:28 2013 +0200
@@ -27,6 +27,12 @@
     // in the storage (a value of "0" indicates no limit on the number
     // of patients)
     "MaximumPatientCount" : 0,
+  
+    // List of paths to the custom Lua scripts to load into this
+    // instance of Orthanc
+    "LuaScripts" : [
+    ],
+
 
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Toolbox.lua	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,19 @@
+--[[ PrintRecursive(struct, [limit], [indent])   Recursively print arbitrary data. 
+Set limit (default 100) to stanch infinite loops.
+Indents tables as [KEY] VALUE, nested tables as [KEY] [KEY]...[KEY] VALUE
+Set indent ("") to prefix each line:    Mytable [KEY] [KEY]...[KEY] VALUE
+Source: https://gist.github.com/stuby/5445834#file-rprint-lua
+--]]
+
+function PrintRecursive(s, l, i) -- recursive Print (structure, limit, indent)
+   l = (l) or 100; i = i or "";	-- default item limit, indent string
+   if (l<1) then print "ERROR: Item limit reached."; return l-1 end;
+   local ts = type(s);
+   if (ts ~= "table") then print (i,ts,s); return l-1 end
+   print (i,ts);           -- print "table"
+   for k,v in pairs(s) do  -- print "[KEY] VALUE"
+      l = PrintRecursive(v, l, i.."\t["..tostring(k).."]");
+      if (l < 0) then break end
+   end
+   return l
+end	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/Lua.cpp	Thu May 02 16:49:28 2013 +0200
@@ -0,0 +1,72 @@
+#include "gtest/gtest.h"
+
+#include "../Core/Lua/LuaFunctionCall.h"
+
+
+TEST(Lua, Simple)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+  lua.Execute("a={}");
+  lua.Execute("a['x'] = 10");
+  lua.Execute("a['y'] = {}");
+  lua.Execute("a['y'][1] = 20");
+  lua.Execute("a['y'][2] = 20");
+  lua.Execute("PrintRecursive(a)");
+
+  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
+
+  Json::Value v, vv, o;
+  //v["a"] = "b";
+  v.append("hello");
+  v.append("world");
+  v.append("42");
+  vv.append("sub");
+  vv.append("set");
+  v.append(vv);
+  o = Json::objectValue;
+  o["x"] = 10;
+  o["y"] = 20;
+  o["z"] = 20.5f;
+  v.append(o);
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushJSON(v);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException);
+  }
+
+  o["bool"] = false;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_FALSE(f.ExecutePredicate());
+  }
+
+  o["bool"] = true;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJSON(o);
+    ASSERT_TRUE(f.ExecutePredicate());
+  }
+}
+
+
+TEST(Lua, Existing)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute("a={}");
+  lua.Execute("function f() end");
+
+  ASSERT_TRUE(lua.IsExistingFunction("f"));
+  ASSERT_FALSE(lua.IsExistingFunction("a"));
+  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
+}
--- a/UnitTests/Versions.cpp	Tue Apr 30 15:26:34 2013 +0200
+++ b/UnitTests/Versions.cpp	Thu May 02 16:49:28 2013 +0200
@@ -8,6 +8,7 @@
 #include <curl/curl.h>
 #include <boost/version.hpp>
 #include <sqlite3.h>
+#include <lua.h>
 
 
 TEST(Versions, Zlib)
@@ -40,6 +41,14 @@
 }
 
 
+TEST(Versions, Lua)
+{
+  // Ensure that the Lua version is above 5.1.0. This version has
+  // introduced some API changes.
+  ASSERT_GE(LUA_VERSION_NUM, 501);
+}
+
+
 #if ORTHANC_STATIC == 1
 TEST(Versions, ZlibStatic)
 {
@@ -63,7 +72,7 @@
   ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING);
 }
 
-TEST(Versions, CurlSsl)
+TEST(Versions, CurlSslStatic)
 {
   curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW);
 
@@ -76,4 +85,10 @@
   ASSERT_TRUE(curlSupportsSsl);
 #endif
 }
+
+TEST(Version, LuaStatic)
+{
+  ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE);
+}
 #endif
+