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 "StatelessDatabaseOperations.h" 36 37 #ifndef NOMINMAX 38 #define NOMINMAX 39 #endif 40 41 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" 42 #include "../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h" 43 #include "../../../OrthancFramework/Sources/Logging.h" 44 #include "../../../OrthancFramework/Sources/OrthancException.h" 45 #include "../OrthancConfiguration.h" 46 #include "../Search/DatabaseLookup.h" 47 #include "../ServerIndexChange.h" 48 #include "../ServerToolbox.h" 49 #include "ResourcesContent.h" 50 51 #include <boost/lexical_cast.hpp> 52 #include <boost/thread.hpp> 53 #include <boost/tuple/tuple.hpp> 54 #include <stack> 55 56 57 namespace Orthanc 58 { 59 namespace 60 { 61 /** 62 * Some handy templates to reduce the verbosity in the definitions 63 * of the internal classes. 64 **/ 65 66 template <typename Operations, 67 typename Tuple> 68 class TupleOperationsWrapper : public StatelessDatabaseOperations::IReadOnlyOperations 69 { 70 protected: 71 Operations& operations_; 72 const Tuple& tuple_; 73 74 public: TupleOperationsWrapper(Operations & operations,const Tuple & tuple)75 TupleOperationsWrapper(Operations& operations, 76 const Tuple& tuple) : 77 operations_(operations), 78 tuple_(tuple) 79 { 80 } 81 Apply(StatelessDatabaseOperations::ReadOnlyTransaction & transaction)82 virtual void Apply(StatelessDatabaseOperations::ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE 83 { 84 operations_.ApplyTuple(transaction, tuple_); 85 } 86 }; 87 88 89 template <typename T1> 90 class ReadOnlyOperationsT1 : public boost::noncopyable 91 { 92 public: 93 typedef typename boost::tuple<T1> Tuple; 94 ~ReadOnlyOperationsT1()95 virtual ~ReadOnlyOperationsT1() 96 { 97 } 98 99 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 100 const Tuple& tuple) = 0; 101 Apply(StatelessDatabaseOperations & index,T1 t1)102 void Apply(StatelessDatabaseOperations& index, 103 T1 t1) 104 { 105 const Tuple tuple(t1); 106 TupleOperationsWrapper<ReadOnlyOperationsT1, Tuple> wrapper(*this, tuple); 107 index.Apply(wrapper); 108 } 109 }; 110 111 112 template <typename T1, 113 typename T2> 114 class ReadOnlyOperationsT2 : public boost::noncopyable 115 { 116 public: 117 typedef typename boost::tuple<T1, T2> Tuple; 118 ~ReadOnlyOperationsT2()119 virtual ~ReadOnlyOperationsT2() 120 { 121 } 122 123 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 124 const Tuple& tuple) = 0; 125 Apply(StatelessDatabaseOperations & index,T1 t1,T2 t2)126 void Apply(StatelessDatabaseOperations& index, 127 T1 t1, 128 T2 t2) 129 { 130 const Tuple tuple(t1, t2); 131 TupleOperationsWrapper<ReadOnlyOperationsT2, Tuple> wrapper(*this, tuple); 132 index.Apply(wrapper); 133 } 134 }; 135 136 137 template <typename T1, 138 typename T2, 139 typename T3> 140 class ReadOnlyOperationsT3 : public boost::noncopyable 141 { 142 public: 143 typedef typename boost::tuple<T1, T2, T3> Tuple; 144 ~ReadOnlyOperationsT3()145 virtual ~ReadOnlyOperationsT3() 146 { 147 } 148 149 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 150 const Tuple& tuple) = 0; 151 Apply(StatelessDatabaseOperations & index,T1 t1,T2 t2,T3 t3)152 void Apply(StatelessDatabaseOperations& index, 153 T1 t1, 154 T2 t2, 155 T3 t3) 156 { 157 const Tuple tuple(t1, t2, t3); 158 TupleOperationsWrapper<ReadOnlyOperationsT3, Tuple> wrapper(*this, tuple); 159 index.Apply(wrapper); 160 } 161 }; 162 163 164 template <typename T1, 165 typename T2, 166 typename T3, 167 typename T4> 168 class ReadOnlyOperationsT4 : public boost::noncopyable 169 { 170 public: 171 typedef typename boost::tuple<T1, T2, T3, T4> Tuple; 172 ~ReadOnlyOperationsT4()173 virtual ~ReadOnlyOperationsT4() 174 { 175 } 176 177 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 178 const Tuple& tuple) = 0; 179 Apply(StatelessDatabaseOperations & index,T1 t1,T2 t2,T3 t3,T4 t4)180 void Apply(StatelessDatabaseOperations& index, 181 T1 t1, 182 T2 t2, 183 T3 t3, 184 T4 t4) 185 { 186 const Tuple tuple(t1, t2, t3, t4); 187 TupleOperationsWrapper<ReadOnlyOperationsT4, Tuple> wrapper(*this, tuple); 188 index.Apply(wrapper); 189 } 190 }; 191 192 193 template <typename T1, 194 typename T2, 195 typename T3, 196 typename T4, 197 typename T5> 198 class ReadOnlyOperationsT5 : public boost::noncopyable 199 { 200 public: 201 typedef typename boost::tuple<T1, T2, T3, T4, T5> Tuple; 202 ~ReadOnlyOperationsT5()203 virtual ~ReadOnlyOperationsT5() 204 { 205 } 206 207 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 208 const Tuple& tuple) = 0; 209 Apply(StatelessDatabaseOperations & index,T1 t1,T2 t2,T3 t3,T4 t4,T5 t5)210 void Apply(StatelessDatabaseOperations& index, 211 T1 t1, 212 T2 t2, 213 T3 t3, 214 T4 t4, 215 T5 t5) 216 { 217 const Tuple tuple(t1, t2, t3, t4, t5); 218 TupleOperationsWrapper<ReadOnlyOperationsT5, Tuple> wrapper(*this, tuple); 219 index.Apply(wrapper); 220 } 221 }; 222 223 224 template <typename T1, 225 typename T2, 226 typename T3, 227 typename T4, 228 typename T5, 229 typename T6> 230 class ReadOnlyOperationsT6 : public boost::noncopyable 231 { 232 public: 233 typedef typename boost::tuple<T1, T2, T3, T4, T5, T6> Tuple; 234 ~ReadOnlyOperationsT6()235 virtual ~ReadOnlyOperationsT6() 236 { 237 } 238 239 virtual void ApplyTuple(StatelessDatabaseOperations::ReadOnlyTransaction& transaction, 240 const Tuple& tuple) = 0; 241 Apply(StatelessDatabaseOperations & index,T1 t1,T2 t2,T3 t3,T4 t4,T5 t5,T6 t6)242 void Apply(StatelessDatabaseOperations& index, 243 T1 t1, 244 T2 t2, 245 T3 t3, 246 T4 t4, 247 T5 t5, 248 T6 t6) 249 { 250 const Tuple tuple(t1, t2, t3, t4, t5, t6); 251 TupleOperationsWrapper<ReadOnlyOperationsT6, Tuple> wrapper(*this, tuple); 252 index.Apply(wrapper); 253 } 254 }; 255 } 256 257 258 template <typename T> FormatLog(Json::Value & target,const std::list<T> & log,const std::string & name,bool done,int64_t since,bool hasLast,int64_t last)259 static void FormatLog(Json::Value& target, 260 const std::list<T>& log, 261 const std::string& name, 262 bool done, 263 int64_t since, 264 bool hasLast, 265 int64_t last) 266 { 267 Json::Value items = Json::arrayValue; 268 for (typename std::list<T>::const_iterator 269 it = log.begin(); it != log.end(); ++it) 270 { 271 Json::Value item; 272 it->Format(item); 273 items.append(item); 274 } 275 276 target = Json::objectValue; 277 target[name] = items; 278 target["Done"] = done; 279 280 if (!hasLast) 281 { 282 // Best-effort guess of the last index in the sequence 283 if (log.empty()) 284 { 285 last = since; 286 } 287 else 288 { 289 last = log.back().GetSeq(); 290 } 291 } 292 293 target["Last"] = static_cast<int>(last); 294 } 295 296 CopyListToVector(std::vector<std::string> & target,const std::list<std::string> & source)297 static void CopyListToVector(std::vector<std::string>& target, 298 const std::list<std::string>& source) 299 { 300 target.resize(source.size()); 301 302 size_t pos = 0; 303 304 for (std::list<std::string>::const_iterator 305 it = source.begin(); it != source.end(); ++it) 306 { 307 target[pos] = *it; 308 pos ++; 309 } 310 } 311 312 313 class StatelessDatabaseOperations::MainDicomTagsRegistry : public boost::noncopyable 314 { 315 private: 316 class TagInfo 317 { 318 private: 319 ResourceType level_; 320 DicomTagType type_; 321 322 public: TagInfo()323 TagInfo() 324 { 325 } 326 TagInfo(ResourceType level,DicomTagType type)327 TagInfo(ResourceType level, 328 DicomTagType type) : 329 level_(level), 330 type_(type) 331 { 332 } 333 GetLevel() const334 ResourceType GetLevel() const 335 { 336 return level_; 337 } 338 GetType() const339 DicomTagType GetType() const 340 { 341 return type_; 342 } 343 }; 344 345 typedef std::map<DicomTag, TagInfo> Registry; 346 347 348 Registry registry_; 349 LoadTags(ResourceType level)350 void LoadTags(ResourceType level) 351 { 352 { 353 const DicomTag* tags = NULL; 354 size_t size; 355 356 ServerToolbox::LoadIdentifiers(tags, size, level); 357 358 for (size_t i = 0; i < size; i++) 359 { 360 if (registry_.find(tags[i]) == registry_.end()) 361 { 362 registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier); 363 } 364 else 365 { 366 // These patient-level tags are copied in the study level 367 assert(level == ResourceType_Study && 368 (tags[i] == DICOM_TAG_PATIENT_ID || 369 tags[i] == DICOM_TAG_PATIENT_NAME || 370 tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE)); 371 } 372 } 373 } 374 375 { 376 std::set<DicomTag> tags; 377 DicomMap::GetMainDicomTags(tags, level); 378 379 for (std::set<DicomTag>::const_iterator 380 tag = tags.begin(); tag != tags.end(); ++tag) 381 { 382 if (registry_.find(*tag) == registry_.end()) 383 { 384 registry_[*tag] = TagInfo(level, DicomTagType_Main); 385 } 386 } 387 } 388 } 389 390 public: MainDicomTagsRegistry()391 MainDicomTagsRegistry() 392 { 393 LoadTags(ResourceType_Patient); 394 LoadTags(ResourceType_Study); 395 LoadTags(ResourceType_Series); 396 LoadTags(ResourceType_Instance); 397 } 398 LookupTag(ResourceType & level,DicomTagType & type,const DicomTag & tag) const399 void LookupTag(ResourceType& level, 400 DicomTagType& type, 401 const DicomTag& tag) const 402 { 403 Registry::const_iterator it = registry_.find(tag); 404 405 if (it == registry_.end()) 406 { 407 // Default values 408 level = ResourceType_Instance; 409 type = DicomTagType_Generic; 410 } 411 else 412 { 413 level = it->second.GetLevel(); 414 type = it->second.GetType(); 415 } 416 } 417 }; 418 419 LogChange(int64_t internalId,ChangeType changeType,ResourceType resourceType,const std::string & publicId)420 void StatelessDatabaseOperations::ReadWriteTransaction::LogChange(int64_t internalId, 421 ChangeType changeType, 422 ResourceType resourceType, 423 const std::string& publicId) 424 { 425 ServerIndexChange change(changeType, resourceType, publicId); 426 427 if (changeType <= ChangeType_INTERNAL_LastLogged) 428 { 429 transaction_.LogChange(internalId, change); 430 } 431 432 GetTransactionContext().SignalChange(change); 433 } 434 435 GetSeriesStatus(int64_t id,int64_t expectedNumberOfInstances)436 SeriesStatus StatelessDatabaseOperations::ReadOnlyTransaction::GetSeriesStatus(int64_t id, 437 int64_t expectedNumberOfInstances) 438 { 439 std::list<std::string> values; 440 transaction_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries); 441 442 std::set<int64_t> instances; 443 444 for (std::list<std::string>::const_iterator 445 it = values.begin(); it != values.end(); ++it) 446 { 447 int64_t index; 448 449 try 450 { 451 index = boost::lexical_cast<int64_t>(*it); 452 } 453 catch (boost::bad_lexical_cast&) 454 { 455 return SeriesStatus_Unknown; 456 } 457 458 if (!(index > 0 && index <= expectedNumberOfInstances)) 459 { 460 // Out-of-range instance index 461 return SeriesStatus_Inconsistent; 462 } 463 464 if (instances.find(index) != instances.end()) 465 { 466 // Twice the same instance index 467 return SeriesStatus_Inconsistent; 468 } 469 470 instances.insert(index); 471 } 472 473 if (static_cast<int64_t>(instances.size()) == expectedNumberOfInstances) 474 { 475 return SeriesStatus_Complete; 476 } 477 else 478 { 479 return SeriesStatus_Missing; 480 } 481 } 482 483 NormalizeLookup(std::vector<DatabaseConstraint> & target,const DatabaseLookup & source,ResourceType queryLevel) const484 void StatelessDatabaseOperations::NormalizeLookup(std::vector<DatabaseConstraint>& target, 485 const DatabaseLookup& source, 486 ResourceType queryLevel) const 487 { 488 assert(mainDicomTagsRegistry_.get() != NULL); 489 490 target.clear(); 491 target.reserve(source.GetConstraintsCount()); 492 493 for (size_t i = 0; i < source.GetConstraintsCount(); i++) 494 { 495 ResourceType level; 496 DicomTagType type; 497 498 mainDicomTagsRegistry_->LookupTag(level, type, source.GetConstraint(i).GetTag()); 499 500 if (type == DicomTagType_Identifier || 501 type == DicomTagType_Main) 502 { 503 // Use the fact that patient-level tags are copied at the study level 504 if (level == ResourceType_Patient && 505 queryLevel != ResourceType_Patient) 506 { 507 level = ResourceType_Study; 508 } 509 510 target.push_back(source.GetConstraint(i).ConvertToDatabaseConstraint(level, type)); 511 } 512 } 513 } 514 515 516 class StatelessDatabaseOperations::Transaction : public boost::noncopyable 517 { 518 private: 519 IDatabaseWrapper& db_; 520 std::unique_ptr<IDatabaseWrapper::ITransaction> transaction_; 521 std::unique_ptr<ITransactionContext> context_; 522 bool isCommitted_; 523 524 public: Transaction(IDatabaseWrapper & db,ITransactionContextFactory & factory,TransactionType type)525 Transaction(IDatabaseWrapper& db, 526 ITransactionContextFactory& factory, 527 TransactionType type) : 528 db_(db), 529 isCommitted_(false) 530 { 531 context_.reset(factory.Create()); 532 if (context_.get() == NULL) 533 { 534 throw OrthancException(ErrorCode_NullPointer); 535 } 536 537 transaction_.reset(db_.StartTransaction(type, *context_)); 538 if (transaction_.get() == NULL) 539 { 540 throw OrthancException(ErrorCode_NullPointer); 541 } 542 } 543 ~Transaction()544 ~Transaction() 545 { 546 if (!isCommitted_) 547 { 548 try 549 { 550 transaction_->Rollback(); 551 } 552 catch (OrthancException& e) 553 { 554 LOG(INFO) << "Cannot rollback transaction: " << e.What(); 555 } 556 } 557 } 558 GetDatabaseTransaction()559 IDatabaseWrapper::ITransaction& GetDatabaseTransaction() 560 { 561 assert(transaction_.get() != NULL); 562 return *transaction_; 563 } 564 Commit()565 void Commit() 566 { 567 if (isCommitted_) 568 { 569 throw OrthancException(ErrorCode_BadSequenceOfCalls); 570 } 571 else 572 { 573 int64_t delta = context_->GetCompressedSizeDelta(); 574 575 transaction_->Commit(delta); 576 context_->Commit(); 577 isCommitted_ = true; 578 } 579 } 580 GetContext() const581 ITransactionContext& GetContext() const 582 { 583 assert(context_.get() != NULL); 584 return *context_; 585 } 586 }; 587 588 ApplyInternal(IReadOnlyOperations * readOperations,IReadWriteOperations * writeOperations)589 void StatelessDatabaseOperations::ApplyInternal(IReadOnlyOperations* readOperations, 590 IReadWriteOperations* writeOperations) 591 { 592 boost::shared_lock<boost::shared_mutex> lock(mutex_); // To protect "factory_" and "maxRetries_" 593 594 if ((readOperations == NULL && writeOperations == NULL) || 595 (readOperations != NULL && writeOperations != NULL)) 596 { 597 throw OrthancException(ErrorCode_InternalError); 598 } 599 600 if (factory_.get() == NULL) 601 { 602 throw OrthancException(ErrorCode_BadSequenceOfCalls, "No transaction context was provided"); 603 } 604 605 unsigned int attempt = 0; 606 607 for (;;) 608 { 609 try 610 { 611 if (readOperations != NULL) 612 { 613 /** 614 * IMPORTANT: In Orthanc <= 1.9.1, there was no transaction 615 * in this case. This was OK because of the presence of the 616 * global mutex that was protecting the database. 617 **/ 618 619 Transaction transaction(db_, *factory_, TransactionType_ReadOnly); // TODO - Only if not "TransactionType_Implicit" 620 { 621 ReadOnlyTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext()); 622 readOperations->Apply(t); 623 } 624 transaction.Commit(); 625 } 626 else 627 { 628 assert(writeOperations != NULL); 629 630 Transaction transaction(db_, *factory_, TransactionType_ReadWrite); 631 { 632 ReadWriteTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext()); 633 writeOperations->Apply(t); 634 } 635 transaction.Commit(); 636 } 637 638 return; // Success 639 } 640 catch (OrthancException& e) 641 { 642 if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize) 643 { 644 if (attempt >= maxRetries_) 645 { 646 throw; 647 } 648 else 649 { 650 attempt++; 651 652 // The "rand()" adds some jitter to de-synchronize writers 653 boost::this_thread::sleep(boost::posix_time::milliseconds(100 * attempt + 5 * (rand() % 10))); 654 } 655 } 656 else 657 { 658 throw; 659 } 660 } 661 } 662 } 663 664 StatelessDatabaseOperations(IDatabaseWrapper & db)665 StatelessDatabaseOperations::StatelessDatabaseOperations(IDatabaseWrapper& db) : 666 db_(db), 667 mainDicomTagsRegistry_(new MainDicomTagsRegistry), 668 hasFlushToDisk_(db.HasFlushToDisk()), 669 maxRetries_(0) 670 { 671 } 672 673 FlushToDisk()674 void StatelessDatabaseOperations::FlushToDisk() 675 { 676 try 677 { 678 db_.FlushToDisk(); 679 } 680 catch (OrthancException&) 681 { 682 LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)"; 683 } 684 } 685 686 SetTransactionContextFactory(ITransactionContextFactory * factory)687 void StatelessDatabaseOperations::SetTransactionContextFactory(ITransactionContextFactory* factory) 688 { 689 boost::unique_lock<boost::shared_mutex> lock(mutex_); 690 691 if (factory == NULL) 692 { 693 throw OrthancException(ErrorCode_NullPointer); 694 } 695 else if (factory_.get() != NULL) 696 { 697 throw OrthancException(ErrorCode_BadSequenceOfCalls); 698 } 699 else 700 { 701 factory_.reset(factory); 702 } 703 } 704 705 SetMaxDatabaseRetries(unsigned int maxRetries)706 void StatelessDatabaseOperations::SetMaxDatabaseRetries(unsigned int maxRetries) 707 { 708 boost::unique_lock<boost::shared_mutex> lock(mutex_); 709 maxRetries_ = maxRetries; 710 } 711 712 Apply(IReadOnlyOperations & operations)713 void StatelessDatabaseOperations::Apply(IReadOnlyOperations& operations) 714 { 715 ApplyInternal(&operations, NULL); 716 } 717 718 Apply(IReadWriteOperations & operations)719 void StatelessDatabaseOperations::Apply(IReadWriteOperations& operations) 720 { 721 ApplyInternal(NULL, &operations); 722 } 723 724 ExpandResource(Json::Value & target,const std::string & publicId,ResourceType level,DicomToJsonFormat format)725 bool StatelessDatabaseOperations::ExpandResource(Json::Value& target, 726 const std::string& publicId, 727 ResourceType level, 728 DicomToJsonFormat format) 729 { 730 class Operations : public ReadOnlyOperationsT5< 731 bool&, Json::Value&, const std::string&, ResourceType, DicomToJsonFormat> 732 { 733 private: 734 static void MainDicomTagsToJson(ReadOnlyTransaction& transaction, 735 Json::Value& target, 736 int64_t resourceId, 737 ResourceType resourceType, 738 DicomToJsonFormat format) 739 { 740 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; 741 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; 742 743 DicomMap tags; 744 transaction.GetMainDicomTags(tags, resourceId); 745 746 if (resourceType == ResourceType_Study) 747 { 748 DicomMap t1, t2; 749 tags.ExtractStudyInformation(t1); 750 tags.ExtractPatientInformation(t2); 751 752 target[MAIN_DICOM_TAGS] = Json::objectValue; 753 FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], t1, format); 754 755 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; 756 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], t2, format); 757 } 758 else 759 { 760 target[MAIN_DICOM_TAGS] = Json::objectValue; 761 FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], tags, format); 762 } 763 } 764 765 766 static bool LookupStringMetadata(std::string& result, 767 const std::map<MetadataType, std::string>& metadata, 768 MetadataType type) 769 { 770 std::map<MetadataType, std::string>::const_iterator found = metadata.find(type); 771 772 if (found == metadata.end()) 773 { 774 return false; 775 } 776 else 777 { 778 result = found->second; 779 return true; 780 } 781 } 782 783 784 static bool LookupIntegerMetadata(int64_t& result, 785 const std::map<MetadataType, std::string>& metadata, 786 MetadataType type) 787 { 788 std::string s; 789 if (!LookupStringMetadata(s, metadata, type)) 790 { 791 return false; 792 } 793 794 try 795 { 796 result = boost::lexical_cast<int64_t>(s); 797 return true; 798 } 799 catch (boost::bad_lexical_cast&) 800 { 801 return false; 802 } 803 } 804 805 806 public: 807 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 808 const Tuple& tuple) ORTHANC_OVERRIDE 809 { 810 // Lookup for the requested resource 811 int64_t internalId; // unused 812 ResourceType type; 813 std::string parent; 814 if (!transaction.LookupResourceAndParent(internalId, type, parent, tuple.get<2>()) || 815 type != tuple.get<3>()) 816 { 817 tuple.get<0>() = false; 818 } 819 else 820 { 821 Json::Value& target = tuple.get<1>(); 822 target = Json::objectValue; 823 824 // Set information about the parent resource (if it exists) 825 if (type == ResourceType_Patient) 826 { 827 if (!parent.empty()) 828 { 829 throw OrthancException(ErrorCode_DatabasePlugin); 830 } 831 } 832 else 833 { 834 if (parent.empty()) 835 { 836 throw OrthancException(ErrorCode_DatabasePlugin); 837 } 838 839 switch (type) 840 { 841 case ResourceType_Study: 842 target["ParentPatient"] = parent; 843 break; 844 845 case ResourceType_Series: 846 target["ParentStudy"] = parent; 847 break; 848 849 case ResourceType_Instance: 850 target["ParentSeries"] = parent; 851 break; 852 853 default: 854 throw OrthancException(ErrorCode_InternalError); 855 } 856 } 857 858 // List the children resources 859 std::list<std::string> children; 860 transaction.GetChildrenPublicId(children, internalId); 861 862 if (type != ResourceType_Instance) 863 { 864 Json::Value c = Json::arrayValue; 865 866 for (std::list<std::string>::const_iterator 867 it = children.begin(); it != children.end(); ++it) 868 { 869 c.append(*it); 870 } 871 872 switch (type) 873 { 874 case ResourceType_Patient: 875 target["Studies"] = c; 876 break; 877 878 case ResourceType_Study: 879 target["Series"] = c; 880 break; 881 882 case ResourceType_Series: 883 target["Instances"] = c; 884 break; 885 886 default: 887 throw OrthancException(ErrorCode_InternalError); 888 } 889 } 890 891 // Extract the metadata 892 std::map<MetadataType, std::string> metadata; 893 transaction.GetAllMetadata(metadata, internalId); 894 895 // Set the resource type 896 switch (type) 897 { 898 case ResourceType_Patient: 899 target["Type"] = "Patient"; 900 break; 901 902 case ResourceType_Study: 903 target["Type"] = "Study"; 904 break; 905 906 case ResourceType_Series: 907 { 908 target["Type"] = "Series"; 909 910 int64_t i; 911 if (LookupIntegerMetadata(i, metadata, MetadataType_Series_ExpectedNumberOfInstances)) 912 { 913 target["ExpectedNumberOfInstances"] = static_cast<int>(i); 914 target["Status"] = EnumerationToString(transaction.GetSeriesStatus(internalId, i)); 915 } 916 else 917 { 918 target["ExpectedNumberOfInstances"] = Json::nullValue; 919 target["Status"] = EnumerationToString(SeriesStatus_Unknown); 920 } 921 922 break; 923 } 924 925 case ResourceType_Instance: 926 { 927 target["Type"] = "Instance"; 928 929 FileInfo attachment; 930 int64_t revision; // ignored 931 if (!transaction.LookupAttachment(attachment, revision, internalId, FileContentType_Dicom)) 932 { 933 throw OrthancException(ErrorCode_InternalError); 934 } 935 936 target["FileSize"] = static_cast<unsigned int>(attachment.GetUncompressedSize()); 937 target["FileUuid"] = attachment.GetUuid(); 938 939 int64_t i; 940 if (LookupIntegerMetadata(i, metadata, MetadataType_Instance_IndexInSeries)) 941 { 942 target["IndexInSeries"] = static_cast<int>(i); 943 } 944 else 945 { 946 target["IndexInSeries"] = Json::nullValue; 947 } 948 949 break; 950 } 951 952 default: 953 throw OrthancException(ErrorCode_InternalError); 954 } 955 956 // Record the remaining information 957 target["ID"] = tuple.get<2>(); 958 MainDicomTagsToJson(transaction, target, internalId, type, tuple.get<4>()); 959 960 std::string tmp; 961 962 if (LookupStringMetadata(tmp, metadata, MetadataType_AnonymizedFrom)) 963 { 964 target["AnonymizedFrom"] = tmp; 965 } 966 967 if (LookupStringMetadata(tmp, metadata, MetadataType_ModifiedFrom)) 968 { 969 target["ModifiedFrom"] = tmp; 970 } 971 972 if (type == ResourceType_Patient || 973 type == ResourceType_Study || 974 type == ResourceType_Series) 975 { 976 target["IsStable"] = !transaction.GetTransactionContext().IsUnstableResource(internalId); 977 978 if (LookupStringMetadata(tmp, metadata, MetadataType_LastUpdate)) 979 { 980 target["LastUpdate"] = tmp; 981 } 982 } 983 984 tuple.get<0>() = true; 985 } 986 } 987 }; 988 989 bool found; 990 Operations operations; 991 operations.Apply(*this, found, target, publicId, level, format); 992 return found; 993 } 994 995 GetAllMetadata(std::map<MetadataType,std::string> & target,const std::string & publicId,ResourceType level)996 void StatelessDatabaseOperations::GetAllMetadata(std::map<MetadataType, std::string>& target, 997 const std::string& publicId, 998 ResourceType level) 999 { 1000 class Operations : public ReadOnlyOperationsT3<std::map<MetadataType, std::string>&, const std::string&, ResourceType> 1001 { 1002 public: 1003 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1004 const Tuple& tuple) ORTHANC_OVERRIDE 1005 { 1006 ResourceType type; 1007 int64_t id; 1008 if (!transaction.LookupResource(id, type, tuple.get<1>()) || 1009 tuple.get<2>() != type) 1010 { 1011 throw OrthancException(ErrorCode_UnknownResource); 1012 } 1013 else 1014 { 1015 transaction.GetAllMetadata(tuple.get<0>(), id); 1016 } 1017 } 1018 }; 1019 1020 Operations operations; 1021 operations.Apply(*this, target, publicId, level); 1022 } 1023 1024 LookupAttachment(FileInfo & attachment,int64_t & revision,const std::string & instancePublicId,FileContentType contentType)1025 bool StatelessDatabaseOperations::LookupAttachment(FileInfo& attachment, 1026 int64_t& revision, 1027 const std::string& instancePublicId, 1028 FileContentType contentType) 1029 { 1030 class Operations : public ReadOnlyOperationsT5<bool&, FileInfo&, int64_t&, const std::string&, FileContentType> 1031 { 1032 public: 1033 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1034 const Tuple& tuple) ORTHANC_OVERRIDE 1035 { 1036 int64_t internalId; 1037 ResourceType type; 1038 if (!transaction.LookupResource(internalId, type, tuple.get<3>())) 1039 { 1040 throw OrthancException(ErrorCode_UnknownResource); 1041 } 1042 else if (transaction.LookupAttachment(tuple.get<1>(), tuple.get<2>(), internalId, tuple.get<4>())) 1043 { 1044 assert(tuple.get<1>().GetContentType() == tuple.get<4>()); 1045 tuple.get<0>() = true; 1046 } 1047 else 1048 { 1049 tuple.get<0>() = false; 1050 } 1051 } 1052 }; 1053 1054 bool found; 1055 Operations operations; 1056 operations.Apply(*this, found, attachment, revision, instancePublicId, contentType); 1057 return found; 1058 } 1059 1060 GetAllUuids(std::list<std::string> & target,ResourceType resourceType)1061 void StatelessDatabaseOperations::GetAllUuids(std::list<std::string>& target, 1062 ResourceType resourceType) 1063 { 1064 class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, ResourceType> 1065 { 1066 public: 1067 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1068 const Tuple& tuple) ORTHANC_OVERRIDE 1069 { 1070 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1071 transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>()); 1072 } 1073 }; 1074 1075 Operations operations; 1076 operations.Apply(*this, target, resourceType); 1077 } 1078 1079 GetAllUuids(std::list<std::string> & target,ResourceType resourceType,size_t since,size_t limit)1080 void StatelessDatabaseOperations::GetAllUuids(std::list<std::string>& target, 1081 ResourceType resourceType, 1082 size_t since, 1083 size_t limit) 1084 { 1085 if (limit == 0) 1086 { 1087 target.clear(); 1088 } 1089 else 1090 { 1091 class Operations : public ReadOnlyOperationsT4<std::list<std::string>&, ResourceType, size_t, size_t> 1092 { 1093 public: 1094 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1095 const Tuple& tuple) ORTHANC_OVERRIDE 1096 { 1097 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1098 transaction.GetAllPublicIds(tuple.get<0>(), tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); 1099 } 1100 }; 1101 1102 Operations operations; 1103 operations.Apply(*this, target, resourceType, since, limit); 1104 } 1105 } 1106 1107 GetGlobalStatistics(uint64_t & diskSize,uint64_t & uncompressedSize,uint64_t & countPatients,uint64_t & countStudies,uint64_t & countSeries,uint64_t & countInstances)1108 void StatelessDatabaseOperations::GetGlobalStatistics(/* out */ uint64_t& diskSize, 1109 /* out */ uint64_t& uncompressedSize, 1110 /* out */ uint64_t& countPatients, 1111 /* out */ uint64_t& countStudies, 1112 /* out */ uint64_t& countSeries, 1113 /* out */ uint64_t& countInstances) 1114 { 1115 class Operations : public ReadOnlyOperationsT6<uint64_t&, uint64_t&, uint64_t&, uint64_t&, uint64_t&, uint64_t&> 1116 { 1117 public: 1118 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1119 const Tuple& tuple) ORTHANC_OVERRIDE 1120 { 1121 tuple.get<0>() = transaction.GetTotalCompressedSize(); 1122 tuple.get<1>() = transaction.GetTotalUncompressedSize(); 1123 tuple.get<2>() = transaction.GetResourcesCount(ResourceType_Patient); 1124 tuple.get<3>() = transaction.GetResourcesCount(ResourceType_Study); 1125 tuple.get<4>() = transaction.GetResourcesCount(ResourceType_Series); 1126 tuple.get<5>() = transaction.GetResourcesCount(ResourceType_Instance); 1127 } 1128 }; 1129 1130 Operations operations; 1131 operations.Apply(*this, diskSize, uncompressedSize, countPatients, 1132 countStudies, countSeries, countInstances); 1133 } 1134 1135 GetChanges(Json::Value & target,int64_t since,unsigned int maxResults)1136 void StatelessDatabaseOperations::GetChanges(Json::Value& target, 1137 int64_t since, 1138 unsigned int maxResults) 1139 { 1140 class Operations : public ReadOnlyOperationsT3<Json::Value&, int64_t, unsigned int> 1141 { 1142 public: 1143 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1144 const Tuple& tuple) ORTHANC_OVERRIDE 1145 { 1146 // NB: In Orthanc <= 1.3.2, a transaction was missing, as 1147 // "GetLastChange()" involves calls to "GetPublicId()" 1148 1149 std::list<ServerIndexChange> changes; 1150 bool done; 1151 bool hasLast = false; 1152 int64_t last = 0; 1153 1154 transaction.GetChanges(changes, done, tuple.get<1>(), tuple.get<2>()); 1155 if (changes.empty()) 1156 { 1157 last = transaction.GetLastChangeIndex(); 1158 hasLast = true; 1159 } 1160 1161 FormatLog(tuple.get<0>(), changes, "Changes", done, tuple.get<1>(), hasLast, last); 1162 } 1163 }; 1164 1165 Operations operations; 1166 operations.Apply(*this, target, since, maxResults); 1167 } 1168 1169 GetLastChange(Json::Value & target)1170 void StatelessDatabaseOperations::GetLastChange(Json::Value& target) 1171 { 1172 class Operations : public ReadOnlyOperationsT1<Json::Value&> 1173 { 1174 public: 1175 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1176 const Tuple& tuple) ORTHANC_OVERRIDE 1177 { 1178 // NB: In Orthanc <= 1.3.2, a transaction was missing, as 1179 // "GetLastChange()" involves calls to "GetPublicId()" 1180 1181 std::list<ServerIndexChange> changes; 1182 bool hasLast = false; 1183 int64_t last = 0; 1184 1185 transaction.GetLastChange(changes); 1186 if (changes.empty()) 1187 { 1188 last = transaction.GetLastChangeIndex(); 1189 hasLast = true; 1190 } 1191 1192 FormatLog(tuple.get<0>(), changes, "Changes", true, 0, hasLast, last); 1193 } 1194 }; 1195 1196 Operations operations; 1197 operations.Apply(*this, target); 1198 } 1199 1200 GetExportedResources(Json::Value & target,int64_t since,unsigned int maxResults)1201 void StatelessDatabaseOperations::GetExportedResources(Json::Value& target, 1202 int64_t since, 1203 unsigned int maxResults) 1204 { 1205 class Operations : public ReadOnlyOperationsT3<Json::Value&, int64_t, unsigned int> 1206 { 1207 public: 1208 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1209 const Tuple& tuple) ORTHANC_OVERRIDE 1210 { 1211 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1212 1213 std::list<ExportedResource> exported; 1214 bool done; 1215 transaction.GetExportedResources(exported, done, tuple.get<1>(), tuple.get<2>()); 1216 FormatLog(tuple.get<0>(), exported, "Exports", done, tuple.get<1>(), false, -1); 1217 } 1218 }; 1219 1220 Operations operations; 1221 operations.Apply(*this, target, since, maxResults); 1222 } 1223 1224 GetLastExportedResource(Json::Value & target)1225 void StatelessDatabaseOperations::GetLastExportedResource(Json::Value& target) 1226 { 1227 class Operations : public ReadOnlyOperationsT1<Json::Value&> 1228 { 1229 public: 1230 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1231 const Tuple& tuple) ORTHANC_OVERRIDE 1232 { 1233 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1234 1235 std::list<ExportedResource> exported; 1236 transaction.GetLastExportedResource(exported); 1237 FormatLog(tuple.get<0>(), exported, "Exports", true, 0, false, -1); 1238 } 1239 }; 1240 1241 Operations operations; 1242 operations.Apply(*this, target); 1243 } 1244 1245 IsProtectedPatient(const std::string & publicId)1246 bool StatelessDatabaseOperations::IsProtectedPatient(const std::string& publicId) 1247 { 1248 class Operations : public ReadOnlyOperationsT2<bool&, const std::string&> 1249 { 1250 public: 1251 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1252 const Tuple& tuple) ORTHANC_OVERRIDE 1253 { 1254 // Lookup for the requested resource 1255 int64_t id; 1256 ResourceType type; 1257 if (!transaction.LookupResource(id, type, tuple.get<1>()) || 1258 type != ResourceType_Patient) 1259 { 1260 throw OrthancException(ErrorCode_ParameterOutOfRange); 1261 } 1262 else 1263 { 1264 tuple.get<0>() = transaction.IsProtectedPatient(id); 1265 } 1266 } 1267 }; 1268 1269 bool isProtected; 1270 Operations operations; 1271 operations.Apply(*this, isProtected, publicId); 1272 return isProtected; 1273 } 1274 1275 GetChildren(std::list<std::string> & result,const std::string & publicId)1276 void StatelessDatabaseOperations::GetChildren(std::list<std::string>& result, 1277 const std::string& publicId) 1278 { 1279 class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&> 1280 { 1281 public: 1282 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1283 const Tuple& tuple) ORTHANC_OVERRIDE 1284 { 1285 ResourceType type; 1286 int64_t resource; 1287 if (!transaction.LookupResource(resource, type, tuple.get<1>())) 1288 { 1289 throw OrthancException(ErrorCode_UnknownResource); 1290 } 1291 else if (type == ResourceType_Instance) 1292 { 1293 // An instance cannot have a child 1294 throw OrthancException(ErrorCode_BadParameterType); 1295 } 1296 else 1297 { 1298 std::list<int64_t> tmp; 1299 transaction.GetChildrenInternalId(tmp, resource); 1300 1301 tuple.get<0>().clear(); 1302 1303 for (std::list<int64_t>::const_iterator 1304 it = tmp.begin(); it != tmp.end(); ++it) 1305 { 1306 tuple.get<0>().push_back(transaction.GetPublicId(*it)); 1307 } 1308 } 1309 } 1310 }; 1311 1312 Operations operations; 1313 operations.Apply(*this, result, publicId); 1314 } 1315 1316 GetChildInstances(std::list<std::string> & result,const std::string & publicId)1317 void StatelessDatabaseOperations::GetChildInstances(std::list<std::string>& result, 1318 const std::string& publicId) 1319 { 1320 class Operations : public ReadOnlyOperationsT2<std::list<std::string>&, const std::string&> 1321 { 1322 public: 1323 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1324 const Tuple& tuple) ORTHANC_OVERRIDE 1325 { 1326 tuple.get<0>().clear(); 1327 1328 ResourceType type; 1329 int64_t top; 1330 if (!transaction.LookupResource(top, type, tuple.get<1>())) 1331 { 1332 throw OrthancException(ErrorCode_UnknownResource); 1333 } 1334 else if (type == ResourceType_Instance) 1335 { 1336 // The resource is already an instance: Do not go down the hierarchy 1337 tuple.get<0>().push_back(tuple.get<1>()); 1338 } 1339 else 1340 { 1341 std::stack<int64_t> toExplore; 1342 toExplore.push(top); 1343 1344 std::list<int64_t> tmp; 1345 while (!toExplore.empty()) 1346 { 1347 // Get the internal ID of the current resource 1348 int64_t resource = toExplore.top(); 1349 toExplore.pop(); 1350 1351 // TODO - This could be optimized by seeing how many 1352 // levels "type == transaction.GetResourceType(top)" is 1353 // above the "instances level" 1354 if (transaction.GetResourceType(resource) == ResourceType_Instance) 1355 { 1356 tuple.get<0>().push_back(transaction.GetPublicId(resource)); 1357 } 1358 else 1359 { 1360 // Tag all the children of this resource as to be explored 1361 transaction.GetChildrenInternalId(tmp, resource); 1362 for (std::list<int64_t>::const_iterator 1363 it = tmp.begin(); it != tmp.end(); ++it) 1364 { 1365 toExplore.push(*it); 1366 } 1367 } 1368 } 1369 } 1370 } 1371 }; 1372 1373 Operations operations; 1374 operations.Apply(*this, result, publicId); 1375 } 1376 1377 LookupMetadata(std::string & target,int64_t & revision,const std::string & publicId,ResourceType expectedType,MetadataType type)1378 bool StatelessDatabaseOperations::LookupMetadata(std::string& target, 1379 int64_t& revision, 1380 const std::string& publicId, 1381 ResourceType expectedType, 1382 MetadataType type) 1383 { 1384 class Operations : public ReadOnlyOperationsT6<bool&, std::string&, int64_t&, 1385 const std::string&, ResourceType, MetadataType> 1386 { 1387 public: 1388 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1389 const Tuple& tuple) ORTHANC_OVERRIDE 1390 { 1391 ResourceType resourceType; 1392 int64_t id; 1393 if (!transaction.LookupResource(id, resourceType, tuple.get<3>()) || 1394 resourceType != tuple.get<4>()) 1395 { 1396 throw OrthancException(ErrorCode_UnknownResource); 1397 } 1398 else 1399 { 1400 tuple.get<0>() = transaction.LookupMetadata(tuple.get<1>(), tuple.get<2>(), id, tuple.get<5>()); 1401 } 1402 } 1403 }; 1404 1405 bool found; 1406 Operations operations; 1407 operations.Apply(*this, found, target, revision, publicId, expectedType, type); 1408 return found; 1409 } 1410 1411 ListAvailableAttachments(std::set<FileContentType> & target,const std::string & publicId,ResourceType expectedType)1412 void StatelessDatabaseOperations::ListAvailableAttachments(std::set<FileContentType>& target, 1413 const std::string& publicId, 1414 ResourceType expectedType) 1415 { 1416 class Operations : public ReadOnlyOperationsT3<std::set<FileContentType>&, const std::string&, ResourceType> 1417 { 1418 public: 1419 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1420 const Tuple& tuple) ORTHANC_OVERRIDE 1421 { 1422 ResourceType type; 1423 int64_t id; 1424 if (!transaction.LookupResource(id, type, tuple.get<1>()) || 1425 tuple.get<2>() != type) 1426 { 1427 throw OrthancException(ErrorCode_UnknownResource); 1428 } 1429 else 1430 { 1431 transaction.ListAvailableAttachments(tuple.get<0>(), id); 1432 } 1433 } 1434 }; 1435 1436 Operations operations; 1437 operations.Apply(*this, target, publicId, expectedType); 1438 } 1439 1440 LookupParent(std::string & target,const std::string & publicId)1441 bool StatelessDatabaseOperations::LookupParent(std::string& target, 1442 const std::string& publicId) 1443 { 1444 class Operations : public ReadOnlyOperationsT3<bool&, std::string&, const std::string&> 1445 { 1446 public: 1447 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1448 const Tuple& tuple) ORTHANC_OVERRIDE 1449 { 1450 ResourceType type; 1451 int64_t id; 1452 if (!transaction.LookupResource(id, type, tuple.get<2>())) 1453 { 1454 throw OrthancException(ErrorCode_UnknownResource); 1455 } 1456 else 1457 { 1458 int64_t parentId; 1459 if (transaction.LookupParent(parentId, id)) 1460 { 1461 tuple.get<1>() = transaction.GetPublicId(parentId); 1462 tuple.get<0>() = true; 1463 } 1464 else 1465 { 1466 tuple.get<0>() = false; 1467 } 1468 } 1469 } 1470 }; 1471 1472 bool found; 1473 Operations operations; 1474 operations.Apply(*this, found, target, publicId); 1475 return found; 1476 } 1477 1478 GetResourceStatistics(ResourceType & type,uint64_t & diskSize,uint64_t & uncompressedSize,unsigned int & countStudies,unsigned int & countSeries,unsigned int & countInstances,uint64_t & dicomDiskSize,uint64_t & dicomUncompressedSize,const std::string & publicId)1479 void StatelessDatabaseOperations::GetResourceStatistics(/* out */ ResourceType& type, 1480 /* out */ uint64_t& diskSize, 1481 /* out */ uint64_t& uncompressedSize, 1482 /* out */ unsigned int& countStudies, 1483 /* out */ unsigned int& countSeries, 1484 /* out */ unsigned int& countInstances, 1485 /* out */ uint64_t& dicomDiskSize, 1486 /* out */ uint64_t& dicomUncompressedSize, 1487 const std::string& publicId) 1488 { 1489 class Operations : public IReadOnlyOperations 1490 { 1491 private: 1492 ResourceType& type_; 1493 uint64_t& diskSize_; 1494 uint64_t& uncompressedSize_; 1495 unsigned int& countStudies_; 1496 unsigned int& countSeries_; 1497 unsigned int& countInstances_; 1498 uint64_t& dicomDiskSize_; 1499 uint64_t& dicomUncompressedSize_; 1500 const std::string& publicId_; 1501 1502 public: 1503 explicit Operations(ResourceType& type, 1504 uint64_t& diskSize, 1505 uint64_t& uncompressedSize, 1506 unsigned int& countStudies, 1507 unsigned int& countSeries, 1508 unsigned int& countInstances, 1509 uint64_t& dicomDiskSize, 1510 uint64_t& dicomUncompressedSize, 1511 const std::string& publicId) : 1512 type_(type), 1513 diskSize_(diskSize), 1514 uncompressedSize_(uncompressedSize), 1515 countStudies_(countStudies), 1516 countSeries_(countSeries), 1517 countInstances_(countInstances), 1518 dicomDiskSize_(dicomDiskSize), 1519 dicomUncompressedSize_(dicomUncompressedSize), 1520 publicId_(publicId) 1521 { 1522 } 1523 1524 virtual void Apply(ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE 1525 { 1526 int64_t top; 1527 if (!transaction.LookupResource(top, type_, publicId_)) 1528 { 1529 throw OrthancException(ErrorCode_UnknownResource); 1530 } 1531 else 1532 { 1533 countInstances_ = 0; 1534 countSeries_ = 0; 1535 countStudies_ = 0; 1536 diskSize_ = 0; 1537 uncompressedSize_ = 0; 1538 dicomDiskSize_ = 0; 1539 dicomUncompressedSize_ = 0; 1540 1541 std::stack<int64_t> toExplore; 1542 toExplore.push(top); 1543 1544 while (!toExplore.empty()) 1545 { 1546 // Get the internal ID of the current resource 1547 int64_t resource = toExplore.top(); 1548 toExplore.pop(); 1549 1550 ResourceType thisType = transaction.GetResourceType(resource); 1551 1552 std::set<FileContentType> f; 1553 transaction.ListAvailableAttachments(f, resource); 1554 1555 for (std::set<FileContentType>::const_iterator 1556 it = f.begin(); it != f.end(); ++it) 1557 { 1558 FileInfo attachment; 1559 int64_t revision; // ignored 1560 if (transaction.LookupAttachment(attachment, revision, resource, *it)) 1561 { 1562 if (attachment.GetContentType() == FileContentType_Dicom) 1563 { 1564 dicomDiskSize_ += attachment.GetCompressedSize(); 1565 dicomUncompressedSize_ += attachment.GetUncompressedSize(); 1566 } 1567 1568 diskSize_ += attachment.GetCompressedSize(); 1569 uncompressedSize_ += attachment.GetUncompressedSize(); 1570 } 1571 } 1572 1573 if (thisType == ResourceType_Instance) 1574 { 1575 countInstances_++; 1576 } 1577 else 1578 { 1579 switch (thisType) 1580 { 1581 case ResourceType_Study: 1582 countStudies_++; 1583 break; 1584 1585 case ResourceType_Series: 1586 countSeries_++; 1587 break; 1588 1589 default: 1590 break; 1591 } 1592 1593 // Tag all the children of this resource as to be explored 1594 std::list<int64_t> tmp; 1595 transaction.GetChildrenInternalId(tmp, resource); 1596 for (std::list<int64_t>::const_iterator 1597 it = tmp.begin(); it != tmp.end(); ++it) 1598 { 1599 toExplore.push(*it); 1600 } 1601 } 1602 } 1603 1604 if (countStudies_ == 0) 1605 { 1606 countStudies_ = 1; 1607 } 1608 1609 if (countSeries_ == 0) 1610 { 1611 countSeries_ = 1; 1612 } 1613 } 1614 } 1615 }; 1616 1617 Operations operations(type, diskSize, uncompressedSize, countStudies, countSeries, 1618 countInstances, dicomDiskSize, dicomUncompressedSize, publicId); 1619 Apply(operations); 1620 } 1621 1622 LookupIdentifierExact(std::vector<std::string> & result,ResourceType level,const DicomTag & tag,const std::string & value)1623 void StatelessDatabaseOperations::LookupIdentifierExact(std::vector<std::string>& result, 1624 ResourceType level, 1625 const DicomTag& tag, 1626 const std::string& value) 1627 { 1628 assert((level == ResourceType_Patient && tag == DICOM_TAG_PATIENT_ID) || 1629 (level == ResourceType_Study && tag == DICOM_TAG_STUDY_INSTANCE_UID) || 1630 (level == ResourceType_Study && tag == DICOM_TAG_ACCESSION_NUMBER) || 1631 (level == ResourceType_Series && tag == DICOM_TAG_SERIES_INSTANCE_UID) || 1632 (level == ResourceType_Instance && tag == DICOM_TAG_SOP_INSTANCE_UID)); 1633 1634 result.clear(); 1635 1636 DicomTagConstraint c(tag, ConstraintType_Equal, value, true, true); 1637 1638 std::vector<DatabaseConstraint> query; 1639 query.push_back(c.ConvertToDatabaseConstraint(level, DicomTagType_Identifier)); 1640 1641 1642 class Operations : public IReadOnlyOperations 1643 { 1644 private: 1645 std::vector<std::string>& result_; 1646 const std::vector<DatabaseConstraint>& query_; 1647 ResourceType level_; 1648 1649 public: 1650 Operations(std::vector<std::string>& result, 1651 const std::vector<DatabaseConstraint>& query, 1652 ResourceType level) : 1653 result_(result), 1654 query_(query), 1655 level_(level) 1656 { 1657 } 1658 1659 virtual void Apply(ReadOnlyTransaction& transaction) ORTHANC_OVERRIDE 1660 { 1661 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1662 std::list<std::string> tmp; 1663 transaction.ApplyLookupResources(tmp, NULL, query_, level_, 0); 1664 CopyListToVector(result_, tmp); 1665 } 1666 }; 1667 1668 Operations operations(result, query, level); 1669 Apply(operations); 1670 } 1671 1672 LookupGlobalProperty(std::string & value,GlobalProperty property,bool shared)1673 bool StatelessDatabaseOperations::LookupGlobalProperty(std::string& value, 1674 GlobalProperty property, 1675 bool shared) 1676 { 1677 class Operations : public ReadOnlyOperationsT4<bool&, std::string&, GlobalProperty, bool> 1678 { 1679 public: 1680 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1681 const Tuple& tuple) ORTHANC_OVERRIDE 1682 { 1683 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1684 tuple.get<0>() = transaction.LookupGlobalProperty(tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); 1685 } 1686 }; 1687 1688 bool found; 1689 Operations operations; 1690 operations.Apply(*this, found, value, property, shared); 1691 return found; 1692 } 1693 1694 GetGlobalProperty(GlobalProperty property,bool shared,const std::string & defaultValue)1695 std::string StatelessDatabaseOperations::GetGlobalProperty(GlobalProperty property, 1696 bool shared, 1697 const std::string& defaultValue) 1698 { 1699 std::string s; 1700 if (LookupGlobalProperty(s, property, shared)) 1701 { 1702 return s; 1703 } 1704 else 1705 { 1706 return defaultValue; 1707 } 1708 } 1709 1710 GetMainDicomTags(DicomMap & result,const std::string & publicId,ResourceType expectedType,ResourceType levelOfInterest)1711 bool StatelessDatabaseOperations::GetMainDicomTags(DicomMap& result, 1712 const std::string& publicId, 1713 ResourceType expectedType, 1714 ResourceType levelOfInterest) 1715 { 1716 // Yes, the following test could be shortened, but we wish to make it as clear as possible 1717 if (!(expectedType == ResourceType_Patient && levelOfInterest == ResourceType_Patient) && 1718 !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Patient) && 1719 !(expectedType == ResourceType_Study && levelOfInterest == ResourceType_Study) && 1720 !(expectedType == ResourceType_Series && levelOfInterest == ResourceType_Series) && 1721 !(expectedType == ResourceType_Instance && levelOfInterest == ResourceType_Instance)) 1722 { 1723 throw OrthancException(ErrorCode_ParameterOutOfRange); 1724 } 1725 1726 1727 class Operations : public ReadOnlyOperationsT5<bool&, DicomMap&, const std::string&, ResourceType, ResourceType> 1728 { 1729 public: 1730 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1731 const Tuple& tuple) ORTHANC_OVERRIDE 1732 { 1733 // Lookup for the requested resource 1734 int64_t id; 1735 ResourceType type; 1736 if (!transaction.LookupResource(id, type, tuple.get<2>()) || 1737 type != tuple.get<3>()) 1738 { 1739 tuple.get<0>() = false; 1740 } 1741 else if (type == ResourceType_Study) 1742 { 1743 DicomMap tmp; 1744 transaction.GetMainDicomTags(tmp, id); 1745 1746 switch (tuple.get<4>()) 1747 { 1748 case ResourceType_Patient: 1749 tmp.ExtractPatientInformation(tuple.get<1>()); 1750 tuple.get<0>() = true; 1751 break; 1752 1753 case ResourceType_Study: 1754 tmp.ExtractStudyInformation(tuple.get<1>()); 1755 tuple.get<0>() = true; 1756 break; 1757 1758 default: 1759 throw OrthancException(ErrorCode_InternalError); 1760 } 1761 } 1762 else 1763 { 1764 transaction.GetMainDicomTags(tuple.get<1>(), id); 1765 tuple.get<0>() = true; 1766 } 1767 } 1768 }; 1769 1770 result.Clear(); 1771 1772 bool found; 1773 Operations operations; 1774 operations.Apply(*this, found, result, publicId, expectedType, levelOfInterest); 1775 return found; 1776 } 1777 1778 GetAllMainDicomTags(DicomMap & result,const std::string & instancePublicId)1779 bool StatelessDatabaseOperations::GetAllMainDicomTags(DicomMap& result, 1780 const std::string& instancePublicId) 1781 { 1782 class Operations : public ReadOnlyOperationsT3<bool&, DicomMap&, const std::string&> 1783 { 1784 public: 1785 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1786 const Tuple& tuple) ORTHANC_OVERRIDE 1787 { 1788 // Lookup for the requested resource 1789 int64_t instance; 1790 ResourceType type; 1791 if (!transaction.LookupResource(instance, type, tuple.get<2>()) || 1792 type != ResourceType_Instance) 1793 { 1794 tuple.get<0>() = false; 1795 } 1796 else 1797 { 1798 DicomMap tmp; 1799 1800 transaction.GetMainDicomTags(tmp, instance); 1801 tuple.get<1>().Merge(tmp); 1802 1803 int64_t series; 1804 if (!transaction.LookupParent(series, instance)) 1805 { 1806 throw OrthancException(ErrorCode_InternalError); 1807 } 1808 1809 tmp.Clear(); 1810 transaction.GetMainDicomTags(tmp, series); 1811 tuple.get<1>().Merge(tmp); 1812 1813 int64_t study; 1814 if (!transaction.LookupParent(study, series)) 1815 { 1816 throw OrthancException(ErrorCode_InternalError); 1817 } 1818 1819 tmp.Clear(); 1820 transaction.GetMainDicomTags(tmp, study); 1821 tuple.get<1>().Merge(tmp); 1822 1823 #ifndef NDEBUG 1824 { 1825 // Sanity test to check that all the main DICOM tags from the 1826 // patient level are copied at the study level 1827 1828 int64_t patient; 1829 if (!transaction.LookupParent(patient, study)) 1830 { 1831 throw OrthancException(ErrorCode_InternalError); 1832 } 1833 1834 tmp.Clear(); 1835 transaction.GetMainDicomTags(tmp, study); 1836 1837 std::set<DicomTag> patientTags; 1838 tmp.GetTags(patientTags); 1839 1840 for (std::set<DicomTag>::const_iterator 1841 it = patientTags.begin(); it != patientTags.end(); ++it) 1842 { 1843 assert(tuple.get<1>().HasTag(*it)); 1844 } 1845 } 1846 #endif 1847 1848 tuple.get<0>() = true; 1849 } 1850 } 1851 }; 1852 1853 result.Clear(); 1854 1855 bool found; 1856 Operations operations; 1857 operations.Apply(*this, found, result, instancePublicId); 1858 return found; 1859 } 1860 1861 LookupResourceType(ResourceType & type,const std::string & publicId)1862 bool StatelessDatabaseOperations::LookupResourceType(ResourceType& type, 1863 const std::string& publicId) 1864 { 1865 class Operations : public ReadOnlyOperationsT3<bool&, ResourceType&, const std::string&> 1866 { 1867 public: 1868 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1869 const Tuple& tuple) ORTHANC_OVERRIDE 1870 { 1871 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1872 int64_t id; 1873 tuple.get<0>() = transaction.LookupResource(id, tuple.get<1>(), tuple.get<2>()); 1874 } 1875 }; 1876 1877 bool found; 1878 Operations operations; 1879 operations.Apply(*this, found, type, publicId); 1880 return found; 1881 } 1882 1883 LookupParent(std::string & target,const std::string & publicId,ResourceType parentType)1884 bool StatelessDatabaseOperations::LookupParent(std::string& target, 1885 const std::string& publicId, 1886 ResourceType parentType) 1887 { 1888 class Operations : public ReadOnlyOperationsT4<bool&, std::string&, const std::string&, ResourceType> 1889 { 1890 public: 1891 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1892 const Tuple& tuple) ORTHANC_OVERRIDE 1893 { 1894 ResourceType type; 1895 int64_t id; 1896 if (!transaction.LookupResource(id, type, tuple.get<2>())) 1897 { 1898 throw OrthancException(ErrorCode_UnknownResource); 1899 } 1900 1901 while (type != tuple.get<3>()) 1902 { 1903 int64_t parentId; 1904 1905 if (type == ResourceType_Patient || // Cannot further go up in hierarchy 1906 !transaction.LookupParent(parentId, id)) 1907 { 1908 tuple.get<0>() = false; 1909 return; 1910 } 1911 1912 id = parentId; 1913 type = GetParentResourceType(type); 1914 } 1915 1916 tuple.get<0>() = true; 1917 tuple.get<1>() = transaction.GetPublicId(id); 1918 } 1919 }; 1920 1921 bool found; 1922 Operations operations; 1923 operations.Apply(*this, found, target, publicId, parentType); 1924 return found; 1925 } 1926 1927 ApplyLookupResources(std::vector<std::string> & resourcesId,std::vector<std::string> * instancesId,const DatabaseLookup & lookup,ResourceType queryLevel,size_t limit)1928 void StatelessDatabaseOperations::ApplyLookupResources(std::vector<std::string>& resourcesId, 1929 std::vector<std::string>* instancesId, 1930 const DatabaseLookup& lookup, 1931 ResourceType queryLevel, 1932 size_t limit) 1933 { 1934 class Operations : public ReadOnlyOperationsT4<bool, const std::vector<DatabaseConstraint>&, ResourceType, size_t> 1935 { 1936 private: 1937 std::list<std::string> resourcesList_; 1938 std::list<std::string> instancesList_; 1939 1940 public: 1941 const std::list<std::string>& GetResourcesList() const 1942 { 1943 return resourcesList_; 1944 } 1945 1946 const std::list<std::string>& GetInstancesList() const 1947 { 1948 return instancesList_; 1949 } 1950 1951 virtual void ApplyTuple(ReadOnlyTransaction& transaction, 1952 const Tuple& tuple) ORTHANC_OVERRIDE 1953 { 1954 // TODO - CANDIDATE FOR "TransactionType_Implicit" 1955 if (tuple.get<0>()) 1956 { 1957 transaction.ApplyLookupResources(resourcesList_, &instancesList_, tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); 1958 } 1959 else 1960 { 1961 transaction.ApplyLookupResources(resourcesList_, NULL, tuple.get<1>(), tuple.get<2>(), tuple.get<3>()); 1962 } 1963 } 1964 }; 1965 1966 1967 std::vector<DatabaseConstraint> normalized; 1968 NormalizeLookup(normalized, lookup, queryLevel); 1969 1970 Operations operations; 1971 operations.Apply(*this, (instancesId != NULL), normalized, queryLevel, limit); 1972 1973 CopyListToVector(resourcesId, operations.GetResourcesList()); 1974 1975 if (instancesId != NULL) 1976 { 1977 CopyListToVector(*instancesId, operations.GetInstancesList()); 1978 } 1979 } 1980 1981 DeleteResource(Json::Value & remainingAncestor,const std::string & uuid,ResourceType expectedType)1982 bool StatelessDatabaseOperations::DeleteResource(Json::Value& remainingAncestor, 1983 const std::string& uuid, 1984 ResourceType expectedType) 1985 { 1986 class Operations : public IReadWriteOperations 1987 { 1988 private: 1989 bool found_; 1990 Json::Value& remainingAncestor_; 1991 const std::string& uuid_; 1992 ResourceType expectedType_; 1993 1994 public: 1995 Operations(Json::Value& remainingAncestor, 1996 const std::string& uuid, 1997 ResourceType expectedType) : 1998 found_(false), 1999 remainingAncestor_(remainingAncestor), 2000 uuid_(uuid), 2001 expectedType_(expectedType) 2002 { 2003 } 2004 2005 bool IsFound() const 2006 { 2007 return found_; 2008 } 2009 2010 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2011 { 2012 int64_t id; 2013 ResourceType type; 2014 if (!transaction.LookupResource(id, type, uuid_) || 2015 expectedType_ != type) 2016 { 2017 found_ = false; 2018 } 2019 else 2020 { 2021 found_ = true; 2022 transaction.DeleteResource(id); 2023 2024 std::string remainingPublicId; 2025 ResourceType remainingLevel; 2026 if (transaction.GetTransactionContext().LookupRemainingLevel(remainingPublicId, remainingLevel)) 2027 { 2028 remainingAncestor_["RemainingAncestor"] = Json::Value(Json::objectValue); 2029 remainingAncestor_["RemainingAncestor"]["Path"] = GetBasePath(remainingLevel, remainingPublicId); 2030 remainingAncestor_["RemainingAncestor"]["Type"] = EnumerationToString(remainingLevel); 2031 remainingAncestor_["RemainingAncestor"]["ID"] = remainingPublicId; 2032 } 2033 else 2034 { 2035 remainingAncestor_["RemainingAncestor"] = Json::nullValue; 2036 } 2037 } 2038 } 2039 }; 2040 2041 Operations operations(remainingAncestor, uuid, expectedType); 2042 Apply(operations); 2043 return operations.IsFound(); 2044 } 2045 2046 LogExportedResource(const std::string & publicId,const std::string & remoteModality)2047 void StatelessDatabaseOperations::LogExportedResource(const std::string& publicId, 2048 const std::string& remoteModality) 2049 { 2050 class Operations : public IReadWriteOperations 2051 { 2052 private: 2053 const std::string& publicId_; 2054 const std::string& remoteModality_; 2055 2056 public: 2057 Operations(const std::string& publicId, 2058 const std::string& remoteModality) : 2059 publicId_(publicId), 2060 remoteModality_(remoteModality) 2061 { 2062 } 2063 2064 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2065 { 2066 int64_t id; 2067 ResourceType type; 2068 if (!transaction.LookupResource(id, type, publicId_)) 2069 { 2070 throw OrthancException(ErrorCode_InexistentItem); 2071 } 2072 2073 std::string patientId; 2074 std::string studyInstanceUid; 2075 std::string seriesInstanceUid; 2076 std::string sopInstanceUid; 2077 2078 int64_t currentId = id; 2079 ResourceType currentType = type; 2080 2081 // Iteratively go up inside the patient/study/series/instance hierarchy 2082 bool done = false; 2083 while (!done) 2084 { 2085 DicomMap map; 2086 transaction.GetMainDicomTags(map, currentId); 2087 2088 switch (currentType) 2089 { 2090 case ResourceType_Patient: 2091 if (map.HasTag(DICOM_TAG_PATIENT_ID)) 2092 { 2093 patientId = map.GetValue(DICOM_TAG_PATIENT_ID).GetContent(); 2094 } 2095 done = true; 2096 break; 2097 2098 case ResourceType_Study: 2099 if (map.HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) 2100 { 2101 studyInstanceUid = map.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(); 2102 } 2103 currentType = ResourceType_Patient; 2104 break; 2105 2106 case ResourceType_Series: 2107 if (map.HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) 2108 { 2109 seriesInstanceUid = map.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(); 2110 } 2111 currentType = ResourceType_Study; 2112 break; 2113 2114 case ResourceType_Instance: 2115 if (map.HasTag(DICOM_TAG_SOP_INSTANCE_UID)) 2116 { 2117 sopInstanceUid = map.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent(); 2118 } 2119 currentType = ResourceType_Series; 2120 break; 2121 2122 default: 2123 throw OrthancException(ErrorCode_InternalError); 2124 } 2125 2126 // If we have not reached the Patient level, find the parent of 2127 // the current resource 2128 if (!done) 2129 { 2130 bool ok = transaction.LookupParent(currentId, currentId); 2131 (void) ok; // Remove warning about unused variable in release builds 2132 assert(ok); 2133 } 2134 } 2135 2136 ExportedResource resource(-1, 2137 type, 2138 publicId_, 2139 remoteModality_, 2140 SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */), 2141 patientId, 2142 studyInstanceUid, 2143 seriesInstanceUid, 2144 sopInstanceUid); 2145 2146 transaction.LogExportedResource(resource); 2147 } 2148 }; 2149 2150 Operations operations(publicId, remoteModality); 2151 Apply(operations); 2152 } 2153 2154 SetProtectedPatient(const std::string & publicId,bool isProtected)2155 void StatelessDatabaseOperations::SetProtectedPatient(const std::string& publicId, 2156 bool isProtected) 2157 { 2158 class Operations : public IReadWriteOperations 2159 { 2160 private: 2161 const std::string& publicId_; 2162 bool isProtected_; 2163 2164 public: 2165 Operations(const std::string& publicId, 2166 bool isProtected) : 2167 publicId_(publicId), 2168 isProtected_(isProtected) 2169 { 2170 } 2171 2172 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2173 { 2174 // Lookup for the requested resource 2175 int64_t id; 2176 ResourceType type; 2177 if (!transaction.LookupResource(id, type, publicId_) || 2178 type != ResourceType_Patient) 2179 { 2180 throw OrthancException(ErrorCode_ParameterOutOfRange); 2181 } 2182 else 2183 { 2184 transaction.SetProtectedPatient(id, isProtected_); 2185 } 2186 } 2187 }; 2188 2189 Operations operations(publicId, isProtected); 2190 Apply(operations); 2191 2192 if (isProtected) 2193 { 2194 LOG(INFO) << "Patient " << publicId << " has been protected"; 2195 } 2196 else 2197 { 2198 LOG(INFO) << "Patient " << publicId << " has been unprotected"; 2199 } 2200 } 2201 2202 SetMetadata(int64_t & newRevision,const std::string & publicId,MetadataType type,const std::string & value,bool hasOldRevision,int64_t oldRevision,const std::string & oldMD5)2203 void StatelessDatabaseOperations::SetMetadata(int64_t& newRevision, 2204 const std::string& publicId, 2205 MetadataType type, 2206 const std::string& value, 2207 bool hasOldRevision, 2208 int64_t oldRevision, 2209 const std::string& oldMD5) 2210 { 2211 class Operations : public IReadWriteOperations 2212 { 2213 private: 2214 int64_t& newRevision_; 2215 const std::string& publicId_; 2216 MetadataType type_; 2217 const std::string& value_; 2218 bool hasOldRevision_; 2219 int64_t oldRevision_; 2220 const std::string& oldMD5_; 2221 2222 public: 2223 Operations(int64_t& newRevision, 2224 const std::string& publicId, 2225 MetadataType type, 2226 const std::string& value, 2227 bool hasOldRevision, 2228 int64_t oldRevision, 2229 const std::string& oldMD5) : 2230 newRevision_(newRevision), 2231 publicId_(publicId), 2232 type_(type), 2233 value_(value), 2234 hasOldRevision_(hasOldRevision), 2235 oldRevision_(oldRevision), 2236 oldMD5_(oldMD5) 2237 { 2238 } 2239 2240 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2241 { 2242 ResourceType resourceType; 2243 int64_t id; 2244 if (!transaction.LookupResource(id, resourceType, publicId_)) 2245 { 2246 throw OrthancException(ErrorCode_UnknownResource); 2247 } 2248 else 2249 { 2250 std::string oldValue; 2251 int64_t expectedRevision; 2252 if (transaction.LookupMetadata(oldValue, expectedRevision, id, type_)) 2253 { 2254 if (hasOldRevision_) 2255 { 2256 std::string expectedMD5; 2257 Toolbox::ComputeMD5(expectedMD5, oldValue); 2258 2259 if (expectedRevision != oldRevision_ || 2260 expectedMD5 != oldMD5_) 2261 { 2262 throw OrthancException(ErrorCode_Revision); 2263 } 2264 } 2265 2266 newRevision_ = expectedRevision + 1; 2267 } 2268 else 2269 { 2270 // The metadata is not existing yet: Ignore "oldRevision" 2271 // and initialize a new sequence of revisions 2272 newRevision_ = 0; 2273 } 2274 2275 transaction.SetMetadata(id, type_, value_, newRevision_); 2276 2277 if (IsUserMetadata(type_)) 2278 { 2279 transaction.LogChange(id, ChangeType_UpdatedMetadata, resourceType, publicId_); 2280 } 2281 } 2282 } 2283 }; 2284 2285 Operations operations(newRevision, publicId, type, value, hasOldRevision, oldRevision, oldMD5); 2286 Apply(operations); 2287 } 2288 2289 OverwriteMetadata(const std::string & publicId,MetadataType type,const std::string & value)2290 void StatelessDatabaseOperations::OverwriteMetadata(const std::string& publicId, 2291 MetadataType type, 2292 const std::string& value) 2293 { 2294 int64_t newRevision; // Unused 2295 SetMetadata(newRevision, publicId, type, value, false /* no old revision */, -1 /* dummy */, "" /* dummy */); 2296 } 2297 2298 DeleteMetadata(const std::string & publicId,MetadataType type,bool hasRevision,int64_t revision,const std::string & md5)2299 bool StatelessDatabaseOperations::DeleteMetadata(const std::string& publicId, 2300 MetadataType type, 2301 bool hasRevision, 2302 int64_t revision, 2303 const std::string& md5) 2304 { 2305 class Operations : public IReadWriteOperations 2306 { 2307 private: 2308 const std::string& publicId_; 2309 MetadataType type_; 2310 bool hasRevision_; 2311 int64_t revision_; 2312 const std::string& md5_; 2313 bool found_; 2314 2315 public: 2316 Operations(const std::string& publicId, 2317 MetadataType type, 2318 bool hasRevision, 2319 int64_t revision, 2320 const std::string& md5) : 2321 publicId_(publicId), 2322 type_(type), 2323 hasRevision_(hasRevision), 2324 revision_(revision), 2325 md5_(md5), 2326 found_(false) 2327 { 2328 } 2329 2330 bool HasFound() const 2331 { 2332 return found_; 2333 } 2334 2335 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2336 { 2337 ResourceType resourceType; 2338 int64_t id; 2339 if (!transaction.LookupResource(id, resourceType, publicId_)) 2340 { 2341 throw OrthancException(ErrorCode_UnknownResource); 2342 } 2343 else 2344 { 2345 std::string value; 2346 int64_t expectedRevision; 2347 if (transaction.LookupMetadata(value, expectedRevision, id, type_)) 2348 { 2349 if (hasRevision_) 2350 { 2351 std::string expectedMD5; 2352 Toolbox::ComputeMD5(expectedMD5, value); 2353 2354 if (expectedRevision != revision_ || 2355 expectedMD5 != md5_) 2356 { 2357 throw OrthancException(ErrorCode_Revision); 2358 } 2359 } 2360 2361 found_ = true; 2362 transaction.DeleteMetadata(id, type_); 2363 2364 if (IsUserMetadata(type_)) 2365 { 2366 transaction.LogChange(id, ChangeType_UpdatedMetadata, resourceType, publicId_); 2367 } 2368 } 2369 else 2370 { 2371 found_ = false; 2372 } 2373 } 2374 } 2375 }; 2376 2377 Operations operations(publicId, type, hasRevision, revision, md5); 2378 Apply(operations); 2379 return operations.HasFound(); 2380 } 2381 2382 IncrementGlobalSequence(GlobalProperty sequence,bool shared)2383 uint64_t StatelessDatabaseOperations::IncrementGlobalSequence(GlobalProperty sequence, 2384 bool shared) 2385 { 2386 class Operations : public IReadWriteOperations 2387 { 2388 private: 2389 uint64_t newValue_; 2390 GlobalProperty sequence_; 2391 bool shared_; 2392 2393 public: 2394 Operations(GlobalProperty sequence, 2395 bool shared) : 2396 newValue_(0), // Dummy initialization 2397 sequence_(sequence), 2398 shared_(shared) 2399 { 2400 } 2401 2402 uint64_t GetNewValue() const 2403 { 2404 return newValue_; 2405 } 2406 2407 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2408 { 2409 std::string oldString; 2410 2411 if (transaction.LookupGlobalProperty(oldString, sequence_, shared_)) 2412 { 2413 uint64_t oldValue; 2414 2415 try 2416 { 2417 oldValue = boost::lexical_cast<uint64_t>(oldString); 2418 } 2419 catch (boost::bad_lexical_cast&) 2420 { 2421 LOG(ERROR) << "Cannot read the global sequence " 2422 << boost::lexical_cast<std::string>(sequence_) << ", resetting it"; 2423 oldValue = 0; 2424 } 2425 2426 newValue_ = oldValue + 1; 2427 } 2428 else 2429 { 2430 // Initialize the sequence at "1" 2431 newValue_ = 1; 2432 } 2433 2434 transaction.SetGlobalProperty(sequence_, shared_, boost::lexical_cast<std::string>(newValue_)); 2435 } 2436 }; 2437 2438 Operations operations(sequence, shared); 2439 Apply(operations); 2440 assert(operations.GetNewValue() != 0); 2441 return operations.GetNewValue(); 2442 } 2443 2444 DeleteChanges()2445 void StatelessDatabaseOperations::DeleteChanges() 2446 { 2447 class Operations : public IReadWriteOperations 2448 { 2449 public: 2450 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2451 { 2452 transaction.ClearChanges(); 2453 } 2454 }; 2455 2456 Operations operations; 2457 Apply(operations); 2458 } 2459 2460 DeleteExportedResources()2461 void StatelessDatabaseOperations::DeleteExportedResources() 2462 { 2463 class Operations : public IReadWriteOperations 2464 { 2465 public: 2466 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2467 { 2468 transaction.ClearExportedResources(); 2469 } 2470 }; 2471 2472 Operations operations; 2473 Apply(operations); 2474 } 2475 2476 SetGlobalProperty(GlobalProperty property,bool shared,const std::string & value)2477 void StatelessDatabaseOperations::SetGlobalProperty(GlobalProperty property, 2478 bool shared, 2479 const std::string& value) 2480 { 2481 class Operations : public IReadWriteOperations 2482 { 2483 private: 2484 GlobalProperty property_; 2485 bool shared_; 2486 const std::string& value_; 2487 2488 public: 2489 Operations(GlobalProperty property, 2490 bool shared, 2491 const std::string& value) : 2492 property_(property), 2493 shared_(shared), 2494 value_(value) 2495 { 2496 } 2497 2498 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2499 { 2500 transaction.SetGlobalProperty(property_, shared_, value_); 2501 } 2502 }; 2503 2504 Operations operations(property, shared, value); 2505 Apply(operations); 2506 } 2507 2508 DeleteAttachment(const std::string & publicId,FileContentType type,bool hasRevision,int64_t revision,const std::string & md5)2509 bool StatelessDatabaseOperations::DeleteAttachment(const std::string& publicId, 2510 FileContentType type, 2511 bool hasRevision, 2512 int64_t revision, 2513 const std::string& md5) 2514 { 2515 class Operations : public IReadWriteOperations 2516 { 2517 private: 2518 const std::string& publicId_; 2519 FileContentType type_; 2520 bool hasRevision_; 2521 int64_t revision_; 2522 const std::string& md5_; 2523 bool found_; 2524 2525 public: 2526 Operations(const std::string& publicId, 2527 FileContentType type, 2528 bool hasRevision, 2529 int64_t revision, 2530 const std::string& md5) : 2531 publicId_(publicId), 2532 type_(type), 2533 hasRevision_(hasRevision), 2534 revision_(revision), 2535 md5_(md5), 2536 found_(false) 2537 { 2538 } 2539 2540 bool HasFound() const 2541 { 2542 return found_; 2543 } 2544 2545 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2546 { 2547 ResourceType resourceType; 2548 int64_t id; 2549 if (!transaction.LookupResource(id, resourceType, publicId_)) 2550 { 2551 throw OrthancException(ErrorCode_UnknownResource); 2552 } 2553 else 2554 { 2555 FileInfo info; 2556 int64_t expectedRevision; 2557 if (transaction.LookupAttachment(info, expectedRevision, id, type_)) 2558 { 2559 if (hasRevision_ && 2560 (expectedRevision != revision_ || 2561 info.GetUncompressedMD5() != md5_)) 2562 { 2563 throw OrthancException(ErrorCode_Revision); 2564 } 2565 2566 found_ = true; 2567 transaction.DeleteAttachment(id, type_); 2568 2569 if (IsUserContentType(type_)) 2570 { 2571 transaction.LogChange(id, ChangeType_UpdatedAttachment, resourceType, publicId_); 2572 } 2573 } 2574 else 2575 { 2576 found_ = false; 2577 } 2578 } 2579 } 2580 }; 2581 2582 Operations operations(publicId, type, hasRevision, revision, md5); 2583 Apply(operations); 2584 return operations.HasFound(); 2585 } 2586 2587 LogChange(int64_t internalId,ChangeType changeType,const std::string & publicId,ResourceType level)2588 void StatelessDatabaseOperations::LogChange(int64_t internalId, 2589 ChangeType changeType, 2590 const std::string& publicId, 2591 ResourceType level) 2592 { 2593 class Operations : public IReadWriteOperations 2594 { 2595 private: 2596 int64_t internalId_; 2597 ChangeType changeType_; 2598 const std::string& publicId_; 2599 ResourceType level_; 2600 2601 public: 2602 Operations(int64_t internalId, 2603 ChangeType changeType, 2604 const std::string& publicId, 2605 ResourceType level) : 2606 internalId_(internalId), 2607 changeType_(changeType), 2608 publicId_(publicId), 2609 level_(level) 2610 { 2611 } 2612 2613 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2614 { 2615 int64_t id; 2616 ResourceType type; 2617 if (transaction.LookupResource(id, type, publicId_) && 2618 id == internalId_) 2619 { 2620 /** 2621 * Make sure that the resource is still existing, with the 2622 * same internal ID, which indicates the absence of bouncing 2623 * (if deleting then recreating the same resource). Don't 2624 * throw an exception if the resource has been deleted, 2625 * because this function might e.g. be called from 2626 * "StatelessDatabaseOperations::UnstableResourcesMonitorThread()" 2627 * (for which a deleted resource is *not* an error case). 2628 **/ 2629 if (type == level_) 2630 { 2631 transaction.LogChange(id, changeType_, type, publicId_); 2632 } 2633 else 2634 { 2635 // Consistency check 2636 throw OrthancException(ErrorCode_UnknownResource); 2637 } 2638 } 2639 } 2640 }; 2641 2642 Operations operations(internalId, changeType, publicId, level); 2643 Apply(operations); 2644 } 2645 2646 ReconstructInstance(const ParsedDicomFile & dicom)2647 void StatelessDatabaseOperations::ReconstructInstance(const ParsedDicomFile& dicom) 2648 { 2649 class Operations : public IReadWriteOperations 2650 { 2651 private: 2652 DicomMap summary_; 2653 std::unique_ptr<DicomInstanceHasher> hasher_; 2654 bool hasTransferSyntax_; 2655 DicomTransferSyntax transferSyntax_; 2656 2657 static void ReplaceMetadata(ReadWriteTransaction& transaction, 2658 int64_t instance, 2659 MetadataType metadata, 2660 const std::string& value) 2661 { 2662 std::string oldValue; 2663 int64_t oldRevision; 2664 2665 if (transaction.LookupMetadata(oldValue, oldRevision, instance, metadata)) 2666 { 2667 transaction.SetMetadata(instance, metadata, value, oldRevision + 1); 2668 } 2669 else 2670 { 2671 transaction.SetMetadata(instance, metadata, value, 0); 2672 } 2673 } 2674 2675 public: 2676 explicit Operations(const ParsedDicomFile& dicom) 2677 { 2678 OrthancConfiguration::DefaultExtractDicomSummary(summary_, dicom); 2679 hasher_.reset(new DicomInstanceHasher(summary_)); 2680 hasTransferSyntax_ = dicom.LookupTransferSyntax(transferSyntax_); 2681 } 2682 2683 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2684 { 2685 int64_t patient = -1, study = -1, series = -1, instance = -1; 2686 2687 ResourceType type1, type2, type3, type4; 2688 if (!transaction.LookupResource(patient, type1, hasher_->HashPatient()) || 2689 !transaction.LookupResource(study, type2, hasher_->HashStudy()) || 2690 !transaction.LookupResource(series, type3, hasher_->HashSeries()) || 2691 !transaction.LookupResource(instance, type4, hasher_->HashInstance()) || 2692 type1 != ResourceType_Patient || 2693 type2 != ResourceType_Study || 2694 type3 != ResourceType_Series || 2695 type4 != ResourceType_Instance || 2696 patient == -1 || 2697 study == -1 || 2698 series == -1 || 2699 instance == -1) 2700 { 2701 throw OrthancException(ErrorCode_InternalError); 2702 } 2703 2704 transaction.ClearMainDicomTags(patient); 2705 transaction.ClearMainDicomTags(study); 2706 transaction.ClearMainDicomTags(series); 2707 transaction.ClearMainDicomTags(instance); 2708 2709 { 2710 ResourcesContent content(false /* prevent the setting of metadata */); 2711 content.AddResource(patient, ResourceType_Patient, summary_); 2712 content.AddResource(study, ResourceType_Study, summary_); 2713 content.AddResource(series, ResourceType_Series, summary_); 2714 content.AddResource(instance, ResourceType_Instance, summary_); 2715 transaction.SetResourcesContent(content); 2716 } 2717 2718 if (hasTransferSyntax_) 2719 { 2720 ReplaceMetadata(transaction, instance, MetadataType_Instance_TransferSyntax, GetTransferSyntaxUid(transferSyntax_)); 2721 } 2722 2723 const DicomValue* value; 2724 if ((value = summary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && 2725 !value->IsNull() && 2726 !value->IsBinary()) 2727 { 2728 ReplaceMetadata(transaction, instance, MetadataType_Instance_SopClassUid, value->GetContent()); 2729 } 2730 } 2731 }; 2732 2733 Operations operations(dicom); 2734 Apply(operations); 2735 } 2736 2737 IsRecyclingNeeded(IDatabaseWrapper::ITransaction & transaction,uint64_t maximumStorageSize,unsigned int maximumPatients,uint64_t addedInstanceSize)2738 static bool IsRecyclingNeeded(IDatabaseWrapper::ITransaction& transaction, 2739 uint64_t maximumStorageSize, 2740 unsigned int maximumPatients, 2741 uint64_t addedInstanceSize) 2742 { 2743 if (maximumStorageSize != 0) 2744 { 2745 if (maximumStorageSize < addedInstanceSize) 2746 { 2747 throw OrthancException(ErrorCode_FullStorage, "Cannot store an instance of size " + 2748 boost::lexical_cast<std::string>(addedInstanceSize) + 2749 " bytes in a storage area limited to " + 2750 boost::lexical_cast<std::string>(maximumStorageSize)); 2751 } 2752 2753 if (transaction.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize)) 2754 { 2755 return true; 2756 } 2757 } 2758 2759 if (maximumPatients != 0) 2760 { 2761 uint64_t patientCount = transaction.GetResourcesCount(ResourceType_Patient); 2762 if (patientCount > maximumPatients) 2763 { 2764 return true; 2765 } 2766 } 2767 2768 return false; 2769 } 2770 2771 Recycle(uint64_t maximumStorageSize,unsigned int maximumPatients,uint64_t addedInstanceSize,const std::string & newPatientId)2772 void StatelessDatabaseOperations::ReadWriteTransaction::Recycle(uint64_t maximumStorageSize, 2773 unsigned int maximumPatients, 2774 uint64_t addedInstanceSize, 2775 const std::string& newPatientId) 2776 { 2777 // TODO - Performance: Avoid calls to "IsRecyclingNeeded()" 2778 2779 if (IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize)) 2780 { 2781 // Check whether other DICOM instances from this patient are 2782 // already stored 2783 int64_t patientToAvoid; 2784 bool hasPatientToAvoid; 2785 2786 if (newPatientId.empty()) 2787 { 2788 hasPatientToAvoid = false; 2789 } 2790 else 2791 { 2792 ResourceType type; 2793 hasPatientToAvoid = transaction_.LookupResource(patientToAvoid, type, newPatientId); 2794 if (type != ResourceType_Patient) 2795 { 2796 throw OrthancException(ErrorCode_InternalError); 2797 } 2798 } 2799 2800 // Iteratively select patient to remove until there is enough 2801 // space in the DICOM store 2802 int64_t patientToRecycle; 2803 while (true) 2804 { 2805 // If other instances of this patient are already in the store, 2806 // we must avoid to recycle them 2807 bool ok = (hasPatientToAvoid ? 2808 transaction_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) : 2809 transaction_.SelectPatientToRecycle(patientToRecycle)); 2810 2811 if (!ok) 2812 { 2813 throw OrthancException(ErrorCode_FullStorage); 2814 } 2815 2816 LOG(TRACE) << "Recycling one patient"; 2817 transaction_.DeleteResource(patientToRecycle); 2818 2819 if (!IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize)) 2820 { 2821 // OK, we're done 2822 return; 2823 } 2824 } 2825 } 2826 } 2827 2828 StandaloneRecycling(uint64_t maximumStorageSize,unsigned int maximumPatientCount)2829 void StatelessDatabaseOperations::StandaloneRecycling(uint64_t maximumStorageSize, 2830 unsigned int maximumPatientCount) 2831 { 2832 class Operations : public IReadWriteOperations 2833 { 2834 private: 2835 uint64_t maximumStorageSize_; 2836 unsigned int maximumPatientCount_; 2837 2838 public: 2839 Operations(uint64_t maximumStorageSize, 2840 unsigned int maximumPatientCount) : 2841 maximumStorageSize_(maximumStorageSize), 2842 maximumPatientCount_(maximumPatientCount) 2843 { 2844 } 2845 2846 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 2847 { 2848 transaction.Recycle(maximumStorageSize_, maximumPatientCount_, 0, ""); 2849 } 2850 }; 2851 2852 if (maximumStorageSize != 0 || 2853 maximumPatientCount != 0) 2854 { 2855 Operations operations(maximumStorageSize, maximumPatientCount); 2856 Apply(operations); 2857 } 2858 } 2859 2860 Store(std::map<MetadataType,std::string> & instanceMetadata,const DicomMap & dicomSummary,const Attachments & attachments,const MetadataMap & metadata,const DicomInstanceOrigin & origin,bool overwrite,bool hasTransferSyntax,DicomTransferSyntax transferSyntax,bool hasPixelDataOffset,uint64_t pixelDataOffset,uint64_t maximumStorageSize,unsigned int maximumPatients)2861 StoreStatus StatelessDatabaseOperations::Store(std::map<MetadataType, std::string>& instanceMetadata, 2862 const DicomMap& dicomSummary, 2863 const Attachments& attachments, 2864 const MetadataMap& metadata, 2865 const DicomInstanceOrigin& origin, 2866 bool overwrite, 2867 bool hasTransferSyntax, 2868 DicomTransferSyntax transferSyntax, 2869 bool hasPixelDataOffset, 2870 uint64_t pixelDataOffset, 2871 uint64_t maximumStorageSize, 2872 unsigned int maximumPatients) 2873 { 2874 class Operations : public IReadWriteOperations 2875 { 2876 private: 2877 StoreStatus storeStatus_; 2878 std::map<MetadataType, std::string>& instanceMetadata_; 2879 const DicomMap& dicomSummary_; 2880 const Attachments& attachments_; 2881 const MetadataMap& metadata_; 2882 const DicomInstanceOrigin& origin_; 2883 bool overwrite_; 2884 bool hasTransferSyntax_; 2885 DicomTransferSyntax transferSyntax_; 2886 bool hasPixelDataOffset_; 2887 uint64_t pixelDataOffset_; 2888 uint64_t maximumStorageSize_; 2889 unsigned int maximumPatientCount_; 2890 2891 // Auto-computed fields 2892 bool hasExpectedInstances_; 2893 int64_t expectedInstances_; 2894 std::string hashPatient_; 2895 std::string hashStudy_; 2896 std::string hashSeries_; 2897 std::string hashInstance_; 2898 2899 2900 static void SetInstanceMetadata(ResourcesContent& content, 2901 std::map<MetadataType, std::string>& instanceMetadata, 2902 int64_t instance, 2903 MetadataType metadata, 2904 const std::string& value) 2905 { 2906 content.AddMetadata(instance, metadata, value); 2907 instanceMetadata[metadata] = value; 2908 } 2909 2910 2911 static bool ComputeExpectedNumberOfInstances(int64_t& target, 2912 const DicomMap& dicomSummary) 2913 { 2914 try 2915 { 2916 const DicomValue* value; 2917 const DicomValue* value2; 2918 2919 if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGES_IN_ACQUISITION)) != NULL && 2920 !value->IsNull() && 2921 !value->IsBinary() && 2922 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS)) != NULL && 2923 !value2->IsNull() && 2924 !value2->IsBinary()) 2925 { 2926 // Patch for series with temporal positions thanks to Will Ryder 2927 int64_t imagesInAcquisition = boost::lexical_cast<int64_t>(value->GetContent()); 2928 int64_t countTemporalPositions = boost::lexical_cast<int64_t>(value2->GetContent()); 2929 target = imagesInAcquisition * countTemporalPositions; 2930 return (target > 0); 2931 } 2932 2933 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_SLICES)) != NULL && 2934 !value->IsNull() && 2935 !value->IsBinary() && 2936 (value2 = dicomSummary.TestAndGetValue(DICOM_TAG_NUMBER_OF_TIME_SLICES)) != NULL && 2937 !value2->IsBinary() && 2938 !value2->IsNull()) 2939 { 2940 // Support of Cardio-PET images 2941 int64_t numberOfSlices = boost::lexical_cast<int64_t>(value->GetContent()); 2942 int64_t numberOfTimeSlices = boost::lexical_cast<int64_t>(value2->GetContent()); 2943 target = numberOfSlices * numberOfTimeSlices; 2944 return (target > 0); 2945 } 2946 2947 else if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)) != NULL && 2948 !value->IsNull() && 2949 !value->IsBinary()) 2950 { 2951 target = boost::lexical_cast<int64_t>(value->GetContent()); 2952 return (target > 0); 2953 } 2954 } 2955 catch (OrthancException&) 2956 { 2957 } 2958 catch (boost::bad_lexical_cast&) 2959 { 2960 } 2961 2962 return false; 2963 } 2964 2965 public: 2966 Operations(std::map<MetadataType, std::string>& instanceMetadata, 2967 const DicomMap& dicomSummary, 2968 const Attachments& attachments, 2969 const MetadataMap& metadata, 2970 const DicomInstanceOrigin& origin, 2971 bool overwrite, 2972 bool hasTransferSyntax, 2973 DicomTransferSyntax transferSyntax, 2974 bool hasPixelDataOffset, 2975 uint64_t pixelDataOffset, 2976 uint64_t maximumStorageSize, 2977 unsigned int maximumPatientCount) : 2978 storeStatus_(StoreStatus_Failure), 2979 instanceMetadata_(instanceMetadata), 2980 dicomSummary_(dicomSummary), 2981 attachments_(attachments), 2982 metadata_(metadata), 2983 origin_(origin), 2984 overwrite_(overwrite), 2985 hasTransferSyntax_(hasTransferSyntax), 2986 transferSyntax_(transferSyntax), 2987 hasPixelDataOffset_(hasPixelDataOffset), 2988 pixelDataOffset_(pixelDataOffset), 2989 maximumStorageSize_(maximumStorageSize), 2990 maximumPatientCount_(maximumPatientCount) 2991 { 2992 hasExpectedInstances_ = ComputeExpectedNumberOfInstances(expectedInstances_, dicomSummary); 2993 2994 instanceMetadata_.clear(); 2995 2996 DicomInstanceHasher hasher(dicomSummary); 2997 hashPatient_ = hasher.HashPatient(); 2998 hashStudy_ = hasher.HashStudy(); 2999 hashSeries_ = hasher.HashSeries(); 3000 hashInstance_ = hasher.HashInstance(); 3001 } 3002 3003 StoreStatus GetStoreStatus() const 3004 { 3005 return storeStatus_; 3006 } 3007 3008 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 3009 { 3010 try 3011 { 3012 IDatabaseWrapper::CreateInstanceResult status; 3013 int64_t instanceId; 3014 3015 // Check whether this instance is already stored 3016 if (!transaction.CreateInstance(status, instanceId, hashPatient_, 3017 hashStudy_, hashSeries_, hashInstance_)) 3018 { 3019 // The instance already exists 3020 3021 if (overwrite_) 3022 { 3023 // Overwrite the old instance 3024 LOG(INFO) << "Overwriting instance: " << hashInstance_; 3025 transaction.DeleteResource(instanceId); 3026 3027 // Re-create the instance, now that the old one is removed 3028 if (!transaction.CreateInstance(status, instanceId, hashPatient_, 3029 hashStudy_, hashSeries_, hashInstance_)) 3030 { 3031 throw OrthancException(ErrorCode_InternalError); 3032 } 3033 } 3034 else 3035 { 3036 // Do nothing if the instance already exists and overwriting is disabled 3037 transaction.GetAllMetadata(instanceMetadata_, instanceId); 3038 storeStatus_ = StoreStatus_AlreadyStored; 3039 return; 3040 } 3041 } 3042 3043 3044 // Warn about the creation of new resources. The order must be 3045 // from instance to patient. 3046 3047 // NB: In theory, could be sped up by grouping the underlying 3048 // calls to "transaction.LogChange()". However, this would only have an 3049 // impact when new patient/study/series get created, which 3050 // occurs far less often that creating new instances. The 3051 // positive impact looks marginal in practice. 3052 transaction.LogChange(instanceId, ChangeType_NewInstance, ResourceType_Instance, hashInstance_); 3053 3054 if (status.isNewSeries_) 3055 { 3056 transaction.LogChange(status.seriesId_, ChangeType_NewSeries, ResourceType_Series, hashSeries_); 3057 } 3058 3059 if (status.isNewStudy_) 3060 { 3061 transaction.LogChange(status.studyId_, ChangeType_NewStudy, ResourceType_Study, hashStudy_); 3062 } 3063 3064 if (status.isNewPatient_) 3065 { 3066 transaction.LogChange(status.patientId_, ChangeType_NewPatient, ResourceType_Patient, hashPatient_); 3067 } 3068 3069 3070 // Ensure there is enough room in the storage for the new instance 3071 uint64_t instanceSize = 0; 3072 for (Attachments::const_iterator it = attachments_.begin(); 3073 it != attachments_.end(); ++it) 3074 { 3075 instanceSize += it->GetCompressedSize(); 3076 } 3077 3078 transaction.Recycle(maximumStorageSize_, maximumPatientCount_, 3079 instanceSize, hashPatient_ /* don't consider the current patient for recycling */); 3080 3081 3082 // Attach the files to the newly created instance 3083 for (Attachments::const_iterator it = attachments_.begin(); 3084 it != attachments_.end(); ++it) 3085 { 3086 transaction.AddAttachment(instanceId, *it, 0 /* this is the first revision */); 3087 } 3088 3089 3090 { 3091 ResourcesContent content(true /* new resource, metadata can be set */); 3092 3093 // Populate the tags of the newly-created resources 3094 3095 content.AddResource(instanceId, ResourceType_Instance, dicomSummary_); 3096 3097 if (status.isNewSeries_) 3098 { 3099 content.AddResource(status.seriesId_, ResourceType_Series, dicomSummary_); 3100 } 3101 3102 if (status.isNewStudy_) 3103 { 3104 content.AddResource(status.studyId_, ResourceType_Study, dicomSummary_); 3105 } 3106 3107 if (status.isNewPatient_) 3108 { 3109 content.AddResource(status.patientId_, ResourceType_Patient, dicomSummary_); 3110 } 3111 3112 3113 // Attach the user-specified metadata 3114 3115 for (MetadataMap::const_iterator 3116 it = metadata_.begin(); it != metadata_.end(); ++it) 3117 { 3118 switch (it->first.first) 3119 { 3120 case ResourceType_Patient: 3121 content.AddMetadata(status.patientId_, it->first.second, it->second); 3122 break; 3123 3124 case ResourceType_Study: 3125 content.AddMetadata(status.studyId_, it->first.second, it->second); 3126 break; 3127 3128 case ResourceType_Series: 3129 content.AddMetadata(status.seriesId_, it->first.second, it->second); 3130 break; 3131 3132 case ResourceType_Instance: 3133 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3134 it->first.second, it->second); 3135 break; 3136 3137 default: 3138 throw OrthancException(ErrorCode_ParameterOutOfRange); 3139 } 3140 } 3141 3142 3143 // Attach the auto-computed metadata for the patient/study/series levels 3144 std::string now = SystemToolbox::GetNowIsoString(true /* use UTC time (not local time) */); 3145 content.AddMetadata(status.seriesId_, MetadataType_LastUpdate, now); 3146 content.AddMetadata(status.studyId_, MetadataType_LastUpdate, now); 3147 content.AddMetadata(status.patientId_, MetadataType_LastUpdate, now); 3148 3149 if (status.isNewSeries_) 3150 { 3151 if (hasExpectedInstances_) 3152 { 3153 content.AddMetadata(status.seriesId_, MetadataType_Series_ExpectedNumberOfInstances, 3154 boost::lexical_cast<std::string>(expectedInstances_)); 3155 } 3156 3157 // New in Orthanc 1.9.0 3158 content.AddMetadata(status.seriesId_, MetadataType_RemoteAet, 3159 origin_.GetRemoteAetC()); 3160 } 3161 3162 3163 // Attach the auto-computed metadata for the instance level, 3164 // reflecting these additions into the input metadata map 3165 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3166 MetadataType_Instance_ReceptionDate, now); 3167 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_RemoteAet, 3168 origin_.GetRemoteAetC()); 3169 SetInstanceMetadata(content, instanceMetadata_, instanceId, MetadataType_Instance_Origin, 3170 EnumerationToString(origin_.GetRequestOrigin())); 3171 3172 3173 if (hasTransferSyntax_) 3174 { 3175 // New in Orthanc 1.2.0 3176 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3177 MetadataType_Instance_TransferSyntax, 3178 GetTransferSyntaxUid(transferSyntax_)); 3179 } 3180 3181 { 3182 std::string s; 3183 3184 if (origin_.LookupRemoteIp(s)) 3185 { 3186 // New in Orthanc 1.4.0 3187 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3188 MetadataType_Instance_RemoteIp, s); 3189 } 3190 3191 if (origin_.LookupCalledAet(s)) 3192 { 3193 // New in Orthanc 1.4.0 3194 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3195 MetadataType_Instance_CalledAet, s); 3196 } 3197 3198 if (origin_.LookupHttpUsername(s)) 3199 { 3200 // New in Orthanc 1.4.0 3201 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3202 MetadataType_Instance_HttpUsername, s); 3203 } 3204 } 3205 3206 if (hasPixelDataOffset_) 3207 { 3208 // New in Orthanc 1.9.1 3209 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3210 MetadataType_Instance_PixelDataOffset, 3211 boost::lexical_cast<std::string>(pixelDataOffset_)); 3212 } 3213 3214 const DicomValue* value; 3215 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_SOP_CLASS_UID)) != NULL && 3216 !value->IsNull() && 3217 !value->IsBinary()) 3218 { 3219 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3220 MetadataType_Instance_SopClassUid, value->GetContent()); 3221 } 3222 3223 3224 if ((value = dicomSummary_.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL || 3225 (value = dicomSummary_.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL) 3226 { 3227 if (!value->IsNull() && 3228 !value->IsBinary()) 3229 { 3230 SetInstanceMetadata(content, instanceMetadata_, instanceId, 3231 MetadataType_Instance_IndexInSeries, Toolbox::StripSpaces(value->GetContent())); 3232 } 3233 } 3234 3235 3236 transaction.SetResourcesContent(content); 3237 } 3238 3239 3240 // Check whether the series of this new instance is now completed 3241 int64_t expectedNumberOfInstances; 3242 if (ComputeExpectedNumberOfInstances(expectedNumberOfInstances, dicomSummary_)) 3243 { 3244 SeriesStatus seriesStatus = transaction.GetSeriesStatus(status.seriesId_, expectedNumberOfInstances); 3245 if (seriesStatus == SeriesStatus_Complete) 3246 { 3247 transaction.LogChange(status.seriesId_, ChangeType_CompletedSeries, ResourceType_Series, hashSeries_); 3248 } 3249 } 3250 3251 transaction.LogChange(status.seriesId_, ChangeType_NewChildInstance, ResourceType_Series, hashSeries_); 3252 transaction.LogChange(status.studyId_, ChangeType_NewChildInstance, ResourceType_Study, hashStudy_); 3253 transaction.LogChange(status.patientId_, ChangeType_NewChildInstance, ResourceType_Patient, hashPatient_); 3254 3255 // Mark the parent resources of this instance as unstable 3256 transaction.GetTransactionContext().MarkAsUnstable(status.seriesId_, ResourceType_Series, hashSeries_); 3257 transaction.GetTransactionContext().MarkAsUnstable(status.studyId_, ResourceType_Study, hashStudy_); 3258 transaction.GetTransactionContext().MarkAsUnstable(status.patientId_, ResourceType_Patient, hashPatient_); 3259 transaction.GetTransactionContext().SignalAttachmentsAdded(instanceSize); 3260 3261 storeStatus_ = StoreStatus_Success; 3262 } 3263 catch (OrthancException& e) 3264 { 3265 if (e.GetErrorCode() == ErrorCode_DatabaseCannotSerialize) 3266 { 3267 throw; 3268 } 3269 else 3270 { 3271 LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; 3272 storeStatus_ = StoreStatus_Failure; 3273 } 3274 } 3275 } 3276 }; 3277 3278 3279 Operations operations(instanceMetadata, dicomSummary, attachments, metadata, origin, 3280 overwrite, hasTransferSyntax, transferSyntax, hasPixelDataOffset, 3281 pixelDataOffset, maximumStorageSize, maximumPatients); 3282 Apply(operations); 3283 return operations.GetStoreStatus(); 3284 } 3285 3286 AddAttachment(int64_t & newRevision,const FileInfo & attachment,const std::string & publicId,uint64_t maximumStorageSize,unsigned int maximumPatients,bool hasOldRevision,int64_t oldRevision,const std::string & oldMD5)3287 StoreStatus StatelessDatabaseOperations::AddAttachment(int64_t& newRevision, 3288 const FileInfo& attachment, 3289 const std::string& publicId, 3290 uint64_t maximumStorageSize, 3291 unsigned int maximumPatients, 3292 bool hasOldRevision, 3293 int64_t oldRevision, 3294 const std::string& oldMD5) 3295 { 3296 class Operations : public IReadWriteOperations 3297 { 3298 private: 3299 int64_t& newRevision_; 3300 StoreStatus status_; 3301 const FileInfo& attachment_; 3302 const std::string& publicId_; 3303 uint64_t maximumStorageSize_; 3304 unsigned int maximumPatientCount_; 3305 bool hasOldRevision_; 3306 int64_t oldRevision_; 3307 const std::string& oldMD5_; 3308 3309 public: 3310 Operations(int64_t& newRevision, 3311 const FileInfo& attachment, 3312 const std::string& publicId, 3313 uint64_t maximumStorageSize, 3314 unsigned int maximumPatientCount, 3315 bool hasOldRevision, 3316 int64_t oldRevision, 3317 const std::string& oldMD5) : 3318 newRevision_(newRevision), 3319 status_(StoreStatus_Failure), 3320 attachment_(attachment), 3321 publicId_(publicId), 3322 maximumStorageSize_(maximumStorageSize), 3323 maximumPatientCount_(maximumPatientCount), 3324 hasOldRevision_(hasOldRevision), 3325 oldRevision_(oldRevision), 3326 oldMD5_(oldMD5) 3327 { 3328 } 3329 3330 StoreStatus GetStatus() const 3331 { 3332 return status_; 3333 } 3334 3335 virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE 3336 { 3337 ResourceType resourceType; 3338 int64_t resourceId; 3339 if (!transaction.LookupResource(resourceId, resourceType, publicId_)) 3340 { 3341 status_ = StoreStatus_Failure; // Inexistent resource 3342 } 3343 else 3344 { 3345 // Possibly remove previous attachment 3346 { 3347 FileInfo oldFile; 3348 int64_t expectedRevision; 3349 if (transaction.LookupAttachment(oldFile, expectedRevision, resourceId, attachment_.GetContentType())) 3350 { 3351 if (hasOldRevision_ && 3352 (expectedRevision != oldRevision_ || 3353 oldFile.GetUncompressedMD5() != oldMD5_)) 3354 { 3355 throw OrthancException(ErrorCode_Revision); 3356 } 3357 else 3358 { 3359 newRevision_ = expectedRevision + 1; 3360 transaction.DeleteAttachment(resourceId, attachment_.GetContentType()); 3361 } 3362 } 3363 else 3364 { 3365 // The attachment is not existing yet: Ignore "oldRevision" 3366 // and initialize a new sequence of revisions 3367 newRevision_ = 0; 3368 } 3369 } 3370 3371 // Locate the patient of the target resource 3372 int64_t patientId = resourceId; 3373 for (;;) 3374 { 3375 int64_t parent; 3376 if (transaction.LookupParent(parent, patientId)) 3377 { 3378 // We have not reached the patient level yet 3379 patientId = parent; 3380 } 3381 else 3382 { 3383 // We have reached the patient level 3384 break; 3385 } 3386 } 3387 3388 // Possibly apply the recycling mechanism while preserving this patient 3389 assert(transaction.GetResourceType(patientId) == ResourceType_Patient); 3390 transaction.Recycle(maximumStorageSize_, maximumPatientCount_, 3391 attachment_.GetCompressedSize(), transaction.GetPublicId(patientId)); 3392 3393 transaction.AddAttachment(resourceId, attachment_, newRevision_); 3394 3395 if (IsUserContentType(attachment_.GetContentType())) 3396 { 3397 transaction.LogChange(resourceId, ChangeType_UpdatedAttachment, resourceType, publicId_); 3398 } 3399 3400 transaction.GetTransactionContext().SignalAttachmentsAdded(attachment_.GetCompressedSize()); 3401 3402 status_ = StoreStatus_Success; 3403 } 3404 } 3405 }; 3406 3407 3408 Operations operations(newRevision, attachment, publicId, maximumStorageSize, maximumPatients, 3409 hasOldRevision, oldRevision, oldMD5); 3410 Apply(operations); 3411 return operations.GetStatus(); 3412 } 3413 } 3414