changeset 2616:2f3007bf0708 jobs

event queues in Lua, serialization of sequence of operations
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 22 May 2018 12:25:37 +0200
parents 3200223f9ade
children 912a767911b0
files Core/DicomParsing/DicomModification.cpp Core/DicomParsing/DicomModification.h Core/HttpServer/IIncomingHttpRequestFilter.h Core/HttpServer/MongooseServer.cpp Core/HttpServer/MongooseServer.h Core/JobsEngine/Operations/IJobOperation.h Core/JobsEngine/Operations/JobOperationValue.h Core/JobsEngine/Operations/JobOperationValues.cpp Core/JobsEngine/Operations/JobOperationValues.h Core/JobsEngine/Operations/LogJobOperation.h Core/JobsEngine/Operations/NullOperationValue.h Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Core/JobsEngine/Operations/SequenceOfOperationsJob.h Core/JobsEngine/Operations/StringOperationValue.h OrthancServer/LuaScripting.cpp OrthancServer/LuaScripting.h OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancFindRequestHandler.h OrthancServer/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/QueryRetrieveHandler.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerJobs/DeleteResourceOperation.h OrthancServer/ServerJobs/DicomInstanceOperationValue.h OrthancServer/ServerJobs/LuaJobManager.cpp OrthancServer/ServerJobs/ModifyInstanceOperation.cpp OrthancServer/ServerJobs/ModifyInstanceOperation.h OrthancServer/ServerJobs/StorePeerOperation.cpp OrthancServer/ServerJobs/StorePeerOperation.h OrthancServer/ServerJobs/StoreScuOperation.cpp OrthancServer/ServerJobs/StoreScuOperation.h OrthancServer/ServerJobs/SystemCallOperation.cpp OrthancServer/ServerJobs/SystemCallOperation.h OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h
diffstat 36 files changed, 557 insertions(+), 204 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomParsing/DicomModification.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/Core/DicomParsing/DicomModification.cpp	Tue May 22 12:25:37 2018 +0200
@@ -1237,4 +1237,11 @@
     patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) &&
                            GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName);
   }
+
+  
+  void DicomModification::Serialize(Json::Value& value) const
+  {
+    // TODO
+    value = "TODO";
+  }
 }
--- a/Core/DicomParsing/DicomModification.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/DicomParsing/DicomModification.h	Tue May 22 12:25:37 2018 +0200
@@ -172,5 +172,7 @@
     {
       identifierGenerator_ = &generator;
     }
+
+    void Serialize(Json::Value& value) const;
   };
 }
--- a/Core/HttpServer/IIncomingHttpRequestFilter.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/HttpServer/IIncomingHttpRequestFilter.h	Tue May 22 12:25:37 2018 +0200
@@ -49,6 +49,6 @@
                            const char* ip,
                            const char* username,
                            const IHttpHandler::Arguments& httpHeaders,
-                           const IHttpHandler::GetArguments& getArguments) const = 0;
+                           const IHttpHandler::GetArguments& getArguments) = 0;
   };
 }
--- a/Core/HttpServer/MongooseServer.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Tue May 22 12:25:37 2018 +0200
@@ -676,7 +676,7 @@
 
     std::string username = GetAuthenticatedUsername(headers);
 
-    const IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter();
+    IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter();
     if (filter != NULL)
     {
       if (!filter->IsAllowed(method, request->uri, remoteIp,
--- a/Core/HttpServer/MongooseServer.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/HttpServer/MongooseServer.h	Tue May 22 12:25:37 2018 +0200
@@ -162,7 +162,7 @@
 
     void SetHttpCompressionEnabled(bool enabled);
 
-    const IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const
+    IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const
     {
       return filter_;
     }
--- a/Core/JobsEngine/Operations/IJobOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/IJobOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -48,5 +48,7 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& dicomConnection) = 0;
+
+    virtual void Serialize(Json::Value& result) const = 0;
   };
 }
--- a/Core/JobsEngine/Operations/JobOperationValue.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/JobOperationValue.h	Tue May 22 12:25:37 2018 +0200
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include <json/value.h>
 #include <boost/noncopyable.hpp>
 
 namespace Orthanc
@@ -67,5 +68,7 @@
     }
 
     virtual JobOperationValue* Clone() const = 0;
+
+    virtual void Serialize(Json::Value& target) const = 0;
   };
 }
--- a/Core/JobsEngine/Operations/JobOperationValues.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/JobOperationValues.cpp	Tue May 22 12:25:37 2018 +0200
@@ -104,4 +104,17 @@
       return *values_[index];
     }
   }
+
+
+  void JobOperationValues::Serialize(Json::Value& target) const
+  {
+    target = Json::arrayValue;
+
+    for (size_t i = 0; i < values_.size(); i++)
+    {
+      Json::Value tmp;
+      values_[i]->Serialize(tmp);
+      target.append(tmp);
+    }
+  }
 }
--- a/Core/JobsEngine/Operations/JobOperationValues.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/JobOperationValues.h	Tue May 22 12:25:37 2018 +0200
@@ -78,5 +78,7 @@
     }
 
     JobOperationValue& GetValue(size_t index) const;
+
+    void Serialize(Json::Value& target) const;
   };
 }
--- a/Core/JobsEngine/Operations/LogJobOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/LogJobOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -43,5 +43,10 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
+
+    virtual void Serialize(Json::Value& result) const
+    {
+      result["Type"] = "Log";
+    }
   };
 }
--- a/Core/JobsEngine/Operations/NullOperationValue.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/NullOperationValue.h	Tue May 22 12:25:37 2018 +0200
@@ -49,5 +49,10 @@
     {
       return new NullOperationValue;
     }
