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