Mercurial > hg > orthanc
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 } |