comparison OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Plugins/Engine/OrthancPluginDatabase.cpp@94f4a18a79cc
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../../OrthancServer/PrecompiledHeadersServer.h"
35 #include "OrthancPluginDatabase.h"
36
37 #if ORTHANC_ENABLE_PLUGINS != 1
38 #error The plugin support is disabled
39 #endif
40
41
42 #include "../../Core/Logging.h"
43 #include "../../Core/OrthancException.h"
44 #include "PluginsEnumerations.h"
45
46 #include <cassert>
47
48 namespace Orthanc
49 {
50 class OrthancPluginDatabase::Transaction : public IDatabaseWrapper::ITransaction
51 {
52 private:
53 OrthancPluginDatabase& that_;
54
55 void CheckSuccess(OrthancPluginErrorCode code) const
56 {
57 if (code != OrthancPluginErrorCode_Success)
58 {
59 that_.errorDictionary_.LogError(code, true);
60 throw OrthancException(static_cast<ErrorCode>(code));
61 }
62 }
63
64 public:
65 Transaction(OrthancPluginDatabase& that) :
66 that_(that)
67 {
68 }
69
70 virtual void Begin()
71 {
72 CheckSuccess(that_.backend_.startTransaction(that_.payload_));
73 }
74
75 virtual void Rollback()
76 {
77 CheckSuccess(that_.backend_.rollbackTransaction(that_.payload_));
78 }
79
80 virtual void Commit(int64_t diskSizeDelta)
81 {
82 if (that_.fastGetTotalSize_)
83 {
84 CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
85 }
86 else
87 {
88 if (static_cast<int64_t>(that_.currentDiskSize_) + diskSizeDelta < 0)
89 {
90 throw OrthancException(ErrorCode_DatabasePlugin);
91 }
92
93 uint64_t newDiskSize = (that_.currentDiskSize_ + diskSizeDelta);
94
95 assert(newDiskSize == that_.GetTotalCompressedSize());
96
97 CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
98
99 // The transaction has succeeded, we can commit the new disk size
100 that_.currentDiskSize_ = newDiskSize;
101 }
102 }
103 };
104
105
106 static FileInfo Convert(const OrthancPluginAttachment& attachment)
107 {
108 return FileInfo(attachment.uuid,
109 static_cast<FileContentType>(attachment.contentType),
110 attachment.uncompressedSize,
111 attachment.uncompressedHash,
112 static_cast<CompressionType>(attachment.compressionType),
113 attachment.compressedSize,
114 attachment.compressedHash);
115 }
116
117
118 void OrthancPluginDatabase::CheckSuccess(OrthancPluginErrorCode code)
119 {
120 if (code != OrthancPluginErrorCode_Success)
121 {
122 errorDictionary_.LogError(code, true);
123 throw OrthancException(static_cast<ErrorCode>(code));
124 }
125 }
126
127
128 void OrthancPluginDatabase::ResetAnswers()
129 {
130 type_ = _OrthancPluginDatabaseAnswerType_None;
131
132 answerDicomMap_ = NULL;
133 answerChanges_ = NULL;
134 answerExportedResources_ = NULL;
135 answerDone_ = NULL;
136 answerMatchingResources_ = NULL;
137 answerMatchingInstances_ = NULL;
138 answerMetadata_ = NULL;
139 }
140
141
142 void OrthancPluginDatabase::ForwardAnswers(std::list<int64_t>& target)
143 {
144 if (type_ != _OrthancPluginDatabaseAnswerType_None &&
145 type_ != _OrthancPluginDatabaseAnswerType_Int64)
146 {
147 throw OrthancException(ErrorCode_DatabasePlugin);
148 }
149
150 target.clear();
151
152 if (type_ == _OrthancPluginDatabaseAnswerType_Int64)
153 {
154 for (std::list<int64_t>::const_iterator
155 it = answerInt64_.begin(); it != answerInt64_.end(); ++it)
156 {
157 target.push_back(*it);
158 }
159 }
160 }
161
162
163 void OrthancPluginDatabase::ForwardAnswers(std::list<std::string>& target)
164 {
165 if (type_ != _OrthancPluginDatabaseAnswerType_None &&
166 type_ != _OrthancPluginDatabaseAnswerType_String)
167 {
168 throw OrthancException(ErrorCode_DatabasePlugin);
169 }
170
171 target.clear();
172
173 if (type_ == _OrthancPluginDatabaseAnswerType_String)
174 {
175 for (std::list<std::string>::const_iterator
176 it = answerStrings_.begin(); it != answerStrings_.end(); ++it)
177 {
178 target.push_back(*it);
179 }
180 }
181 }
182
183
184 bool OrthancPluginDatabase::ForwardSingleAnswer(std::string& target)
185 {
186 if (type_ == _OrthancPluginDatabaseAnswerType_None)
187 {
188 return false;
189 }
190 else if (type_ == _OrthancPluginDatabaseAnswerType_String &&
191 answerStrings_.size() == 1)
192 {
193 target = answerStrings_.front();
194 return true;
195 }
196 else
197 {
198 throw OrthancException(ErrorCode_DatabasePlugin);
199 }
200 }
201
202
203 bool OrthancPluginDatabase::ForwardSingleAnswer(int64_t& target)
204 {
205 if (type_ == _OrthancPluginDatabaseAnswerType_None)
206 {
207 return false;
208 }
209 else if (type_ == _OrthancPluginDatabaseAnswerType_Int64 &&
210 answerInt64_.size() == 1)
211 {
212 target = answerInt64_.front();
213 return true;
214 }
215 else
216 {
217 throw OrthancException(ErrorCode_DatabasePlugin);
218 }
219 }
220
221
222 OrthancPluginDatabase::OrthancPluginDatabase(SharedLibrary& library,
223 PluginsErrorDictionary& errorDictionary,
224 const OrthancPluginDatabaseBackend& backend,
225 const OrthancPluginDatabaseExtensions* extensions,
226 size_t extensionsSize,
227 void *payload) :
228 library_(library),
229 errorDictionary_(errorDictionary),
230 backend_(backend),
231 payload_(payload),
232 listener_(NULL)
233 {
234 static const char* const MISSING = " Missing extension in database index plugin: ";
235
236 ResetAnswers();
237
238 memset(&extensions_, 0, sizeof(extensions_));
239
240 size_t size = sizeof(extensions_);
241 if (extensionsSize < size)
242 {
243 size = extensionsSize; // Not all the extensions are available
244 }
245
246 memcpy(&extensions_, extensions, size);
247
248 bool isOptimal = true;
249
250 if (extensions_.lookupResources == NULL)
251 {
252 LOG(INFO) << MISSING << "LookupIdentifierRange()";
253 isOptimal = false;
254 }
255
256 if (extensions_.createInstance == NULL)
257 {
258 LOG(INFO) << MISSING << "CreateInstance()";
259 isOptimal = false;
260 }
261
262 if (extensions_.setResourcesContent == NULL)
263 {
264 LOG(INFO) << MISSING << "SetResourcesContent()";
265 isOptimal = false;
266 }
267
268 if (extensions_.getChildrenMetadata == NULL)
269 {
270 LOG(INFO) << MISSING << "GetChildrenMetadata()";
271 isOptimal = false;
272 }
273
274 if (extensions_.getAllMetadata == NULL)
275 {
276 LOG(INFO) << MISSING << "GetAllMetadata()";
277 isOptimal = false;
278 }
279
280 if (extensions_.lookupResourceAndParent == NULL)
281 {
282 LOG(INFO) << MISSING << "LookupResourceAndParent()";
283 isOptimal = false;
284 }
285
286 if (isOptimal)
287 {
288 LOG(INFO) << "The performance of the database index plugin "
289 << "is optimal for this version of Orthanc";
290 }
291 else
292 {
293 LOG(WARNING) << "Performance warning in the database index: "
294 << "Some extensions are missing in the plugin";
295 }
296
297 if (extensions_.getLastChangeIndex == NULL)
298 {
299 LOG(WARNING) << "The database extension GetLastChangeIndex() is missing";
300 }
301
302 if (extensions_.tagMostRecentPatient == NULL)
303 {
304 LOG(WARNING) << "The database extension TagMostRecentPatient() is missing "
305 << "(affected by issue 58)";
306 }
307 }
308
309
310 void OrthancPluginDatabase::Open()
311 {
312 CheckSuccess(backend_.open(payload_));
313
314 {
315 Transaction transaction(*this);
316 transaction.Begin();
317
318 std::string tmp;
319 fastGetTotalSize_ =
320 (LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) &&
321 tmp == "1");
322
323 if (fastGetTotalSize_)
324 {
325 currentDiskSize_ = 0; // Unused
326 }
327 else
328 {
329 // This is the case of database plugins using Orthanc SDK <= 1.5.2
330 LOG(WARNING) << "Your database index plugin is not compatible with multiple Orthanc writers";
331 currentDiskSize_ = GetTotalCompressedSize();
332 }
333
334 transaction.Commit(0);
335 }
336 }
337
338
339 void OrthancPluginDatabase::AddAttachment(int64_t id,
340 const FileInfo& attachment)
341 {
342 OrthancPluginAttachment tmp;
343 tmp.uuid = attachment.GetUuid().c_str();
344 tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
345 tmp.uncompressedSize = attachment.GetUncompressedSize();
346 tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
347 tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
348 tmp.compressedSize = attachment.GetCompressedSize();
349 tmp.compressedHash = attachment.GetCompressedMD5().c_str();
350
351 CheckSuccess(backend_.addAttachment(payload_, id, &tmp));
352 }
353
354
355 void OrthancPluginDatabase::AttachChild(int64_t parent,
356 int64_t child)
357 {
358 CheckSuccess(backend_.attachChild(payload_, parent, child));
359 }
360
361
362 void OrthancPluginDatabase::ClearChanges()
363 {
364 CheckSuccess(backend_.clearChanges(payload_));
365 }
366
367
368 void OrthancPluginDatabase::ClearExportedResources()
369 {
370 CheckSuccess(backend_.clearExportedResources(payload_));
371 }
372
373
374 int64_t OrthancPluginDatabase::CreateResource(const std::string& publicId,
375 ResourceType type)
376 {
377 int64_t id;
378 CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type)));
379 return id;
380 }
381
382
383 void OrthancPluginDatabase::DeleteAttachment(int64_t id,
384 FileContentType attachment)
385 {
386 CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)));
387 }
388
389
390 void OrthancPluginDatabase::DeleteMetadata(int64_t id,
391 MetadataType type)
392 {
393 CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)));
394 }
395
396
397 void OrthancPluginDatabase::DeleteResource(int64_t id)
398 {
399 CheckSuccess(backend_.deleteResource(payload_, id));
400 }
401
402
403 void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target,
404 int64_t id)
405 {
406 if (extensions_.getAllMetadata == NULL)
407 {
408 // Fallback implementation if extension is missing
409 target.clear();
410
411 ResetAnswers();
412 CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
413
414 if (type_ != _OrthancPluginDatabaseAnswerType_None &&
415 type_ != _OrthancPluginDatabaseAnswerType_Int32)
416 {
417 throw OrthancException(ErrorCode_DatabasePlugin);
418 }
419
420 target.clear();
421
422 if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
423 {
424 for (std::list<int32_t>::const_iterator
425 it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
426 {
427 MetadataType type = static_cast<MetadataType>(*it);
428
429 std::string value;
430 if (LookupMetadata(value, id, type))
431 {
432 target[type] = value;
433 }
434 }
435 }
436 }
437 else
438 {
439 ResetAnswers();
440
441 answerMetadata_ = &target;
442 target.clear();
443
444 CheckSuccess(extensions_.getAllMetadata(GetContext(), payload_, id));
445
446 if (type_ != _OrthancPluginDatabaseAnswerType_None &&
447 type_ != _OrthancPluginDatabaseAnswerType_Metadata)
448 {
449 throw OrthancException(ErrorCode_DatabasePlugin);
450 }
451 }
452 }
453
454
455 void OrthancPluginDatabase::GetAllInternalIds(std::list<int64_t>& target,
456 ResourceType resourceType)
457 {
458 if (extensions_.getAllInternalIds == NULL)
459 {
460 throw OrthancException(ErrorCode_DatabasePlugin,
461 "The database plugin does not implement the mandatory GetAllInternalIds() extension");
462 }
463
464 ResetAnswers();
465 CheckSuccess(extensions_.getAllInternalIds(GetContext(), payload_, Plugins::Convert(resourceType)));
466 ForwardAnswers(target);
467 }
468
469
470 void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
471 ResourceType resourceType)
472 {
473 ResetAnswers();
474 CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType)));
475 ForwardAnswers(target);
476 }
477
478
479 void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
480 ResourceType resourceType,
481 size_t since,
482 size_t limit)
483 {
484 if (extensions_.getAllPublicIdsWithLimit != NULL)
485 {
486 // This extension is available since Orthanc 0.9.4
487 ResetAnswers();
488 CheckSuccess(extensions_.getAllPublicIdsWithLimit
489 (GetContext(), payload_, Plugins::Convert(resourceType), since, limit));
490 ForwardAnswers(target);
491 }
492 else
493 {
494 // The extension is not available in the database plugin, use a
495 // fallback implementation
496 target.clear();
497
498 if (limit == 0)
499 {
500 return;
501 }
502
503 std::list<std::string> tmp;
504 GetAllPublicIds(tmp, resourceType);
505
506 if (tmp.size() <= since)
507 {
508 // Not enough results => empty answer
509 return;
510 }
511
512 std::list<std::string>::iterator current = tmp.begin();
513 std::advance(current, since);
514
515 while (limit > 0 && current != tmp.end())
516 {
517 target.push_back(*current);
518 --limit;
519 ++current;
520 }
521 }
522 }
523
524
525
526 void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/,
527 bool& done /*out*/,
528 int64_t since,
529 uint32_t maxResults)
530 {
531 ResetAnswers();
532 answerChanges_ = &target;
533 answerDone_ = &done;
534 done = false;
535
536 CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults));
537 }
538
539
540 void OrthancPluginDatabase::GetChildrenInternalId(std::list<int64_t>& target,
541 int64_t id)
542 {
543 ResetAnswers();
544 CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id));
545 ForwardAnswers(target);
546 }
547
548
549 void OrthancPluginDatabase::GetChildrenPublicId(std::list<std::string>& target,
550 int64_t id)
551 {
552 ResetAnswers();
553 CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id));
554 ForwardAnswers(target);
555 }
556
557
558 void OrthancPluginDatabase::GetExportedResources(std::list<ExportedResource>& target /*out*/,
559 bool& done /*out*/,
560 int64_t since,
561 uint32_t maxResults)
562 {
563 ResetAnswers();
564 answerExportedResources_ = &target;
565 answerDone_ = &done;
566 done = false;
567
568 CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults));
569 }
570
571
572 void OrthancPluginDatabase::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
573 {
574 bool ignored = false;
575
576 ResetAnswers();
577 answerChanges_ = &target;
578 answerDone_ = &ignored;
579
580 CheckSuccess(backend_.getLastChange(GetContext(), payload_));
581 }
582
583
584 void OrthancPluginDatabase::GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
585 {
586 bool ignored = false;
587
588 ResetAnswers();
589 answerExportedResources_ = &target;
590 answerDone_ = &ignored;
591
592 CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_));
593 }
594
595
596 void OrthancPluginDatabase::GetMainDicomTags(DicomMap& map,
597 int64_t id)
598 {
599 ResetAnswers();
600 answerDicomMap_ = &map;
601
602 CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id));
603 }
604
605
606 std::string OrthancPluginDatabase::GetPublicId(int64_t resourceId)
607 {
608 ResetAnswers();
609 std::string s;
610
611 CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId));
612
613 if (!ForwardSingleAnswer(s))
614 {
615 throw OrthancException(ErrorCode_DatabasePlugin);
616 }
617
618 return s;
619 }
620
621
622 uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
623 {
624 uint64_t count;
625 CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType)));
626 return count;
627 }
628
629
630 ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
631 {
632 OrthancPluginResourceType type;
633 CheckSuccess(backend_.getResourceType(&type, payload_, resourceId));
634 return Plugins::Convert(type);
635 }
636
637
638 uint64_t OrthancPluginDatabase::GetTotalCompressedSize()
639 {
640 uint64_t size;
641 CheckSuccess(backend_.getTotalCompressedSize(&size, payload_));
642 return size;
643 }
644
645
646 uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
647 {
648 uint64_t size;
649 CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_));
650 return size;
651 }
652
653
654 bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
655 {
656 int32_t existing;
657 CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId));
658 return (existing != 0);
659 }
660
661
662 bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
663 {
664 int32_t isProtected;
665 CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId));
666 return (isProtected != 0);
667 }
668
669
670 void OrthancPluginDatabase::ListAvailableAttachments(std::list<FileContentType>& target,
671 int64_t id)
672 {
673 ResetAnswers();
674
675 CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id));
676
677 if (type_ != _OrthancPluginDatabaseAnswerType_None &&
678 type_ != _OrthancPluginDatabaseAnswerType_Int32)
679 {
680 throw OrthancException(ErrorCode_DatabasePlugin);
681 }
682
683 target.clear();
684
685 if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
686 {
687 for (std::list<int32_t>::const_iterator
688 it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
689 {
690 target.push_back(static_cast<FileContentType>(*it));
691 }
692 }
693 }
694
695
696 void OrthancPluginDatabase::LogChange(int64_t internalId,
697 const ServerIndexChange& change)
698 {
699 OrthancPluginChange tmp;
700 tmp.seq = change.GetSeq();
701 tmp.changeType = static_cast<int32_t>(change.GetChangeType());
702 tmp.resourceType = Plugins::Convert(change.GetResourceType());
703 tmp.publicId = change.GetPublicId().c_str();
704 tmp.date = change.GetDate().c_str();
705
706 CheckSuccess(backend_.logChange(payload_, &tmp));
707 }
708
709
710 void OrthancPluginDatabase::LogExportedResource(const ExportedResource& resource)
711 {
712 OrthancPluginExportedResource tmp;
713 tmp.seq = resource.GetSeq();
714 tmp.resourceType = Plugins::Convert(resource.GetResourceType());
715 tmp.publicId = resource.GetPublicId().c_str();
716 tmp.modality = resource.GetModality().c_str();
717 tmp.date = resource.GetDate().c_str();
718 tmp.patientId = resource.GetPatientId().c_str();
719 tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str();
720 tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
721 tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
722
723 CheckSuccess(backend_.logExportedResource(payload_, &tmp));
724 }
725
726
727 bool OrthancPluginDatabase::LookupAttachment(FileInfo& attachment,
728 int64_t id,
729 FileContentType contentType)
730 {
731 ResetAnswers();
732
733 CheckSuccess(backend_.lookupAttachment
734 (GetContext(), payload_, id, static_cast<int32_t>(contentType)));
735
736 if (type_ == _OrthancPluginDatabaseAnswerType_None)
737 {
738 return false;
739 }
740 else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
741 answerAttachments_.size() == 1)
742 {
743 attachment = answerAttachments_.front();
744 return true;
745 }
746 else
747 {
748 throw OrthancException(ErrorCode_DatabasePlugin);
749 }
750 }
751
752
753 bool OrthancPluginDatabase::LookupGlobalProperty(std::string& target,
754 GlobalProperty property)
755 {
756 ResetAnswers();
757
758 CheckSuccess(backend_.lookupGlobalProperty
759 (GetContext(), payload_, static_cast<int32_t>(property)));
760
761 return ForwardSingleAnswer(target);
762 }
763
764
765 bool OrthancPluginDatabase::LookupMetadata(std::string& target,
766 int64_t id,
767 MetadataType type)
768 {
769 ResetAnswers();
770 CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)));
771 return ForwardSingleAnswer(target);
772 }
773
774
775 bool OrthancPluginDatabase::LookupParent(int64_t& parentId,
776 int64_t resourceId)
777 {
778 ResetAnswers();
779 CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId));
780 return ForwardSingleAnswer(parentId);
781 }
782
783
784 bool OrthancPluginDatabase::LookupResource(int64_t& id,
785 ResourceType& type,
786 const std::string& publicId)
787 {
788 ResetAnswers();
789
790 CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str()));
791
792 if (type_ == _OrthancPluginDatabaseAnswerType_None)
793 {
794 return false;
795 }
796 else if (type_ == _OrthancPluginDatabaseAnswerType_Resource &&
797 answerResources_.size() == 1)
798 {
799 id = answerResources_.front().first;
800 type = answerResources_.front().second;
801 return true;
802 }
803 else
804 {
805 throw OrthancException(ErrorCode_DatabasePlugin);
806 }
807 }
808
809
810 bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
811 {
812 ResetAnswers();
813 CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_));
814 return ForwardSingleAnswer(internalId);
815 }
816
817
818 bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId,
819 int64_t patientIdToAvoid)
820 {
821 ResetAnswers();
822 CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid));
823 return ForwardSingleAnswer(internalId);
824 }
825
826
827 void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property,
828 const std::string& value)
829 {
830 CheckSuccess(backend_.setGlobalProperty
831 (payload_, static_cast<int32_t>(property), value.c_str()));
832 }
833
834
835 void OrthancPluginDatabase::ClearMainDicomTags(int64_t id)
836 {
837 if (extensions_.clearMainDicomTags == NULL)
838 {
839 throw OrthancException(ErrorCode_DatabasePlugin,
840 "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
841 }
842
843 CheckSuccess(extensions_.clearMainDicomTags(payload_, id));
844 }
845
846
847 void OrthancPluginDatabase::SetMainDicomTag(int64_t id,
848 const DicomTag& tag,
849 const std::string& value)
850 {
851 OrthancPluginDicomTag tmp;
852 tmp.group = tag.GetGroup();
853 tmp.element = tag.GetElement();
854 tmp.value = value.c_str();
855
856 CheckSuccess(backend_.setMainDicomTag(payload_, id, &tmp));
857 }
858
859
860 void OrthancPluginDatabase::SetIdentifierTag(int64_t id,
861 const DicomTag& tag,
862 const std::string& value)
863 {
864 OrthancPluginDicomTag tmp;
865 tmp.group = tag.GetGroup();
866 tmp.element = tag.GetElement();
867 tmp.value = value.c_str();
868
869 CheckSuccess(backend_.setIdentifierTag(payload_, id, &tmp));
870 }
871
872
873 void OrthancPluginDatabase::SetMetadata(int64_t id,
874 MetadataType type,
875 const std::string& value)
876 {
877 CheckSuccess(backend_.setMetadata
878 (payload_, id, static_cast<int32_t>(type), value.c_str()));
879 }
880
881
882 void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId,
883 bool isProtected)
884 {
885 CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected));
886 }
887
888
889 IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction()
890 {
891 return new Transaction(*this);
892 }
893
894
895 static void ProcessEvent(IDatabaseListener& listener,
896 const _OrthancPluginDatabaseAnswer& answer)
897 {
898 switch (answer.type)
899 {
900 case _OrthancPluginDatabaseAnswerType_DeletedAttachment:
901 {
902 const OrthancPluginAttachment& attachment =
903 *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
904 listener.SignalFileDeleted(Convert(attachment));
905 break;
906 }
907
908 case _OrthancPluginDatabaseAnswerType_RemainingAncestor:
909 {
910 ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
911 listener.SignalRemainingAncestor(type, answer.valueString);
912 break;
913 }
914
915 case _OrthancPluginDatabaseAnswerType_DeletedResource:
916 {
917 ResourceType type = Plugins::Convert(static_cast<OrthancPluginResourceType>(answer.valueInt32));
918 ServerIndexChange change(ChangeType_Deleted, type, answer.valueString);
919 listener.SignalChange(change);
920 break;
921 }
922
923 default:
924 throw OrthancException(ErrorCode_DatabasePlugin);
925 }
926 }
927
928
929 unsigned int OrthancPluginDatabase::GetDatabaseVersion()
930 {
931 if (extensions_.getDatabaseVersion != NULL)
932 {
933 uint32_t version;
934 CheckSuccess(extensions_.getDatabaseVersion(&version, payload_));
935 return version;
936 }
937 else
938 {
939 // Before adding the "GetDatabaseVersion()" extension in plugins
940 // (OrthancPostgreSQL <= 1.2), the only supported DB schema was
941 // version 5.
942 return 5;
943 }
944 }
945
946
947 void OrthancPluginDatabase::Upgrade(unsigned int targetVersion,
948 IStorageArea& storageArea)
949 {
950 if (extensions_.upgradeDatabase != NULL)
951 {
952 Transaction transaction(*this);
953 transaction.Begin();
954
955 OrthancPluginErrorCode code = extensions_.upgradeDatabase(
956 payload_, targetVersion,
957 reinterpret_cast<OrthancPluginStorageArea*>(&storageArea));
958
959 if (code == OrthancPluginErrorCode_Success)
960 {
961 transaction.Commit(0);
962 }
963 else
964 {
965 transaction.Rollback();
966 errorDictionary_.LogError(code, true);
967 throw OrthancException(static_cast<ErrorCode>(code));
968 }
969 }
970 }
971
972
973 void OrthancPluginDatabase::AnswerReceived(const _OrthancPluginDatabaseAnswer& answer)
974 {
975 if (answer.type == _OrthancPluginDatabaseAnswerType_None)
976 {
977 throw OrthancException(ErrorCode_DatabasePlugin);
978 }
979
980 if (answer.type == _OrthancPluginDatabaseAnswerType_DeletedAttachment ||
981 answer.type == _OrthancPluginDatabaseAnswerType_DeletedResource ||
982 answer.type == _OrthancPluginDatabaseAnswerType_RemainingAncestor)
983 {
984 assert(listener_ != NULL);
985 ProcessEvent(*listener_, answer);
986 return;
987 }
988
989 if (type_ == _OrthancPluginDatabaseAnswerType_None)
990 {
991 type_ = answer.type;
992
993 switch (type_)
994 {
995 case _OrthancPluginDatabaseAnswerType_Int32:
996 answerInt32_.clear();
997 break;
998
999 case _OrthancPluginDatabaseAnswerType_Int64:
1000 answerInt64_.clear();
1001 break;
1002
1003 case _OrthancPluginDatabaseAnswerType_Resource:
1004 answerResources_.clear();
1005 break;
1006
1007 case _OrthancPluginDatabaseAnswerType_Attachment:
1008 answerAttachments_.clear();
1009 break;
1010
1011 case _OrthancPluginDatabaseAnswerType_String:
1012 answerStrings_.clear();
1013 break;
1014
1015 case _OrthancPluginDatabaseAnswerType_DicomTag:
1016 assert(answerDicomMap_ != NULL);
1017 answerDicomMap_->Clear();
1018 break;
1019
1020 case _OrthancPluginDatabaseAnswerType_Change:
1021 assert(answerChanges_ != NULL);
1022 answerChanges_->clear();
1023 break;
1024
1025 case _OrthancPluginDatabaseAnswerType_ExportedResource:
1026 assert(answerExportedResources_ != NULL);
1027 answerExportedResources_->clear();
1028 break;
1029
1030 case _OrthancPluginDatabaseAnswerType_MatchingResource:
1031 assert(answerMatchingResources_ != NULL);
1032 answerMatchingResources_->clear();
1033
1034 if (answerMatchingInstances_ != NULL)
1035 {
1036 answerMatchingInstances_->clear();
1037 }
1038
1039 break;
1040
1041 case _OrthancPluginDatabaseAnswerType_Metadata:
1042 assert(answerMetadata_ != NULL);
1043 answerMetadata_->clear();
1044 break;
1045
1046 default:
1047 throw OrthancException(ErrorCode_DatabasePlugin,
1048 "Unhandled type of answer for custom index plugin: " +
1049 boost::lexical_cast<std::string>(answer.type));
1050 }
1051 }
1052 else if (type_ != answer.type)
1053 {
1054 throw OrthancException(ErrorCode_DatabasePlugin,
1055 "Error in the plugin protocol: Cannot change the answer type");
1056 }
1057
1058 switch (answer.type)
1059 {
1060 case _OrthancPluginDatabaseAnswerType_Int32:
1061 {
1062 answerInt32_.push_back(answer.valueInt32);
1063 break;
1064 }
1065
1066 case _OrthancPluginDatabaseAnswerType_Int64:
1067 {
1068 answerInt64_.push_back(answer.valueInt64);
1069 break;
1070 }
1071
1072 case _OrthancPluginDatabaseAnswerType_Resource:
1073 {
1074 OrthancPluginResourceType type = static_cast<OrthancPluginResourceType>(answer.valueInt32);
1075 answerResources_.push_back(std::make_pair(answer.valueInt64, Plugins::Convert(type)));
1076 break;
1077 }
1078
1079 case _OrthancPluginDatabaseAnswerType_Attachment:
1080 {
1081 const OrthancPluginAttachment& attachment =
1082 *reinterpret_cast<const OrthancPluginAttachment*>(answer.valueGeneric);
1083
1084 answerAttachments_.push_back(Convert(attachment));
1085 break;
1086 }
1087
1088 case _OrthancPluginDatabaseAnswerType_DicomTag:
1089 {
1090 const OrthancPluginDicomTag& tag = *reinterpret_cast<const OrthancPluginDicomTag*>(answer.valueGeneric);
1091 assert(answerDicomMap_ != NULL);
1092 answerDicomMap_->SetValue(tag.group, tag.element, std::string(tag.value), false);
1093 break;
1094 }
1095
1096 case _OrthancPluginDatabaseAnswerType_String:
1097 {
1098 if (answer.valueString == NULL)
1099 {
1100 throw OrthancException(ErrorCode_DatabasePlugin);
1101 }
1102
1103 if (type_ == _OrthancPluginDatabaseAnswerType_None)
1104 {
1105 type_ = _OrthancPluginDatabaseAnswerType_String;
1106 answerStrings_.clear();
1107 }
1108 else if (type_ != _OrthancPluginDatabaseAnswerType_String)
1109 {
1110 throw OrthancException(ErrorCode_DatabasePlugin);
1111 }
1112
1113 answerStrings_.push_back(std::string(answer.valueString));
1114 break;
1115 }
1116
1117 case _OrthancPluginDatabaseAnswerType_Change:
1118 {
1119 assert(answerDone_ != NULL);
1120 if (answer.valueUint32 == 1)
1121 {
1122 *answerDone_ = true;
1123 }
1124 else if (*answerDone_)
1125 {
1126 throw OrthancException(ErrorCode_DatabasePlugin);
1127 }
1128 else
1129 {
1130 const OrthancPluginChange& change =
1131 *reinterpret_cast<const OrthancPluginChange*>(answer.valueGeneric);
1132 assert(answerChanges_ != NULL);
1133 answerChanges_->push_back
1134 (ServerIndexChange(change.seq,
1135 static_cast<ChangeType>(change.changeType),
1136 Plugins::Convert(change.resourceType),
1137 change.publicId,
1138 change.date));
1139 }
1140
1141 break;
1142 }
1143
1144 case _OrthancPluginDatabaseAnswerType_ExportedResource:
1145 {
1146 assert(answerDone_ != NULL);
1147 if (answer.valueUint32 == 1)
1148 {
1149 *answerDone_ = true;
1150 }
1151 else if (*answerDone_)
1152 {
1153 throw OrthancException(ErrorCode_DatabasePlugin);
1154 }
1155 else
1156 {
1157 const OrthancPluginExportedResource& exported =
1158 *reinterpret_cast<const OrthancPluginExportedResource*>(answer.valueGeneric);
1159 assert(answerExportedResources_ != NULL);
1160 answerExportedResources_->push_back
1161 (ExportedResource(exported.seq,
1162 Plugins::Convert(exported.resourceType),
1163 exported.publicId,
1164 exported.modality,
1165 exported.date,
1166 exported.patientId,
1167 exported.studyInstanceUid,
1168 exported.seriesInstanceUid,
1169 exported.sopInstanceUid));
1170 }
1171
1172 break;
1173 }
1174
1175 case _OrthancPluginDatabaseAnswerType_MatchingResource:
1176 {
1177 const OrthancPluginMatchingResource& match =
1178 *reinterpret_cast<const OrthancPluginMatchingResource*>(answer.valueGeneric);
1179
1180 if (match.resourceId == NULL)
1181 {
1182 throw OrthancException(ErrorCode_DatabasePlugin);
1183 }
1184
1185 assert(answerMatchingResources_ != NULL);
1186 answerMatchingResources_->push_back(match.resourceId);
1187
1188 if (answerMatchingInstances_ != NULL)
1189 {
1190 if (match.someInstanceId == NULL)
1191 {
1192 throw OrthancException(ErrorCode_DatabasePlugin);
1193 }
1194
1195 answerMatchingInstances_->push_back(match.someInstanceId);
1196 }
1197
1198 break;
1199 }
1200
1201 case _OrthancPluginDatabaseAnswerType_Metadata:
1202 {
1203 const OrthancPluginResourcesContentMetadata& metadata =
1204 *reinterpret_cast<const OrthancPluginResourcesContentMetadata*>(answer.valueGeneric);
1205
1206 MetadataType type = static_cast<MetadataType>(metadata.metadata);
1207
1208 if (metadata.value == NULL)
1209 {
1210 throw OrthancException(ErrorCode_DatabasePlugin);
1211 }
1212
1213 assert(answerMetadata_ != NULL &&
1214 answerMetadata_->find(type) == answerMetadata_->end());
1215 (*answerMetadata_) [type] = metadata.value;
1216 break;
1217 }
1218
1219 default:
1220 throw OrthancException(ErrorCode_DatabasePlugin,
1221 "Unhandled type of answer for custom index plugin: " +
1222 boost::lexical_cast<std::string>(answer.type));
1223 }
1224 }
1225
1226
1227 bool OrthancPluginDatabase::IsDiskSizeAbove(uint64_t threshold)
1228 {
1229 if (fastGetTotalSize_)
1230 {
1231 return GetTotalCompressedSize() > threshold;
1232 }
1233 else
1234 {
1235 assert(GetTotalCompressedSize() == currentDiskSize_);
1236 return currentDiskSize_ > threshold;
1237 }
1238 }
1239
1240
1241 void OrthancPluginDatabase::ApplyLookupResources(std::list<std::string>& resourcesId,
1242 std::list<std::string>* instancesId,
1243 const std::vector<DatabaseConstraint>& lookup,
1244 ResourceType queryLevel,
1245 size_t limit)
1246 {
1247 if (extensions_.lookupResources == NULL)
1248 {
1249 // Fallback to compatibility mode
1250 ILookupResources::Apply
1251 (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
1252 }
1253 else
1254 {
1255 std::vector<OrthancPluginDatabaseConstraint> constraints;
1256 std::vector< std::vector<const char*> > constraintsValues;
1257
1258 constraints.resize(lookup.size());
1259 constraintsValues.resize(lookup.size());
1260
1261 for (size_t i = 0; i < lookup.size(); i++)
1262 {
1263 lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
1264 }
1265
1266 ResetAnswers();
1267 answerMatchingResources_ = &resourcesId;
1268 answerMatchingInstances_ = instancesId;
1269
1270 CheckSuccess(extensions_.lookupResources(GetContext(), payload_, lookup.size(),
1271 (lookup.empty() ? NULL : &constraints[0]),
1272 Plugins::Convert(queryLevel),
1273 limit, (instancesId == NULL ? 0 : 1)));
1274 }
1275 }
1276
1277
1278 bool OrthancPluginDatabase::CreateInstance(
1279 IDatabaseWrapper::CreateInstanceResult& result,
1280 int64_t& instanceId,
1281 const std::string& patient,
1282 const std::string& study,
1283 const std::string& series,
1284 const std::string& instance)
1285 {
1286 if (extensions_.createInstance == NULL)
1287 {
1288 // Fallback to compatibility mode
1289 return ICreateInstance::Apply
1290 (*this, result, instanceId, patient, study, series, instance);
1291 }
1292 else
1293 {
1294 OrthancPluginCreateInstanceResult output;
1295 memset(&output, 0, sizeof(output));
1296
1297 CheckSuccess(extensions_.createInstance(&output, payload_, patient.c_str(),
1298 study.c_str(), series.c_str(), instance.c_str()));
1299
1300 instanceId = output.instanceId;
1301
1302 if (output.isNewInstance)
1303 {
1304 result.isNewPatient_ = output.isNewPatient;
1305 result.isNewStudy_ = output.isNewStudy;
1306 result.isNewSeries_ = output.isNewSeries;
1307 result.patientId_ = output.patientId;
1308 result.studyId_ = output.studyId;
1309 result.seriesId_ = output.seriesId;
1310 return true;
1311 }
1312 else
1313 {
1314 return false;
1315 }
1316 }
1317 }
1318
1319
1320 void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& result,
1321 ResourceType level,
1322 const DicomTag& tag,
1323 Compatibility::IdentifierConstraintType type,
1324 const std::string& value)
1325 {
1326 if (extensions_.lookupIdentifier3 == NULL)
1327 {
1328 throw OrthancException(ErrorCode_DatabasePlugin,
1329 "The database plugin does not implement the mandatory LookupIdentifier3() extension");
1330 }
1331
1332 OrthancPluginDicomTag tmp;
1333 tmp.group = tag.GetGroup();
1334 tmp.element = tag.GetElement();
1335 tmp.value = value.c_str();
1336
1337 ResetAnswers();
1338 CheckSuccess(extensions_.lookupIdentifier3(GetContext(), payload_, Plugins::Convert(level),
1339 &tmp, Compatibility::Convert(type)));
1340 ForwardAnswers(result);
1341 }
1342
1343
1344 void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result,
1345 ResourceType level,
1346 const DicomTag& tag,
1347 const std::string& start,
1348 const std::string& end)
1349 {
1350 if (extensions_.lookupIdentifierRange == NULL)
1351 {
1352 // Default implementation, for plugins using Orthanc SDK <= 1.3.2
1353
1354 LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
1355
1356 std::list<int64_t> b;
1357 LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
1358
1359 result.splice(result.end(), b);
1360 }
1361 else
1362 {
1363 ResetAnswers();
1364 CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level),
1365 tag.GetGroup(), tag.GetElement(),
1366 start.c_str(), end.c_str()));
1367 ForwardAnswers(result);
1368 }
1369 }
1370
1371
1372 void OrthancPluginDatabase::SetResourcesContent(const Orthanc::ResourcesContent& content)
1373 {
1374 if (extensions_.setResourcesContent == NULL)
1375 {
1376 ISetResourcesContent::Apply(*this, content);
1377 }
1378 else
1379 {
1380 std::vector<OrthancPluginResourcesContentTags> identifierTags;
1381 std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
1382 std::vector<OrthancPluginResourcesContentMetadata> metadata;
1383
1384 identifierTags.reserve(content.GetListTags().size());
1385 mainDicomTags.reserve(content.GetListTags().size());
1386 metadata.reserve(content.GetListMetadata().size());
1387
1388 for (ResourcesContent::ListTags::const_iterator
1389 it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
1390 {
1391 OrthancPluginResourcesContentTags tmp;
1392 tmp.resource = it->resourceId_;
1393 tmp.group = it->tag_.GetGroup();
1394 tmp.element = it->tag_.GetElement();
1395 tmp.value = it->value_.c_str();
1396
1397 if (it->isIdentifier_)
1398 {
1399 identifierTags.push_back(tmp);
1400 }
1401 else
1402 {
1403 mainDicomTags.push_back(tmp);
1404 }
1405 }
1406
1407 for (ResourcesContent::ListMetadata::const_iterator
1408 it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
1409 {
1410 OrthancPluginResourcesContentMetadata tmp;
1411 tmp.resource = it->resourceId_;
1412 tmp.metadata = it->metadata_;
1413 tmp.value = it->value_.c_str();
1414 metadata.push_back(tmp);
1415 }
1416
1417 assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
1418 metadata.size() == content.GetListMetadata().size());
1419
1420 CheckSuccess(extensions_.setResourcesContent(
1421 payload_,
1422 identifierTags.size(),
1423 (identifierTags.empty() ? NULL : &identifierTags[0]),
1424 mainDicomTags.size(),
1425 (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
1426 metadata.size(),
1427 (metadata.empty() ? NULL : &metadata[0])));
1428 }
1429 }
1430
1431
1432
1433 void OrthancPluginDatabase::GetChildrenMetadata(std::list<std::string>& target,
1434 int64_t resourceId,
1435 MetadataType metadata)
1436 {
1437 if (extensions_.getChildrenMetadata == NULL)
1438 {
1439 IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
1440 }
1441 else
1442 {
1443 ResetAnswers();
1444 CheckSuccess(extensions_.getChildrenMetadata
1445 (GetContext(), payload_, resourceId, static_cast<int32_t>(metadata)));
1446 ForwardAnswers(target);
1447 }
1448 }
1449
1450
1451 int64_t OrthancPluginDatabase::GetLastChangeIndex()
1452 {
1453 if (extensions_.getLastChangeIndex == NULL)
1454 {
1455 // This was the default behavior in Orthanc <= 1.5.1
1456 // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
1457 return 0;
1458 }
1459 else
1460 {
1461 int64_t result = 0;
1462 CheckSuccess(extensions_.getLastChangeIndex(&result, payload_));
1463 return result;
1464 }
1465 }
1466
1467
1468 void OrthancPluginDatabase::TagMostRecentPatient(int64_t patient)
1469 {
1470 if (extensions_.tagMostRecentPatient != NULL)
1471 {
1472 CheckSuccess(extensions_.tagMostRecentPatient(payload_, patient));
1473 }
1474 }
1475
1476
1477 bool OrthancPluginDatabase::LookupResourceAndParent(int64_t& id,
1478 ResourceType& type,
1479 std::string& parentPublicId,
1480 const std::string& publicId)
1481 {
1482 if (extensions_.lookupResourceAndParent == NULL)
1483 {
1484 return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
1485 }
1486 else
1487 {
1488 std::list<std::string> parent;
1489
1490 uint8_t isExisting;
1491 OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
1492
1493 ResetAnswers();
1494 CheckSuccess(extensions_.lookupResourceAndParent
1495 (GetContext(), &isExisting, &id, &pluginType, payload_, publicId.c_str()));
1496 ForwardAnswers(parent);
1497
1498 if (isExisting)
1499 {
1500 type = Plugins::Convert(pluginType);
1501
1502 if (parent.empty())
1503 {
1504 if (type != ResourceType_Patient)
1505 {
1506 throw OrthancException(ErrorCode_DatabasePlugin);
1507 }
1508 }
1509 else if (parent.size() == 1)
1510 {
1511 if ((type != ResourceType_Study &&
1512 type != ResourceType_Series &&
1513 type != ResourceType_Instance) ||
1514 parent.front().empty())
1515 {
1516 throw OrthancException(ErrorCode_DatabasePlugin);
1517 }
1518
1519 parentPublicId = parent.front();
1520 }
1521 else
1522 {
1523 throw OrthancException(ErrorCode_DatabasePlugin);
1524 }
1525
1526 return true;
1527 }
1528 else
1529 {
1530 return false;
1531 }
1532 }
1533 }
1534 }