+
+    virtual void Serialize(Json::Value& target) const
+    {
+      target["Type"] = "Null";
+    }
   };
 }
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Tue May 22 12:25:37 2018 +0200
@@ -42,6 +42,7 @@
   class SequenceOfOperationsJob::Operation : public boost::noncopyable
   {
   private:
+    size_t                        index_;
     JobOperationValues            originalInputs_;
     JobOperationValues            workInputs_;
     std::auto_ptr<IJobOperation>  operation_;
@@ -49,9 +50,11 @@
     size_t                        currentInput_;
 
   public:
-    Operation(IJobOperation* operation) :
-    operation_(operation),
-    currentInput_(0)
+    Operation(size_t index,
+              IJobOperation* operation) :
+      index_(index),
+      operation_(operation),
+      currentInput_(0)
     {
       if (operation == NULL)
       {
@@ -85,6 +88,11 @@
 
     void AddNextOperation(Operation& other)
     {
+      if (other.index_ <= index_)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
       if (currentInput_ != 0)
       {
         // Cannot add input after processing has started
@@ -139,15 +147,31 @@
 
       currentInput_ += 1;
     }
+
+    void Serialize(Json::Value& target) const
+    {
+      target = Json::objectValue;
+      operation_->Serialize(target["Operation"]);
+      originalInputs_.Serialize(target["OriginalInputs"]);
+      workInputs_.Serialize(target["WorkInputs"]);      
+
+      Json::Value tmp = Json::arrayValue;
+      for (std::list<Operation*>::const_iterator it = nextOperations_.begin();
+           it != nextOperations_.end(); ++it)
+      {
+        tmp.append(static_cast<int>((*it)->index_));
+      }
+
+      target["NextOperations"] = tmp;
+    }
   };
 
 
-  // Invoked from constructors
-  void SequenceOfOperationsJob::Setup()
+  SequenceOfOperationsJob::SequenceOfOperationsJob() :
+    done_(false),
+    current_(0),
+    trailingTimeout_(boost::posix_time::milliseconds(1000))
   {
-    done_ = false;
-    current_ = 0;
-    trailingTimeout_ = boost::posix_time::milliseconds(1000); 
   }
 
 
@@ -163,6 +187,13 @@
   }
 
 
+  void SequenceOfOperationsJob::SetDescription(const std::string& description)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    description_ = description;
+  }
+
+
   void SequenceOfOperationsJob::Register(IObserver& observer)
   {
     boost::mutex::scoped_lock lock(mutex_);
@@ -189,10 +220,12 @@
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
 
-    that_.operations_.push_back(new Operation(operation));
+    size_t index = that_.operations_.size();
+
+    that_.operations_.push_back(new Operation(index, operation));
     that_.operationAdded_.notify_one();
 
-    return that_.operations_.size() - 1;
+    return index;
   }
 
 
@@ -324,5 +357,20 @@
     boost::mutex::scoped_lock lock(mutex_);
 
     value["CountOperations"] = static_cast<unsigned int>(operations_.size());
+    value["Description"] = description_;
+  }
+
+
+  void SequenceOfOperationsJob::GetInternalContent(Json::Value& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    value = Json::arrayValue;
+    for (size_t i = 0; i < operations_.size(); i++)
+    {
+      Json::Value operation = Json::objectValue;
+      operations_[i]->Serialize(operation);
+      value.append(operation);
+    }
   }
 }
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Tue May 22 12:25:37 2018 +0200
@@ -61,7 +61,7 @@
   private:
     class Operation;
 
-    std::string                       jobType_;
+    std::string                       description_;
     bool                              done_;
     boost::mutex                      mutex_;
     std::vector<Operation*>           operations_;
@@ -71,23 +71,13 @@
     std::list<IObserver*>             observers_;
     TimeoutDicomConnectionManager     connectionManager_;
 
-    void Setup();
-
   public:
-    SequenceOfOperationsJob() :
-      jobType_("SequenceOfOperations")
-    {
-      Setup();
-    }    
-
-    SequenceOfOperationsJob(const std::string& jobType) :
-      jobType_(jobType)
-    {
-      Setup();
-    }    
+    SequenceOfOperationsJob();
 
     virtual ~SequenceOfOperationsJob();
 
+    void SetDescription(const std::string& description);
+
     void Register(IObserver& observer);
 
     // This lock allows adding new operations to the end of the job,
@@ -143,14 +133,11 @@
 
     virtual void GetJobType(std::string& target)
     {
-      target = jobType_;
+      target = "SequenceOfOperations";
     }
 
     virtual void GetPublicContent(Json::Value& value);
 
-    virtual void GetInternalContent(Json::Value& value)
-    {
-      // TODO
-    }
+    virtual void GetInternalContent(Json::Value& value);
   };
 }
--- a/Core/JobsEngine/Operations/StringOperationValue.h	Mon May 21 09:00:20 2018 +0200
+++ b/Core/JobsEngine/Operations/StringOperationValue.h	Tue May 22 12:25:37 2018 +0200
@@ -60,5 +60,11 @@
     {
       return content_;
     }
+
+    virtual void Serialize(Json::Value& target) const
+    {
+      target["Type"] = "String";
+      target["Content"] = content_;
+    }
   };
 }
