comparison OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp @ 4586:1d96fe7e054e db-changes

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