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