--- a/OrthancServer/LuaScripting.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/LuaScripting.cpp	Tue May 22 12:25:37 2018 +0200
@@ -47,6 +47,136 @@
 
 namespace Orthanc
 {
+  class LuaScripting::IEvent : public IDynamicObject
+  {
+  public:
+    virtual void Apply(LuaScripting& lock) = 0;
+  };
+
+
+  class LuaScripting::OnStoredInstanceEvent : public LuaScripting::IEvent
+  {
+  private:
+    std::string    instanceId_;
+    Json::Value    simplifiedTags_;
+    Json::Value    metadata_;
+    Json::Value    origin_;
+
+  public:
+    OnStoredInstanceEvent(const std::string& instanceId,
+                          const Json::Value& simplifiedTags,
+                          const Json::Value& metadata,
+                          const DicomInstanceToStore& instance) :
+      instanceId_(instanceId),
+      simplifiedTags_(simplifiedTags),
+      metadata_(metadata)
+    {
+      instance.GetOriginInformation(origin_);
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      static const char* NAME = "OnStoredInstance";
+
+      LuaScripting::Lock lock(that);
+
+      if (lock.GetLua().IsExistingFunction(NAME))
+      {
+        that.InitializeJob();
+
+        LuaFunctionCall call(lock.GetLua(), NAME);
+        call.PushString(instanceId_);
+        call.PushJson(simplifiedTags_);
+        call.PushJson(metadata_);
+        call.PushJson(origin_);
+        call.Execute();
+
+        that.SubmitJob();
+      }
+    }
+  };
+
+
+  class LuaScripting::ExecuteEvent : public LuaScripting::IEvent
+  {
+  private:
+    std::string    command_;
+
+  public:
+    ExecuteEvent(const std::string& command) :
+      command_(command)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      LuaScripting::Lock lock(that);
+
+      if (lock.GetLua().IsExistingFunction(command_.c_str()))
+      {
+        LuaFunctionCall call(lock.GetLua(), command_.c_str());
+        call.Execute();
+      }
+    }
+  };
+
+
+  class LuaScripting::StableResourceEvent : public LuaScripting::IEvent
+  {
+  private:
+    ServerIndexChange  change_;
+
+  public:
+    StableResourceEvent(const ServerIndexChange& change) :
+    change_(change)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      const char* name;
+
+      switch (change_.GetChangeType())
+      {
+        case ChangeType_StablePatient:
+          name = "OnStablePatient";
+          break;
+
+        case ChangeType_StableStudy:
+          name = "OnStableStudy";
+          break;
+
+        case ChangeType_StableSeries:
+          name = "OnStableSeries";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      Json::Value tags, metadata;
+      if (that.context_.GetIndex().LookupResource(tags, change_.GetPublicId(), change_.GetResourceType()) &&
+          that.context_.GetIndex().GetMetadata(metadata, change_.GetPublicId()))
+      {
+        LuaScripting::Lock lock(that);
+
+        if (lock.GetLua().IsExistingFunction(name))
+        {
+          that.InitializeJob();
+
+          LuaFunctionCall call(lock.GetLua(), name);
+          call.PushString(change_.GetPublicId());
+          call.PushJson(tags["MainDicomTags"]);
+          call.PushJson(metadata);
+          call.Execute();
+
+          that.SubmitJob();
+        }
+      }
+    }
+  };
+
+
   ServerContext* LuaScripting::GetServerContext(lua_State *state)
   {
     const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext");
@@ -206,7 +336,7 @@
     }
 
     LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri;
-      lua_pushnil(state);
+    lua_pushnil(state);
 
     return 1;
   }
@@ -325,7 +455,7 @@
   }
 
 
-  void LuaScripting::SubmitJob(const std::string& description)
+  void LuaScripting::SubmitJob()
   {
     Json::Value operations;
     LuaFunctionCall call2(lua_, "_AccessJob");
@@ -374,7 +504,9 @@
   }
 
 
-  LuaScripting::LuaScripting(ServerContext& context) : context_(context)
+  LuaScripting::LuaScripting(ServerContext& context) : 
+    context_(context),
+    continue_(true)
   {
     lua_.SetGlobalVariable("_ServerContext", &context);
     lua_.RegisterFunction("RestApiGet", RestApiGet);
@@ -382,34 +514,85 @@
     lua_.RegisterFunction("RestApiPut", RestApiPut);
     lua_.RegisterFunction("RestApiDelete", RestApiDelete);
     lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration);
+  }
 
-    lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+
+  LuaScripting::~LuaScripting()
+  {
+    if (continue_)
+    {
+      LOG(ERROR) << "INTERNAL ERROR: LuaScripting::Stop() should be invoked manually to avoid mess in the destruction order!";
+      Stop();
+    }
   }
 
 
-  void LuaScripting::ApplyOnStoredInstance(const std::string& instanceId,
-                                           const Json::Value& simplifiedTags,
-                                           const Json::Value& metadata,
-                                           const DicomInstanceToStore& instance)
+  void LuaScripting::EventThread(LuaScripting* that)
   {
-    static const char* NAME = "OnStoredInstance";
+    for (;;)
+    {
+      std::auto_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100));
+
+      if (event.get() == NULL)
+      {
+        // The event queue is empty, check whether we should stop
+        boost::mutex::scoped_lock lock(that->mutex_);
 
-    if (lua_.IsExistingFunction(NAME))
-    {
-      InitializeJob();
+        if (!that->continue_)
+        {
+          return;
+        }
+      }
+      else
+      {
+        try
+        {
+          dynamic_cast<IEvent&>(*event).Apply(*that);
+        }
+        catch (OrthancException& e)
+        {
+          LOG(ERROR) << "Error while processing Lua events: " << e.What();
+        }
+      }
+    }
+  }
+
 
-      LuaFunctionCall call(lua_, NAME);
-      call.PushString(instanceId);
-      call.PushJson(simplifiedTags);
-      call.PushJson(metadata);
+  void LuaScripting::Start()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (!continue_ ||
+        eventThread_.joinable()  /* already started */)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      LOG(INFO) << "Starting the Lua engine";
+      eventThread_ = boost::thread(EventThread, this);
+    }
+  }
+
 
