1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * In addition, as a special exception, the copyright holders of this
13  * program give permission to link the code of its release with the
14  * OpenSSL project's "OpenSSL" library (or with modified versions of it
15  * that use the same license as the "OpenSSL" library), and distribute
16  * the linked executables. You must obey the GNU General Public License
17  * in all respects for all of the code used other than "OpenSSL". If you
18  * modify file(s) with this exception, you may extend this exception to
19  * your version of the file(s), but you are not obligated to do so. If
20  * you do not wish to do so, delete this exception statement from your
21  * version. If you delete this exception statement from all source files
22  * in the program, then also delete it here.
23  *
24  * This program is distributed in the hope that it will be useful, but
25  * WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27  * General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <http://www.gnu.org/licenses/>.
31  **/
32 
33 
34 #include "PrecompiledHeadersServer.h"
35 #include "LuaScripting.h"
36 
37 #include "OrthancConfiguration.h"
38 #include "OrthancRestApi/OrthancRestApi.h"
39 #include "ServerContext.h"
40 
41 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
42 #include "../../OrthancFramework/Sources/HttpServer/StringHttpOutput.h"
43 #include "../../OrthancFramework/Sources/Logging.h"
44 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
45 
46 #include <OrthancServerResources.h>
47 
48 
49 namespace Orthanc
50 {
51   class LuaScripting::IEvent : public IDynamicObject
52   {
53   public:
54     virtual void Apply(LuaScripting& lock) = 0;
55   };
56 
57 
58   class LuaScripting::OnStoredInstanceEvent : public LuaScripting::IEvent
59   {
60   private:
61     std::string    instanceId_;
62     Json::Value    simplifiedTags_;
63     Json::Value    metadata_;
64     Json::Value    origin_;
65 
66   public:
OnStoredInstanceEvent(const std::string & instanceId,const Json::Value & simplifiedTags,const Json::Value & metadata,const DicomInstanceToStore & instance)67     OnStoredInstanceEvent(const std::string& instanceId,
68                           const Json::Value& simplifiedTags,
69                           const Json::Value& metadata,
70                           const DicomInstanceToStore& instance) :
71       instanceId_(instanceId),
72       simplifiedTags_(simplifiedTags),
73       metadata_(metadata)
74     {
75       instance.GetOrigin().Format(origin_);
76     }
77 
Apply(LuaScripting & that)78     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
79     {
80       static const char* NAME = "OnStoredInstance";
81 
82       LuaScripting::Lock lock(that);
83 
84       if (lock.GetLua().IsExistingFunction(NAME))
85       {
86         that.InitializeJob();
87 
88         LuaFunctionCall call(lock.GetLua(), NAME);
89         call.PushString(instanceId_);
90         call.PushJson(simplifiedTags_);
91         call.PushJson(metadata_);
92         call.PushJson(origin_);
93         call.Execute();
94 
95         that.SubmitJob();
96       }
97     }
98   };
99 
100 
101   class LuaScripting::ExecuteEvent : public LuaScripting::IEvent
102   {
103   private:
104     std::string    command_;
105 
106   public:
ExecuteEvent(const std::string & command)107     explicit ExecuteEvent(const std::string& command) :
108       command_(command)
109     {
110     }
111 
Apply(LuaScripting & that)112     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
113     {
114       LuaScripting::Lock lock(that);
115 
116       if (lock.GetLua().IsExistingFunction(command_.c_str()))
117       {
118         LuaFunctionCall call(lock.GetLua(), command_.c_str());
119         call.Execute();
120       }
121     }
122   };
123 
124 
125   class LuaScripting::StableResourceEvent : public LuaScripting::IEvent
126   {
127   private:
128     ServerIndexChange  change_;
129 
130     class GetInfoOperations : public ServerIndex::IReadOnlyOperations
131     {
132     private:
133       const ServerIndexChange&            change_;
134       bool                                ok_;
135       DicomMap                            tags_;
136       std::map<MetadataType, std::string> metadata_;
137 
138     public:
GetInfoOperations(const ServerIndexChange & change)139       explicit GetInfoOperations(const ServerIndexChange& change) :
140         change_(change),
141         ok_(false)
142       {
143       }
144 
Apply(ServerIndex::ReadOnlyTransaction & transaction)145       virtual void Apply(ServerIndex::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE
146       {
147         int64_t internalId;
148         ResourceType level;
149         if (transaction.LookupResource(internalId, level, change_.GetPublicId()) &&
150             level == change_.GetResourceType())
151         {
152           transaction.GetMainDicomTags(tags_, internalId);
153           transaction.GetAllMetadata(metadata_, internalId);
154           ok_ = true;
155         }
156       }
157 
CallLua(LuaScripting & that,const char * name) const158       void CallLua(LuaScripting& that,
159                    const char* name) const
160       {
161         if (ok_)
162         {
163           Json::Value formattedMetadata = Json::objectValue;
164 
165           for (std::map<MetadataType, std::string>::const_iterator
166                  it = metadata_.begin(); it != metadata_.end(); ++it)
167           {
168             std::string key = EnumerationToString(it->first);
169             formattedMetadata[key] = it->second;
170           }
171 
172           {
173             LuaScripting::Lock lock(that);
174 
175             if (lock.GetLua().IsExistingFunction(name))
176             {
177               that.InitializeJob();
178 
179               Json::Value json = Json::objectValue;
180 
181               if (change_.GetResourceType() == ResourceType_Study)
182               {
183                 DicomMap t;
184                 tags_.ExtractStudyInformation(t);  // Discard patient-related tags
185                 FromDcmtkBridge::ToJson(json, t, true);
186               }
187               else
188               {
189                 FromDcmtkBridge::ToJson(json, tags_, true);
190               }
191 
192               LuaFunctionCall call(lock.GetLua(), name);
193               call.PushString(change_.GetPublicId());
194               call.PushJson(json);
195               call.PushJson(formattedMetadata);
196               call.Execute();
197 
198               that.SubmitJob();
199             }
200           }
201         }
202       }
203     };
204 
205 
206   public:
StableResourceEvent(const ServerIndexChange & change)207     explicit StableResourceEvent(const ServerIndexChange& change) :
208       change_(change)
209     {
210     }
211 
Apply(LuaScripting & that)212     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
213     {
214       const char* name;
215 
216       switch (change_.GetChangeType())
217       {
218         case ChangeType_StablePatient:
219           name = "OnStablePatient";
220           break;
221 
222         case ChangeType_StableStudy:
223           name = "OnStableStudy";
224           break;
225 
226         case ChangeType_StableSeries:
227           name = "OnStableSeries";
228           break;
229 
230         default:
231           throw OrthancException(ErrorCode_InternalError);
232       }
233 
234       {
235         // Avoid unnecessary calls to the database if there's no Lua callback
236         LuaScripting::Lock lock(that);
237 
238         if (!lock.GetLua().IsExistingFunction(name))
239         {
240           return;
241         }
242       }
243 
244       GetInfoOperations operations(change_);
245       that.context_.GetIndex().Apply(operations);
246       operations.CallLua(that, name);
247     }
248   };
249 
250 
251   class LuaScripting::JobEvent : public LuaScripting::IEvent
252   {
253   public:
254     enum Type
255     {
256       Type_Failure,
257       Type_Submitted,
258       Type_Success
259     };
260 
261   private:
262     Type         type_;
263     std::string  jobId_;
264 
265   public:
JobEvent(Type type,const std::string & jobId)266     JobEvent(Type type,
267              const std::string& jobId) :
268       type_(type),
269       jobId_(jobId)
270     {
271     }
272 
Apply(LuaScripting & that)273     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
274     {
275       std::string functionName;
276 
277       switch (type_)
278       {
279         case Type_Failure:
280           functionName = "OnJobFailure";
281           break;
282 
283         case Type_Submitted:
284           functionName = "OnJobSubmitted";
285           break;
286 
287         case Type_Success:
288           functionName = "OnJobSuccess";
289           break;
290 
291         default:
292           throw OrthancException(ErrorCode_InternalError);
293       }
294 
295       {
296         LuaScripting::Lock lock(that);
297 
298         if (lock.GetLua().IsExistingFunction(functionName.c_str()))
299         {
300           LuaFunctionCall call(lock.GetLua(), functionName.c_str());
301           call.PushString(jobId_);
302           call.Execute();
303         }
304       }
305     }
306   };
307 
308 
309   class LuaScripting::DeleteEvent : public LuaScripting::IEvent
310   {
311   private:
312     ResourceType  level_;
313     std::string   publicId_;
314 
315   public:
DeleteEvent(ResourceType level,const std::string & publicId)316     DeleteEvent(ResourceType level,
317                 const std::string& publicId) :
318       level_(level),
319       publicId_(publicId)
320     {
321     }
322 
Apply(LuaScripting & that)323     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
324     {
325       std::string functionName;
326 
327       switch (level_)
328       {
329         case ResourceType_Patient:
330           functionName = "OnDeletedPatient";
331           break;
332 
333         case ResourceType_Study:
334           functionName = "OnDeletedStudy";
335           break;
336 
337         case ResourceType_Series:
338           functionName = "OnDeletedSeries";
339           break;
340 
341         case ResourceType_Instance:
342           functionName = "OnDeletedInstance";
343           break;
344 
345         default:
346           throw OrthancException(ErrorCode_InternalError);
347       }
348 
349       {
350         LuaScripting::Lock lock(that);
351 
352         if (lock.GetLua().IsExistingFunction(functionName.c_str()))
353         {
354           LuaFunctionCall call(lock.GetLua(), functionName.c_str());
355           call.PushString(publicId_);
356           call.Execute();
357         }
358       }
359     }
360   };
361 
362 
363   class LuaScripting::UpdateEvent : public LuaScripting::IEvent
364   {
365   private:
366     ResourceType  level_;
367     std::string   publicId_;
368 
369   public:
UpdateEvent(ResourceType level,const std::string & publicId)370     UpdateEvent(ResourceType level,
371                 const std::string& publicId) :
372       level_(level),
373       publicId_(publicId)
374     {
375     }
376 
Apply(LuaScripting & that)377     virtual void Apply(LuaScripting& that) ORTHANC_OVERRIDE
378     {
379       std::string functionName;
380 
381       switch (level_)
382       {
383         case ResourceType_Patient:
384           functionName = "OnUpdatedPatient";
385           break;
386 
387         case ResourceType_Study:
388           functionName = "OnUpdatedStudy";
389           break;
390 
391         case ResourceType_Series:
392           functionName = "OnUpdatedSeries";
393           break;
394 
395         case ResourceType_Instance:
396           functionName = "OnUpdatedInstance";
397           break;
398 
399         default:
400           throw OrthancException(ErrorCode_InternalError);
401       }
402 
403       {
404         LuaScripting::Lock lock(that);
405 
406         if (lock.GetLua().IsExistingFunction(functionName.c_str()))
407         {
408           LuaFunctionCall call(lock.GetLua(), functionName.c_str());
409           call.PushString(publicId_);
410           call.Execute();
411         }
412       }
413     }
414   };
415 
416 
GetServerContext(lua_State * state)417   ServerContext* LuaScripting::GetServerContext(lua_State *state)
418   {
419     const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext");
420     return const_cast<ServerContext*>(reinterpret_cast<const ServerContext*>(value));
421   }
422 
423 
424   // Syntax in Lua: RestApiGet(uri, builtin)
RestApiGet(lua_State * state)425   int LuaScripting::RestApiGet(lua_State *state)
426   {
427     ServerContext* serverContext = GetServerContext(state);
428     if (serverContext == NULL)
429     {
430       LOG(ERROR) << "Lua: The Orthanc API is unavailable";
431       lua_pushnil(state);
432       return 1;
433     }
434 
435     // Check the types of the arguments
436     int nArgs = lua_gettop(state);
437     if (nArgs < 1 || nArgs > 3 ||
438         !lua_isstring(state, 1) ||                 // URI
439         (nArgs >= 2 && !lua_isboolean(state, 2)))  // Restrict to built-in API?
440     {
441       LOG(ERROR) << "Lua: Bad parameters to RestApiGet()";
442       lua_pushnil(state);
443       return 1;
444     }
445 
446     const char* uri = lua_tostring(state, 1);
447     bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false);
448 
449     std::map<std::string, std::string> headers;
450     LuaContext::GetDictionaryArgument(headers, state, 3, true /* HTTP header key to lower case */);
451 
452     try
453     {
454       std::string result;
455       if (IHttpHandler::SimpleGet(result, NULL, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin),
456                                   RequestOrigin_Lua, uri, headers) == HttpStatus_200_Ok)
457       {
458         lua_pushlstring(state, result.c_str(), result.size());
459         return 1;
460       }
461     }
462     catch (OrthancException& e)
463     {
464       LOG(ERROR) << "Lua: " << e.What();
465     }
466 
467     LOG(ERROR) << "Lua: Error in RestApiGet() for URI: " << uri;
468     lua_pushnil(state);
469     return 1;
470   }
471 
472 
RestApiPostOrPut(lua_State * state,bool isPost)473   int LuaScripting::RestApiPostOrPut(lua_State *state,
474                                      bool isPost)
475   {
476     ServerContext* serverContext = GetServerContext(state);
477     if (serverContext == NULL)
478     {
479       LOG(ERROR) << "Lua: The Orthanc API is unavailable";
480       lua_pushnil(state);
481       return 1;
482     }
483 
484     // Check the types of the arguments
485     int nArgs = lua_gettop(state);
486     if (nArgs < 2 || nArgs > 4 ||
487         !lua_isstring(state, 1) ||                 // URI
488         !lua_isstring(state, 2) ||                 // Body
489         (nArgs >= 3 && !lua_isboolean(state, 3)))  // Restrict to built-in API?
490     {
491       LOG(ERROR) << "Lua: Bad parameters to " << (isPost ? "RestApiPost()" : "RestApiPut()");
492       lua_pushnil(state);
493       return 1;
494     }
495 
496     const char* uri = lua_tostring(state, 1);
497     size_t bodySize = 0;
498     const char* bodyData = lua_tolstring(state, 2, &bodySize);
499     bool builtin = (nArgs == 3 ? lua_toboolean(state, 3) != 0 : false);
500 
501     std::map<std::string, std::string> headers;
502     LuaContext::GetDictionaryArgument(headers, state, 4, true /* HTTP header key to lower case */);
503 
504     try
505     {
506       std::string result;
507       if (isPost ?
508           IHttpHandler::SimplePost(result, NULL,
509                                    serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin),
510                                    RequestOrigin_Lua, uri, bodyData, bodySize, headers) == HttpStatus_200_Ok :
511           IHttpHandler::SimplePut(result, NULL,
512                                   serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin),
513                                   RequestOrigin_Lua, uri, bodyData, bodySize, headers) == HttpStatus_200_Ok)
514       {
515         lua_pushlstring(state, result.c_str(), result.size());
516         return 1;
517       }
518     }
519     catch (OrthancException& e)
520     {
521       LOG(ERROR) << "Lua: " << e.What();
522     }
523 
524     LOG(ERROR) << "Lua: Error in " << (isPost ? "RestApiPost()" : "RestApiPut()") << " for URI: " << uri;
525     lua_pushnil(state);
526     return 1;
527   }
528 
529 
530   // Syntax in Lua: RestApiPost(uri, body, builtin)
RestApiPost(lua_State * state)531   int LuaScripting::RestApiPost(lua_State *state)
532   {
533     return RestApiPostOrPut(state, true);
534   }
535 
536 
537   // Syntax in Lua: RestApiPut(uri, body, builtin)
RestApiPut(lua_State * state)538   int LuaScripting::RestApiPut(lua_State *state)
539   {
540     return RestApiPostOrPut(state, false);
541   }
542 
543 
544   // Syntax in Lua: RestApiDelete(uri, builtin)
RestApiDelete(lua_State * state)545   int LuaScripting::RestApiDelete(lua_State *state)
546   {
547     ServerContext* serverContext = GetServerContext(state);
548     if (serverContext == NULL)
549     {
550       LOG(ERROR) << "Lua: The Orthanc API is unavailable";
551       lua_pushnil(state);
552       return 1;
553     }
554 
555     // Check the types of the arguments
556     int nArgs = lua_gettop(state);
557     if (nArgs < 1 || nArgs > 3 ||
558         !lua_isstring(state, 1) ||                 // URI
559         (nArgs >= 2 && !lua_isboolean(state, 2)))  // Restrict to built-in API?
560     {
561       LOG(ERROR) << "Lua: Bad parameters to RestApiDelete()";
562       lua_pushnil(state);
563       return 1;
564     }
565 
566     const char* uri = lua_tostring(state, 1);
567     bool builtin = (nArgs == 2 ? lua_toboolean(state, 2) != 0 : false);
568 
569     std::map<std::string, std::string> headers;
570     LuaContext::GetDictionaryArgument(headers, state, 3, true /* HTTP header key to lower case */);
571 
572     try
573     {
574       if (IHttpHandler::SimpleDelete(NULL, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin),
575                                      RequestOrigin_Lua, uri, headers) == HttpStatus_200_Ok)
576       {
577         lua_pushboolean(state, 1);
578         return 1;
579       }
580     }
581     catch (OrthancException& e)
582     {
583       LOG(ERROR) << "Lua: " << e.What();
584     }
585 
586     LOG(ERROR) << "Lua: Error in RestApiDelete() for URI: " << uri;
587     lua_pushnil(state);
588 
589     return 1;
590   }
591 
592 
593   // Syntax in Lua: GetOrthancConfiguration()
GetOrthancConfiguration(lua_State * state)594   int LuaScripting::GetOrthancConfiguration(lua_State *state)
595   {
596     Json::Value configuration;
597 
598     {
599       OrthancConfiguration::ReaderLock lock;
600       configuration = lock.GetJson();
601     }
602 
603     LuaContext::GetLuaContext(state).PushJson(configuration);
604 
605     return 1;
606   }
607 
608 
ParseOperation(LuaJobManager::Lock & lock,const std::string & operation,const Json::Value & parameters)609   size_t LuaScripting::ParseOperation(LuaJobManager::Lock& lock,
610                                       const std::string& operation,
611                                       const Json::Value& parameters)
612   {
613     if (operation == "delete")
614     {
615       LOG(INFO) << "Lua script to delete resource " << parameters["Resource"].asString();
616       return lock.AddDeleteResourceOperation(context_);
617     }
618 
619     if (operation == "store-scu")
620     {
621       std::string localAet;
622       if (parameters.isMember("LocalAet"))
623       {
624         localAet = parameters["LocalAet"].asString();
625       }
626       else
627       {
628         localAet = context_.GetDefaultLocalApplicationEntityTitle();
629       }
630 
631       std::string name = parameters["Modality"].asString();
632       RemoteModalityParameters modality;
633 
634       {
635         OrthancConfiguration::ReaderLock configLock;
636         modality = configLock.GetConfiguration().GetModalityUsingSymbolicName(name);
637       }
638 
639       // This is not a C-MOVE: No need to call "StoreScuCommand::SetMoveOriginator()"
640       return lock.AddStoreScuOperation(context_, localAet, modality);
641     }
642 
643     if (operation == "store-peer")
644     {
645       OrthancConfiguration::ReaderLock configLock;
646       std::string name = parameters["Peer"].asString();
647 
648       WebServiceParameters peer;
649       if (configLock.GetConfiguration().LookupOrthancPeer(peer, name))
650       {
651         return lock.AddStorePeerOperation(peer);
652       }
653       else
654       {
655         throw OrthancException(ErrorCode_UnknownResource,
656                                "No peer with symbolic name: " + name);
657       }
658     }
659 
660     if (operation == "modify")
661     {
662       std::unique_ptr<DicomModification> modification(new DicomModification);
663       modification->ParseModifyRequest(parameters);
664 
665       return lock.AddModifyInstanceOperation(context_, modification.release());
666     }
667 
668     if (operation == "call-system")
669     {
670       LOG(INFO) << "Lua script to call system command on " << parameters["Resource"].asString();
671 
672       const Json::Value& argsIn = parameters["Arguments"];
673       if (argsIn.type() != Json::arrayValue)
674       {
675         throw OrthancException(ErrorCode_BadParameterType);
676       }
677 
678       std::vector<std::string> args;
679       args.reserve(argsIn.size());
680       for (Json::Value::ArrayIndex i = 0; i < argsIn.size(); ++i)
681       {
682         // http://jsoncpp.sourceforge.net/namespace_json.html#7d654b75c16a57007925868e38212b4e
683         switch (argsIn[i].type())
684         {
685           case Json::stringValue:
686             args.push_back(argsIn[i].asString());
687             break;
688 
689           case Json::intValue:
690             args.push_back(boost::lexical_cast<std::string>(argsIn[i].asInt()));
691             break;
692 
693           case Json::uintValue:
694             args.push_back(boost::lexical_cast<std::string>(argsIn[i].asUInt()));
695             break;
696 
697           case Json::realValue:
698             args.push_back(boost::lexical_cast<std::string>(argsIn[i].asFloat()));
699             break;
700 
701           default:
702             throw OrthancException(ErrorCode_BadParameterType);
703         }
704       }
705 
706       std::string command = parameters["Command"].asString();
707       std::vector<std::string> postArgs;
708 
709       return lock.AddSystemCallOperation(command, args, postArgs);
710     }
711 
712     throw OrthancException(ErrorCode_ParameterOutOfRange);
713   }
714 
715 
InitializeJob()716   void LuaScripting::InitializeJob()
717   {
718     lua_.Execute("_InitializeJob()");
719   }
720 
721 
SubmitJob()722   void LuaScripting::SubmitJob()
723   {
724     Json::Value operations;
725     LuaFunctionCall call2(lua_, "_AccessJob");
726     call2.ExecuteToJson(operations, false);
727 
728     if (operations.type() != Json::arrayValue)
729     {
730       throw OrthancException(ErrorCode_InternalError);
731     }
732 
733     LuaJobManager::Lock lock(jobManager_, context_.GetJobsEngine());
734 
735     bool isFirst = true;
736     size_t previous = 0;  // Dummy initialization to avoid warning
737 
738     for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i)
739     {
740       if (operations[i].type() != Json::objectValue ||
741           !operations[i].isMember("Operation"))
742       {
743         throw OrthancException(ErrorCode_InternalError);
744       }
745 
746       const Json::Value& parameters = operations[i];
747       if (!parameters.isMember("Resource"))
748       {
749         throw OrthancException(ErrorCode_InternalError);
750       }
751 
752       std::string operation = parameters["Operation"].asString();
753       size_t index = ParseOperation(lock, operation, operations[i]);
754 
755       std::string resource = parameters["Resource"].asString();
756       if (!resource.empty())
757       {
758         lock.AddDicomInstanceInput(index, context_, resource);
759       }
760       else if (!isFirst)
761       {
762         lock.Connect(previous, index);
763       }
764 
765       isFirst = false;
766       previous = index;
767     }
768   }
769 
770 
LuaScripting(ServerContext & context)771   LuaScripting::LuaScripting(ServerContext& context) :
772     context_(context),
773     state_(State_Setup)
774   {
775     lua_.SetGlobalVariable("_ServerContext", &context);
776     lua_.RegisterFunction("RestApiGet", RestApiGet);
777     lua_.RegisterFunction("RestApiPost", RestApiPost);
778     lua_.RegisterFunction("RestApiPut", RestApiPut);
779     lua_.RegisterFunction("RestApiDelete", RestApiDelete);
780     lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration);
781 
782     LOG(INFO) << "Initializing Lua for the event handler";
783     LoadGlobalConfiguration();
784   }
785 
786 
~LuaScripting()787   LuaScripting::~LuaScripting()
788   {
789     if (state_ == State_Running)
790     {
791       LOG(ERROR) << "INTERNAL ERROR: LuaScripting::Stop() should be invoked manually to avoid mess in the destruction order!";
792       Stop();
793     }
794   }
795 
796 
EventThread(LuaScripting * that)797   void LuaScripting::EventThread(LuaScripting* that)
798   {
799     for (;;)
800     {
801       std::unique_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100));
802 
803       if (event.get() == NULL)
804       {
805         // The event queue is empty, check whether we should stop
806         boost::recursive_mutex::scoped_lock lock(that->mutex_);
807 
808         if (that->state_ != State_Running)
809         {
810           return;
811         }
812       }
813       else
814       {
815         try
816         {
817           dynamic_cast<IEvent&>(*event).Apply(*that);
818         }
819         catch (OrthancException& e)
820         {
821           LOG(ERROR) << "Error while processing Lua events: " << e.What();
822         }
823       }
824 
825       that->jobManager_.GetDicomConnectionManager().CloseIfInactive();
826     }
827   }
828 
829 
Start()830   void LuaScripting::Start()
831   {
832     boost::recursive_mutex::scoped_lock lock(mutex_);
833 
834     if (state_ != State_Setup ||
835         eventThread_.joinable()  /* already started */)
836     {
837       throw OrthancException(ErrorCode_BadSequenceOfCalls);
838     }
839     else
840     {
841       LOG(INFO) << "Starting the Lua engine";
842       eventThread_ = boost::thread(EventThread, this);
843       state_ = State_Running;
844     }
845   }
846 
847 
Stop()848   void LuaScripting::Stop()
849   {
850     {
851       boost::recursive_mutex::scoped_lock lock(mutex_);
852 
853       if (state_ != State_Running)
854       {
855         throw OrthancException(ErrorCode_BadSequenceOfCalls);
856       }
857 
858       state_ = State_Done;
859     }
860 
861     jobManager_.AwakeTrailingSleep();
862 
863     if (eventThread_.joinable())
864     {
865       LOG(INFO) << "Stopping the Lua engine";
866       eventThread_.join();
867       LOG(INFO) << "The Lua engine has stopped";
868     }
869   }
870 
871 
SignalStoredInstance(const std::string & publicId,const DicomInstanceToStore & instance,const Json::Value & simplifiedTags)872   void LuaScripting::SignalStoredInstance(const std::string& publicId,
873                                           const DicomInstanceToStore& instance,
874                                           const Json::Value& simplifiedTags)
875   {
876     Json::Value metadata = Json::objectValue;
877 
878     for (ServerIndex::MetadataMap::const_iterator
879            it = instance.GetMetadata().begin();
880          it != instance.GetMetadata().end(); ++it)
881     {
882       if (it->first.first == ResourceType_Instance)
883       {
884         metadata[EnumerationToString(it->first.second)] = it->second;
885       }
886     }
887 
888     pendingEvents_.Enqueue(new OnStoredInstanceEvent(publicId, simplifiedTags, metadata, instance));
889   }
890 
891 
SignalChange(const ServerIndexChange & change)892   void LuaScripting::SignalChange(const ServerIndexChange& change)
893   {
894     if (change.GetChangeType() == ChangeType_StablePatient ||
895         change.GetChangeType() == ChangeType_StableStudy ||
896         change.GetChangeType() == ChangeType_StableSeries)
897     {
898       pendingEvents_.Enqueue(new StableResourceEvent(change));
899     }
900     else if (change.GetChangeType() == ChangeType_Deleted)
901     {
902       pendingEvents_.Enqueue(new DeleteEvent(change.GetResourceType(), change.GetPublicId()));
903     }
904     else if (change.GetChangeType() == ChangeType_UpdatedAttachment ||
905              change.GetChangeType() == ChangeType_UpdatedMetadata)
906     {
907       pendingEvents_.Enqueue(new UpdateEvent(change.GetResourceType(), change.GetPublicId()));
908     }
909   }
910 
911 
FilterIncomingInstance(const DicomInstanceToStore & instance,const Json::Value & simplified)912   bool LuaScripting::FilterIncomingInstance(const DicomInstanceToStore& instance,
913                                             const Json::Value& simplified)
914   {
915     static const char* NAME = "ReceivedInstanceFilter";
916 
917     boost::recursive_mutex::scoped_lock lock(mutex_);
918 
919     if (lua_.IsExistingFunction(NAME))
920     {
921       LuaFunctionCall call(lua_, NAME);
922       call.PushJson(simplified);
923 
924       Json::Value origin;
925       instance.GetOrigin().Format(origin);
926       call.PushJson(origin);
927 
928       Json::Value info = Json::objectValue;
929       info["HasPixelData"] = instance.HasPixelData();
930 
931       DicomTransferSyntax s;
932       if (instance.LookupTransferSyntax(s))
933       {
934         info["TransferSyntaxUID"] = GetTransferSyntaxUid(s);
935       }
936 
937       call.PushJson(info);
938 
939       if (!call.ExecutePredicate())
940       {
941         return false;
942       }
943     }
944 
945     return true;
946   }
947 
948 
Execute(const std::string & command)949   void LuaScripting::Execute(const std::string& command)
950   {
951     pendingEvents_.Enqueue(new ExecuteEvent(command));
952   }
953 
954 
LoadGlobalConfiguration()955   void LuaScripting::LoadGlobalConfiguration()
956   {
957     OrthancConfiguration::ReaderLock configLock;
958 
959     {
960       std::string command;
961       Orthanc::ServerResources::GetFileResource(command, Orthanc::ServerResources::LUA_TOOLBOX);
962       lua_.Execute(command);
963     }
964 
965     std::list<std::string> luaScripts;
966     configLock.GetConfiguration().GetListOfStringsParameter(luaScripts, "LuaScripts");
967 
968     LuaScripting::Lock lock(*this);
969 
970     for (std::list<std::string>::const_iterator
971            it = luaScripts.begin(); it != luaScripts.end(); ++it)
972     {
973       std::string path = configLock.GetConfiguration().InterpretStringParameterAsPath(*it);
974       LOG(INFO) << "Installing the Lua scripts from: " << path;
975       std::string script;
976       SystemToolbox::ReadFile(script, path);
977 
978       lock.GetLua().Execute(script);
979     }
980   }
981 
982 
SignalJobSubmitted(const std::string & jobId)983   void LuaScripting::SignalJobSubmitted(const std::string& jobId)
984   {
985     pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Submitted, jobId));
986   }
987 
988 
SignalJobSuccess(const std::string & jobId)989   void LuaScripting::SignalJobSuccess(const std::string& jobId)
990   {
991     pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Success, jobId));
992   }
993 
994 
SignalJobFailure(const std::string & jobId)995   void LuaScripting::SignalJobFailure(const std::string& jobId)
996   {
997     pendingEvents_.Enqueue(new JobEvent(JobEvent::Type_Failure, jobId));
998   }
999 }
1000