-      Json::Value origin;
-      instance.GetOriginInformation(origin);
-      call.PushJson(origin);
+  void LuaScripting::Stop()
+  {
+    {
+      boost::mutex::scoped_lock lock(mutex_);
 
-      call.Execute();
+      if (!continue_)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
 
-      SubmitJob(std::string("Lua script: ") + NAME);
+      continue_ = false;
+    }
+
+    if (eventThread_.joinable())
+    {
+      LOG(INFO) << "Stopping the Lua engine";
+      eventThread_.join();
+      LOG(INFO) << "The Lua engine has stopped";
     }
   }
 
@@ -418,8 +601,6 @@
                                           DicomInstanceToStore& instance,
                                           const Json::Value& simplifiedTags)
   {
-    boost::recursive_mutex::scoped_lock lock(mutex_);
-
     Json::Value metadata = Json::objectValue;
 
     for (ServerIndex::MetadataMap::const_iterator 
@@ -432,64 +613,17 @@
       }
     }
 
-    ApplyOnStoredInstance(publicId, simplifiedTags, metadata, instance);
+    pendingEvents_.Enqueue(new OnStoredInstanceEvent(publicId, simplifiedTags, metadata, instance));
   }
 
 
-  
-  void LuaScripting::OnStableResource(const ServerIndexChange& change)
-  {
-    const char* name;
-
-    switch (change.GetChangeType())
-    {
-      case ChangeType_StablePatient:
-        name = "OnStablePatient";
-        break;
-
-      case ChangeType_StableStudy:
-        name = "OnStableStudy";
-        break;
-
-      case ChangeType_StableSeries:
-        name = "OnStableSeries";
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-
-    Json::Value tags, metadata;
-    if (context_.GetIndex().LookupResource(tags, change.GetPublicId(), change.GetResourceType()) &&
-        context_.GetIndex().GetMetadata(metadata, change.GetPublicId()))
-    {
-      boost::recursive_mutex::scoped_lock lock(mutex_);
-
-      if (lua_.IsExistingFunction(name))
-      {
-        InitializeJob();
-
-        LuaFunctionCall call(lua_, name);
-        call.PushString(change.GetPublicId());
-        call.PushJson(tags["MainDicomTags"]);
-        call.PushJson(metadata);
-        call.Execute();
-
-        SubmitJob(std::string("Lua script: ") + name);
-      }
-    }
-  }
-
-
-
   void LuaScripting::SignalChange(const ServerIndexChange& change)
   {
     if (change.GetChangeType() == ChangeType_StablePatient ||
         change.GetChangeType() == ChangeType_StableStudy ||
         change.GetChangeType() == ChangeType_StableSeries)
     {
-      OnStableResource(change);
+      pendingEvents_.Enqueue(new StableResourceEvent(change));
     }
   }
 
@@ -499,7 +633,7 @@
   {
     static const char* NAME = "ReceivedInstanceFilter";
 
-    boost::recursive_mutex::scoped_lock lock(mutex_);
+    boost::mutex::scoped_lock lock(mutex_);
 
     if (lua_.IsExistingFunction(NAME))
     {
@@ -522,12 +656,28 @@
 
   void LuaScripting::Execute(const std::string& command)
   {
-    LuaScripting::Locker locker(*this);
-      
-    if (locker.GetLua().IsExistingFunction(command.c_str()))
+    pendingEvents_.Enqueue(new ExecuteEvent(command));
+  }
+
+
+  void LuaScripting::LoadGlobalConfiguration()
+  {
+    lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+
+    std::list<std::string> luaScripts;
+    Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
+
+    LuaScripting::Lock lock(*this);
+
+    for (std::list<std::string>::const_iterator
+           it = luaScripts.begin(); it != luaScripts.end(); ++it)
     {
-      LuaFunctionCall call(locker.GetLua(), command.c_str());
-      call.Execute();
+      std::string path = Configuration::InterpretStringParameterAsPath(*it);
+      LOG(INFO) << "Installing the Lua scripts from: " << path;
+      std::string script;
+      SystemToolbox::ReadFile(script, path);
+
+      lock.GetLua().Execute(script);
     }
   }
 }
--- a/OrthancServer/LuaScripting.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/LuaScripting.h	Tue May 22 12:25:37 2018 +0200
@@ -37,6 +37,7 @@
 
 #include "ServerJobs/LuaJobManager.h"
 
+#include "../Core/MultiThreading/SharedMessageQueue.h"
 #include "../Core/Lua/LuaContext.h"
 
 namespace Orthanc
@@ -46,6 +47,11 @@
   class LuaScripting : public IServerListener
   {
   private:
+    class ExecuteEvent;
+    class IEvent;
+    class OnStoredInstanceEvent;
+    class StableResourceEvent;
+
     static ServerContext* GetServerContext(lua_State *state);
 
     static int RestApiPostOrPut(lua_State *state,
@@ -56,35 +62,33 @@
     static int RestApiDelete(lua_State *state);
     static int GetOrthancConfiguration(lua_State *state);
 
-    void ApplyOnStoredInstance(const std::string& instanceId,
-                               const Json::Value& simplifiedDicom,
-                               const Json::Value& metadata,
-                               const DicomInstanceToStore& instance);
-
     size_t ParseOperation(LuaJobManager::Lock& lock,
                           const std::string& operation,
                           const Json::Value& parameters);
 
     void InitializeJob();
 
-    void SubmitJob(const std::string& description);
-
-    void OnStableResource(const ServerIndexChange& change);
+    void SubmitJob();
 
-    boost::recursive_mutex   mutex_;
-    LuaContext               lua_;
-    ServerContext&           context_;
-    LuaJobManager            jobManager_;
+    boost::mutex        mutex_;
+    LuaContext          lua_;
+    ServerContext&      context_;
+    LuaJobManager       jobManager_;
+    bool                continue_;
+    boost::thread       eventThread_;
+    SharedMessageQueue  pendingEvents_;
+
+    static void EventThread(LuaScripting* that);
 
   public:
-    class Locker : public boost::noncopyable
+    class Lock : public boost::noncopyable
     {
     private:
-      LuaScripting& that_;
-      boost::recursive_mutex::scoped_lock lock_;
+      LuaScripting&              that_;
+      boost::mutex::scoped_lock  lock_;
 
     public:
-      Locker(LuaScripting& that) : 
+      Lock(LuaScripting& that) : 
         that_(that), 
         lock_(that.mutex_)
       {
@@ -97,6 +101,12 @@
     };
 
     LuaScripting(ServerContext& context);
+
+    ~LuaScripting();
+
+    void Start();
+
+    void Stop();
     
     virtual void SignalStoredInstance(const std::string& publicId,
                                       DicomInstanceToStore& instance,
@@ -108,5 +118,7 @@
                                         const Json::Value& simplifiedTags);
 
     void Execute(const std::string& command);
+
+    void LoadGlobalConfiguration();
   };
 }
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Tue May 22 12:25:37 2018 +0200
@@ -47,6 +47,25 @@
 
 namespace Orthanc
 {
+  static LuaScripting& GetLuaScripting(ServerContext& context)
+  {
+    // Returns a singleton Lua context
+    static boost::mutex mutex_;
+    static std::auto_ptr<LuaScripting>  lua_;
+    
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (lua_.get() == NULL)
+    {
+      LOG(INFO) << "Initializing Lua for OrthancFindRequestHandler";
+      lua_.reset(new LuaScripting(context));
+      lua_->LoadGlobalConfiguration();
+    }
+
+    return *lua_;
+  }
+
+
   static void GetChildren(std::list<std::string>& target,
                           ServerIndex& index,
                           const std::list<std::string>& source)
@@ -485,9 +504,9 @@
                                                  const std::string& calledAet)
   {
     static const char* LUA_CALLBACK = "IncomingFindRequestFilter";
-
-    LuaScripting::Locker locker(context_.GetLuaScripting());
-    if (!locker.GetLua().IsExistingFunction(LUA_CALLBACK))
+    
+    LuaScripting::Lock lock(GetLuaScripting(context_));
+    if (!lock.GetLua().IsExistingFunction(LUA_CALLBACK))
     {
       return false;
     }
@@ -498,7 +517,7 @@
       origin["RemoteAet"] = remoteAet;
       origin["CalledAet"] = calledAet;
 
-      LuaFunctionCall call(locker.GetLua(), LUA_CALLBACK);
+      LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK);
       call.PushDicom(source);
       call.PushJson(origin);
       FromDcmtkBridge::ExecuteToDicom(target, call);
@@ -508,6 +527,14 @@
   }
 
 
+  OrthancFindRequestHandler::OrthancFindRequestHandler(ServerContext& context) :
+    context_(context),
+    maxResults_(0),
+    maxInstances_(0)
+  {
+  }
+
+
   void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::list<DicomTag>& sequencesToReturn,
--- a/OrthancServer/OrthancFindRequestHandler.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/OrthancFindRequestHandler.h	Tue May 22 12:25:37 2018 +0200
@@ -42,8 +42,8 @@
   {
   private:
     ServerContext& context_;
-    unsigned int maxResults_;
-    unsigned int maxInstances_;
+    unsigned int   maxResults_;
+    unsigned int   maxInstances_;
 
     bool HasReachedLimit(const DicomFindAnswers& answers,
                          ResourceType level) const;
@@ -60,12 +60,7 @@
                         const std::string& calledAet);
 
   public:
-    OrthancFindRequestHandler(ServerContext& context) :
-      context_(context), 
-      maxResults_(0),
-      maxInstances_(0)
-    {
-    }
+    OrthancFindRequestHandler(ServerContext& context);
 
     virtual void Handle(DicomFindAnswers& answers,
                         const DicomMap& input,
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Tue May 22 12:25:37 2018 +0200
@@ -124,8 +124,8 @@
     call.BodyToString(command);
 
     {
-      LuaScripting::Locker locker(context.GetLuaScripting());
-      locker.GetLua().Execute(result, command);
+      LuaScripting::Lock lock(context.GetLuaEventHandler());
+      lock.GetLua().Execute(result, command);
     }
 
     call.GetOutput().AnswerBuffer(result, "text/plain");
@@ -276,6 +276,7 @@
   static void ListJobs(RestApiGetCall& call)
   {
     bool expand = call.HasArgument("expand");
+    bool internal = call.HasArgument("internal");
 
     Json::Value v = Json::arrayValue;
 
@@ -291,7 +292,7 @@
         if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, *it))
         {
           Json::Value tmp;
-          info.Serialize(tmp, false);
+          info.Serialize(tmp, internal);
           v.append(tmp);
         }
       }
@@ -307,12 +308,13 @@
   static void GetJobInfo(RestApiGetCall& call)
   {
     std::string id = call.GetUriComponent("id", "");
+    bool internal = call.HasArgument("internal");
 
     JobInfo info;
     if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, id))
     {
       Json::Value json;
-      info.Serialize(json, false);
+      info.Serialize(json, internal);
       call.GetOutput().AnswerJson(json);
     }
   }
--- a/OrthancServer/QueryRetrieveHandler.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Tue May 22 12:25:37 2018 +0200
@@ -35,21 +35,43 @@
 #include "QueryRetrieveHandler.h"
 
 #include "OrthancInitialization.h"
+
 #include "../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../Core/Logging.h"
 
 
 namespace Orthanc
 {
-  static void FixQuery(DicomMap& query,
-                       ServerContext& context,
-                       const std::string& modality)
+  static LuaScripting& GetLuaScripting(ServerContext& context)
+  {
+    // Returns a singleton Lua context
+    static boost::mutex mutex_;
+    static std::auto_ptr<LuaScripting>  lua_;
+    
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (lua_.get() == NULL)
+    {
+      LOG(INFO) << "Initializing Lua for QueryRetrieveHandler";
+      lua_.reset(new LuaScripting(context));
+      lua_->LoadGlobalConfiguration();
+    }
+
+    return *lua_;
+  }
+
+
+  static void FixQueryLua(DicomMap& query,
+                          ServerContext& context,
+                          const std::string& modality)
   {
     static const char* LUA_CALLBACK = "OutgoingFindRequestFilter";
 
-    LuaScripting::Locker locker(context.GetLuaScripting());
-    if (locker.GetLua().IsExistingFunction(LUA_CALLBACK))
+    LuaScripting::Lock lock(GetLuaScripting(context));
+
+    if (lock.GetLua().IsExistingFunction(LUA_CALLBACK))
     {
-      LuaFunctionCall call(locker.GetLua(), LUA_CALLBACK);
+      LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK);
       call.PushDicom(query);
       call.PushJson(modality);
       FromDcmtkBridge::ExecuteToDicom(query, call);
@@ -57,8 +79,8 @@
   }
 
 
-  static void FixQuery(DicomMap& query,
-                       ModalityManufacturer manufacturer)
+  static void FixQueryBuiltin(DicomMap& query,
+                              ModalityManufacturer manufacturer)
   {
     /**
      * Introduce patches for specific manufacturers below.
@@ -99,10 +121,10 @@
       // Firstly, fix the content of the query for specific manufacturers
       DicomMap fixed;
       fixed.Assign(query_);
-      FixQuery(fixed, modality_.GetManufacturer());
+      FixQueryBuiltin(fixed, modality_.GetManufacturer());
 
       // Secondly, possibly fix the query with the user-provider Lua callback
-      FixQuery(fixed, context_, modality_.GetApplicationEntityTitle()); 
+      FixQueryLua(fixed, context_, modality_.GetApplicationEntityTitle()); 
 
       GetConnection().Find(answers_, level_, fixed);
 
--- a/OrthancServer/ServerContext.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue May 22 12:25:37 2018 +0200
@@ -114,7 +114,7 @@
     storeMD5_(true),
     provider_(*this),
     dicomCache_(provider_, DICOM_CACHE_SIZE),
-    lua_(*this),
+    luaEventHandler_(*this),
 #if ORTHANC_ENABLE_PLUGINS == 1
     plugins_(NULL),
 #endif
@@ -122,7 +122,7 @@
     queryRetrieveArchive_(Configuration::GetGlobalUnsignedIntegerParameter("QueryRetrieveSize", 10)),
     defaultLocalAet_(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"))
   {
-    listeners_.push_back(ServerListener(lua_, "Lua"));
+    listeners_.push_back(ServerListener(luaEventHandler_, "Lua"));
 
     jobsEngine_.SetWorkersCount(Configuration::GetGlobalUnsignedIntegerParameter("ConcurrentJobs", 2));
     //jobsEngine_.SetMaxCompleted   // TODO
@@ -580,7 +580,7 @@
 
     // TODO REFACTOR THIS
     listeners_.clear();
-    listeners_.push_back(ServerListener(lua_, "Lua"));
+    listeners_.push_back(ServerListener(luaEventHandler_, "Lua"));
     listeners_.push_back(ServerListener(plugins, "plugin"));
   }
 
@@ -593,7 +593,7 @@
 
     // TODO REFACTOR THIS
     listeners_.clear();
-    listeners_.push_back(ServerListener(lua_, "Lua"));
+    listeners_.push_back(ServerListener(luaEventHandler_, "Lua"));
   }
 
 
--- a/OrthancServer/ServerContext.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerContext.h	Tue May 22 12:25:37 2018 +0200
@@ -119,7 +119,7 @@
     MemoryCache dicomCache_;
     JobsEngine jobsEngine_;
 
-    LuaScripting lua_;
+    LuaScripting luaEventHandler_;
 
 #if ORTHANC_ENABLE_PLUGINS == 1
     OrthancPlugins* plugins_;
@@ -257,9 +257,9 @@
       return defaultLocalAet_;
     }
 
-    LuaScripting& GetLuaScripting()
+    LuaScripting& GetLuaEventHandler()
     {
-      return lua_;
+      return luaEventHandler_;
     }
 
     OrthancHttpHandler& GetHttpHandler()
--- a/OrthancServer/ServerJobs/DeleteResourceOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/DeleteResourceOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -53,6 +53,11 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
+
+    virtual void Serialize(Json::Value& result) const
+    {
+      result["Type"] = "DeleteResource";
+    }
   };
 }
 
--- a/OrthancServer/ServerJobs/DicomInstanceOperationValue.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/DicomInstanceOperationValue.h	Tue May 22 12:25:37 2018 +0200
@@ -54,6 +54,11 @@
     {
     }
 
+    ServerContext& GetServerContext() const
+    {
+      return context_;
+    }
+
     const std::string& GetId() const
     {
       return id_;
@@ -68,5 +73,11 @@
     {
       return new DicomInstanceOperationValue(context_, id_);
     }
+
+    virtual void Serialize(Json::Value& target) const
+    {
+      target["Type"] = "DicomInstance";
+      target["ID"] = id_;
+    }
   };
 }
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Tue May 22 12:25:37 2018 +0200
@@ -119,9 +119,13 @@
     {
       // Need to create a new job, as the previous one is either
       // finished, or is getting too long
-      that_.currentJob_ = new SequenceOfOperationsJob("Lua");
-      jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_));
-      jobLock_->SetTrailingOperationTimeout(that_.trailingTimeout_);
+      that_.currentJob_ = new SequenceOfOperationsJob;
+      that_.currentJob_->SetDescription("Lua");
+
+      {
+        jobLock_.reset(new SequenceOfOperationsJob::Lock(*that_.currentJob_));
+        jobLock_->SetTrailingOperationTimeout(that_.trailingTimeout_);
+      }
     }
 
     assert(jobLock_.get() != NULL);
--- a/OrthancServer/ServerJobs/ModifyInstanceOperation.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/ModifyInstanceOperation.cpp	Tue May 22 12:25:37 2018 +0200
@@ -115,7 +115,7 @@
       context_.Store(modifiedId, toStore);
 
       // Only chain with other commands if this command succeeds
-      outputs.Append(input.Clone());
+      outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId));
     }
     catch (OrthancException& e)
     {
@@ -123,5 +123,13 @@
                  << ": " << e.What();
     }
   }
+
+
+  void ModifyInstanceOperation::Serialize(Json::Value& target) const
+  {
+    target["Type"] = "ModifyInstance";
+    target["Origin"] = EnumerationToString(origin_);
+    modification_->Serialize(target["Modification"]);
+  }
 }
 
--- a/OrthancServer/ServerJobs/ModifyInstanceOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/ModifyInstanceOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -60,6 +60,8 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
+
+    virtual void Serialize(Json::Value& target) const;
   };
 }
 
--- a/OrthancServer/ServerJobs/StorePeerOperation.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/StorePeerOperation.cpp	Tue May 22 12:25:37 2018 +0200
@@ -80,4 +80,11 @@
 
     outputs.Append(input.Clone());
   }
+
+  
+  void StorePeerOperation::Serialize(Json::Value& result) const
+  {
+    result["Type"] = "StorePeer";
+    peer_.ToJson(result["Remote"]);
+  }
 }
--- a/OrthancServer/ServerJobs/StorePeerOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/StorePeerOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -52,6 +52,8 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
+
+    virtual void Serialize(Json::Value& result) const;
   };
 }
 
--- a/OrthancServer/ServerJobs/StoreScuOperation.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/StoreScuOperation.cpp	Tue May 22 12:25:37 2018 +0200
@@ -79,4 +79,12 @@
 
     outputs.Append(input.Clone());
   }
+
+  
+  void StoreScuOperation::Serialize(Json::Value& result) const
+  {
+    result["Type"] = "StoreScu";
+    result["LocalAET"] = localAet_;
+    modality_.ToJson(result["Modality"]);
+  }
 }
--- a/OrthancServer/ServerJobs/StoreScuOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/StoreScuOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -56,6 +56,8 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& manager);
+
+    virtual void Serialize(Json::Value& result) const;
   };
 }
 
--- a/OrthancServer/ServerJobs/SystemCallOperation.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/SystemCallOperation.cpp	Tue May 22 12:25:37 2018 +0200
@@ -111,5 +111,30 @@
       LOG(ERROR) << "Lua: Failed system call - \"" << info << "\": " << e.What();
     }
   }
+
+
+  void SystemCallOperation::Serialize(Json::Value& result) const
+  {
+    result["Type"] = "SystemCall";
+    result["Command"] = command_;
+
+    Json::Value tmp;
+
+    tmp = Json::arrayValue;
+    for (size_t i = 0; i < preArguments_.size(); i++)
+    {
+      tmp.append(preArguments_[i]);
+    }
+
+    result["PreArguments"] = tmp;
+
+    tmp = Json::arrayValue;
+    for (size_t i = 0; i < postArguments_.size(); i++)
+    {
+      tmp.append(postArguments_[i]);
+    }
+
+    result["PostArguments"] = tmp;
+  }
 }
 
--- a/OrthancServer/ServerJobs/SystemCallOperation.h	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/ServerJobs/SystemCallOperation.h	Tue May 22 12:25:37 2018 +0200
@@ -74,6 +74,8 @@
     virtual void Apply(JobOperationValues& outputs,
                        const JobOperationValue& input,
                        IDicomConnectionManager& connectionManager);
+
+    virtual void Serialize(Json::Value& result) const;
   };
 }
 
--- a/OrthancServer/main.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/OrthancServer/main.cpp	Tue May 22 12:25:37 2018 +0200
@@ -167,16 +167,19 @@
 class OrthancApplicationEntityFilter : public IApplicationEntityFilter
 {
 private:
-  ServerContext& context_;
-  bool           alwaysAllowEcho_;
-  bool           alwaysAllowStore_;
+  LuaScripting  lua_;
+  bool          alwaysAllowEcho_;
+  bool          alwaysAllowStore_;
 
 public:
   OrthancApplicationEntityFilter(ServerContext& context) :
-    context_(context)
+    lua_(context)
   {
     alwaysAllowEcho_ = Configuration::GetGlobalBoolParameter("DicomAlwaysAllowEcho", true);
     alwaysAllowStore_ = Configuration::GetGlobalBoolParameter("DicomAlwaysAllowStore", true);
+
+    LOG(INFO) << "Initializing Lua for OrthancApplicationEntityFilter";
+    lua_.LoadGlobalConfiguration();
   }
 
   virtual bool IsAllowedConnection(const std::string& remoteIp,
@@ -263,13 +266,13 @@
     }
 
     {
-      std::string lua = "Is" + configuration;
+      std::string name = "Is" + configuration;
 
-      LuaScripting::Locker locker(context_.GetLuaScripting());
+      LuaScripting::Lock lock(lua_);
       
-      if (locker.GetLua().IsExistingFunction(lua.c_str()))
+      if (lock.GetLua().IsExistingFunction(name.c_str()))
       {
-        LuaFunctionCall call(locker.GetLua(), lua.c_str());
+        LuaFunctionCall call(lock.GetLua(), name.c_str());
         call.PushString(remoteAet);
         call.PushString(remoteIp);
         call.PushString(calledAet);
@@ -290,11 +293,11 @@
     {
       std::string lua = "Is" + std::string(configuration);
 
-      LuaScripting::Locker locker(context_.GetLuaScripting());
+      LuaScripting::Lock lock(lua_);
       
-      if (locker.GetLua().IsExistingFunction(lua.c_str()))
+      if (lock.GetLua().IsExistingFunction(lua.c_str()))
       {
-        LuaFunctionCall call(locker.GetLua(), lua.c_str());
+        LuaFunctionCall call(lock.GetLua(), lua.c_str());
         call.PushString(remoteAet);
         call.PushString(remoteIp);
         call.PushString(calledAet);
@@ -310,15 +313,17 @@
 class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter
 {
 private:
-  ServerContext& context_;
+  LuaScripting     lua_;
   OrthancPlugins*  plugins_;
 
 public:
   MyIncomingHttpRequestFilter(ServerContext& context,
                               OrthancPlugins* plugins) : 
-    context_(context),
+    lua_(context),
     plugins_(plugins)
   {
+    LOG(INFO) << "Initializing Lua for MyIncomingHttpRequestFilter";
+    lua_.LoadGlobalConfiguration();
   }
 
   virtual bool IsAllowed(HttpMethod method,
@@ -326,7 +331,7 @@
                          const char* ip,
                          const char* username,
                          const IHttpHandler::Arguments& httpHeaders,
-                         const IHttpHandler::GetArguments& getArguments) const
+                         const IHttpHandler::GetArguments& getArguments)
   {
     if (plugins_ != NULL &&
         !plugins_->IsAllowed(method, uri, ip, username, httpHeaders, getArguments))
@@ -336,12 +341,12 @@
 
     static const char* HTTP_FILTER = "IncomingHttpRequestFilter";
 
-    LuaScripting::Locker locker(context_.GetLuaScripting());
+    LuaScripting::Lock lock(lua_);
 
     // Test if the instance must be filtered out
-    if (locker.GetLua().IsExistingFunction(HTTP_FILTER))
+    if (lock.GetLua().IsExistingFunction(HTTP_FILTER))
     {
-      LuaFunctionCall call(locker.GetLua(), HTTP_FILTER);
+      LuaFunctionCall call(lock.GetLua(), HTTP_FILTER);
 
       switch (method)
       {
@@ -640,25 +645,6 @@
 
 
 
-static void LoadLuaScripts(ServerContext& context)
-{
-  std::list<std::string> luaScripts;
-  Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
-  for (std::list<std::string>::const_iterator
-         it = luaScripts.begin(); it != luaScripts.end(); ++it)
-  {
-    std::string path = Configuration::InterpretStringParameterAsPath(*it);
-    LOG(WARNING) << "Installing the Lua scripts from: " << path;
-    std::string script;
-    SystemToolbox::ReadFile(script, path);
-
-    LuaScripting::Locker locker(context.GetLuaScripting());
-    locker.GetLua().Execute(script);
-  }
-}
-
-
-
 #if ORTHANC_ENABLE_PLUGINS == 1
 static void LoadPlugins(OrthancPlugins& plugins)
 {
@@ -689,7 +675,8 @@
   }
 #endif
 
-  context.GetLuaScripting().Execute("Initialize");
+  context.GetLuaEventHandler().Start();
+  context.GetLuaEventHandler().Execute("Initialize");
 
   bool restart;
 
@@ -723,7 +710,8 @@
     }
   }
 
-  context.GetLuaScripting().Execute("Finalize");
+  context.GetLuaEventHandler().Execute("Finalize");
+  context.GetLuaEventHandler().Stop();
 
 #if ORTHANC_ENABLE_PLUGINS == 1
   if (context.HasPlugins())
@@ -1011,7 +999,8 @@
     context.GetIndex().SetMaximumStorageSize(0);
   }
 
-  LoadLuaScripts(context);
+  LOG(INFO) << "Initializing Lua for the event handler";
+  context.GetLuaEventHandler().LoadGlobalConfiguration();
 
 #if ORTHANC_ENABLE_PLUGINS == 1
   if (plugins)
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon May 21 09:00:20 2018 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue May 22 12:25:37 2018 +0200
@@ -3115,7 +3115,7 @@
                                  const char* ip,
                                  const char* username,
                                  const IHttpHandler::Arguments& httpHeaders,
-                                 const IHttpHandler::GetArguments& getArguments) const
+                                 const IHttpHandler::GetArguments& getArguments)
   {
     OrthancPluginHttpMethod cMethod = Plugins::Convert(method);
 
--- a/Plugins/Engine/OrthancPlugins.h	Mon May 21 09:00:20 2018 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue May 22 12:25:37 2018 +0200
@@ -284,7 +284,7 @@
                            const char* ip,
                            const char* username,
                            const IHttpHandler::Arguments& httpHeaders,
-                           const IHttpHandler::GetArguments& getArguments) const;
+                           const IHttpHandler::GetArguments& getArguments);
 
     bool HasFindHandler();