comparison Framework/Plugins/DatabaseBackendAdapterV4.cpp @ 387:f35b17a38301

integration db-protobuf->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 03 Apr 2023 17:12:08 +0200
parents e236be67e5f9
children 3d6886f3e5b3
comparison
equal deleted inserted replaced
371:c1fe28de1bf6 387:f35b17a38301
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-2022 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Affero General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 **/
21
22
23 #include "DatabaseBackendAdapterV4.h"
24
25 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) // Macro introduced in Orthanc 1.3.1
26 # if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
27
28 #include "IndexConnectionsPool.h"
29
30 #include <OrthancDatabasePlugin.pb.h> // Include protobuf messages
31
32 #include <Logging.h>
33 #include <OrthancException.h>
34
35 #include <stdexcept>
36 #include <list>
37 #include <string>
38 #include <cassert>
39
40
41 #define ORTHANC_PLUGINS_DATABASE_CATCH(context) \
42
43
44 namespace OrthancDatabases
45 {
46 static bool isBackendInUse_ = false; // Only for sanity checks
47
48
49 static Orthanc::DatabasePluginMessages::ResourceType Convert(OrthancPluginResourceType resourceType)
50 {
51 switch (resourceType)
52 {
53 case OrthancPluginResourceType_Patient:
54 return Orthanc::DatabasePluginMessages::RESOURCE_PATIENT;
55
56 case OrthancPluginResourceType_Study:
57 return Orthanc::DatabasePluginMessages::RESOURCE_STUDY;
58
59 case OrthancPluginResourceType_Series:
60 return Orthanc::DatabasePluginMessages::RESOURCE_SERIES;
61
62 case OrthancPluginResourceType_Instance:
63 return Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE;
64
65 default:
66 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
67 }
68 }
69
70
71 static OrthancPluginResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType)
72 {
73 switch (resourceType)
74 {
75 case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT:
76 return OrthancPluginResourceType_Patient;
77
78 case Orthanc::DatabasePluginMessages::RESOURCE_STUDY:
79 return OrthancPluginResourceType_Study;
80
81 case Orthanc::DatabasePluginMessages::RESOURCE_SERIES:
82 return OrthancPluginResourceType_Series;
83
84 case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE:
85 return OrthancPluginResourceType_Instance;
86
87 default:
88 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
89 }
90 }
91
92
93 class Output : public IDatabaseBackendOutput
94 {
95 private:
96 Orthanc::DatabasePluginMessages::DeleteAttachment::Response* deleteAttachment_;
97 Orthanc::DatabasePluginMessages::DeleteResource::Response* deleteResource_;
98 Orthanc::DatabasePluginMessages::GetChanges::Response* getChanges_;
99 Orthanc::DatabasePluginMessages::GetExportedResources::Response* getExportedResources_;
100 Orthanc::DatabasePluginMessages::GetLastChange::Response* getLastChange_;
101 Orthanc::DatabasePluginMessages::GetLastExportedResource::Response* getLastExportedResource_;
102 Orthanc::DatabasePluginMessages::GetMainDicomTags::Response* getMainDicomTags_;
103 Orthanc::DatabasePluginMessages::LookupAttachment::Response* lookupAttachment_;
104 Orthanc::DatabasePluginMessages::LookupResources::Response* lookupResources_;
105
106 void Clear()
107 {
108 deleteAttachment_ = NULL;
109 deleteResource_ = NULL;
110 getChanges_ = NULL;
111 getExportedResources_ = NULL;
112 getLastChange_ = NULL;
113 getLastExportedResource_ = NULL;
114 getMainDicomTags_ = NULL;
115 lookupAttachment_ = NULL;
116 lookupResources_ = NULL;
117 }
118
119 public:
120 Output(Orthanc::DatabasePluginMessages::DeleteAttachment::Response& deleteAttachment)
121 {
122 Clear();
123 deleteAttachment_ = &deleteAttachment;
124 }
125
126 Output(Orthanc::DatabasePluginMessages::DeleteResource::Response& deleteResource)
127 {
128 Clear();
129 deleteResource_ = &deleteResource;
130 }
131
132 Output(Orthanc::DatabasePluginMessages::GetChanges::Response& getChanges)
133 {
134 Clear();
135 getChanges_ = &getChanges;
136 }
137
138 Output(Orthanc::DatabasePluginMessages::GetExportedResources::Response& getExportedResources)
139 {
140 Clear();
141 getExportedResources_ = &getExportedResources;
142 }
143
144 Output(Orthanc::DatabasePluginMessages::GetLastChange::Response& getLastChange)
145 {
146 Clear();
147 getLastChange_ = &getLastChange;
148 }
149
150 Output(Orthanc::DatabasePluginMessages::GetLastExportedResource::Response& getLastExportedResource)
151 {
152 Clear();
153 getLastExportedResource_ = &getLastExportedResource;
154 }
155
156 Output(Orthanc::DatabasePluginMessages::GetMainDicomTags::Response& getMainDicomTags)
157 {
158 Clear();
159 getMainDicomTags_ = &getMainDicomTags;
160 }
161
162 Output(Orthanc::DatabasePluginMessages::LookupAttachment::Response& lookupAttachment)
163 {
164 Clear();
165 lookupAttachment_ = &lookupAttachment;
166 }
167
168 Output(Orthanc::DatabasePluginMessages::LookupResources::Response& lookupResources)
169 {
170 Clear();
171 lookupResources_ = &lookupResources;
172 }
173
174 virtual void SignalDeletedAttachment(const std::string& uuid,
175 int32_t contentType,
176 uint64_t uncompressedSize,
177 const std::string& uncompressedHash,
178 int32_t compressionType,
179 uint64_t compressedSize,
180 const std::string& compressedHash) ORTHANC_OVERRIDE
181 {
182 Orthanc::DatabasePluginMessages::FileInfo* attachment;
183
184 if (deleteAttachment_ != NULL)
185 {
186 if (deleteAttachment_->has_deleted_attachment())
187 {
188 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
189 }
190
191 attachment = deleteAttachment_->mutable_deleted_attachment();
192 }
193 else if (deleteResource_ != NULL)
194 {
195 attachment = deleteResource_->add_deleted_attachments();
196 }
197 else
198 {
199 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
200 }
201
202 attachment->set_uuid(uuid);
203 attachment->set_content_type(contentType);
204 attachment->set_uncompressed_size(uncompressedSize);
205 attachment->set_uncompressed_hash(uncompressedHash);
206 attachment->set_compression_type(compressionType);
207 attachment->set_compressed_size(compressedSize);
208 attachment->set_compressed_hash(compressedHash);
209 }
210
211 virtual void SignalDeletedResource(const std::string& publicId,
212 OrthancPluginResourceType resourceType) ORTHANC_OVERRIDE
213 {
214 if (deleteResource_ != NULL)
215 {
216 Orthanc::DatabasePluginMessages::DeleteResource_Response_Resource* resource = deleteResource_->add_deleted_resources();
217 resource->set_level(Convert(resourceType));
218 resource->set_public_id(publicId);
219 }
220 else
221 {
222 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
223 }
224 }
225
226 virtual void SignalRemainingAncestor(const std::string& ancestorId,
227 OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE
228 {
229 if (deleteResource_ != NULL)
230 {
231 if (deleteResource_->is_remaining_ancestor())
232 {
233 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
234 }
235 else
236 {
237 deleteResource_->set_is_remaining_ancestor(true);
238 deleteResource_->mutable_remaining_ancestor()->set_level(Convert(ancestorType));
239 deleteResource_->mutable_remaining_ancestor()->set_public_id(ancestorId);
240 }
241 }
242 else
243 {
244 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
245 }
246 }
247
248 virtual void AnswerAttachment(const std::string& uuid,
249 int32_t contentType,
250 uint64_t uncompressedSize,
251 const std::string& uncompressedHash,
252 int32_t compressionType,
253 uint64_t compressedSize,
254 const std::string& compressedHash) ORTHANC_OVERRIDE
255 {
256 if (lookupAttachment_ != NULL)
257 {
258 if (lookupAttachment_->found())
259 {
260 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
261 }
262
263 lookupAttachment_->set_found(true);
264 lookupAttachment_->mutable_attachment()->set_uuid(uuid);
265 lookupAttachment_->mutable_attachment()->set_content_type(contentType);
266 lookupAttachment_->mutable_attachment()->set_uncompressed_size(uncompressedSize);
267 lookupAttachment_->mutable_attachment()->set_uncompressed_hash(uncompressedHash);
268 lookupAttachment_->mutable_attachment()->set_compression_type(compressionType);
269 lookupAttachment_->mutable_attachment()->set_compressed_size(compressedSize);
270 lookupAttachment_->mutable_attachment()->set_compressed_hash(compressedHash);
271 }
272 else
273 {
274 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
275 }
276 }
277
278 virtual void AnswerChange(int64_t seq,
279 int32_t changeType,
280 OrthancPluginResourceType resourceType,
281 const std::string& publicId,
282 const std::string& date) ORTHANC_OVERRIDE
283 {
284 Orthanc::DatabasePluginMessages::ServerIndexChange* change;
285
286 if (getChanges_ != NULL)
287 {
288 change = getChanges_->add_changes();
289 }
290 else if (getLastChange_ != NULL)
291 {
292 if (getLastChange_->found())
293 {
294 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
295 }
296
297 getLastChange_->set_found(true);
298 change = getLastChange_->mutable_change();
299 }
300 else
301 {
302 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
303 }
304
305 change->set_seq(seq);
306 change->set_change_type(changeType);
307 change->set_resource_type(Convert(resourceType));
308 change->set_public_id(publicId);
309 change->set_date(date);
310 }
311
312 virtual void AnswerDicomTag(uint16_t group,
313 uint16_t element,
314 const std::string& value) ORTHANC_OVERRIDE
315 {
316 if (getMainDicomTags_ != NULL)
317 {
318 Orthanc::DatabasePluginMessages::GetMainDicomTags_Response_Tag* tag = getMainDicomTags_->add_tags();
319 tag->set_group(group);
320 tag->set_element(element);
321 tag->set_value(value);
322 }
323 else
324 {
325 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
326 }
327 }
328
329 virtual void AnswerExportedResource(int64_t seq,
330 OrthancPluginResourceType resourceType,
331 const std::string& publicId,
332 const std::string& modality,
333 const std::string& date,
334 const std::string& patientId,
335 const std::string& studyInstanceUid,
336 const std::string& seriesInstanceUid,
337 const std::string& sopInstanceUid) ORTHANC_OVERRIDE
338 {
339 Orthanc::DatabasePluginMessages::ExportedResource* resource;
340
341 if (getExportedResources_ != NULL)
342 {
343 resource = getExportedResources_->add_resources();
344 }
345 else if (getLastExportedResource_ != NULL)
346 {
347 if (getLastExportedResource_->found())
348 {
349 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
350 }
351
352 getLastExportedResource_->set_found(true);
353 resource = getLastExportedResource_->mutable_resource();
354 }
355 else
356 {
357 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
358 }
359
360 resource->set_seq(seq);
361 resource->set_resource_type(Convert(resourceType));
362 resource->set_public_id(publicId);
363 resource->set_modality(modality);
364 resource->set_date(date);
365 resource->set_patient_id(patientId);
366 resource->set_study_instance_uid(studyInstanceUid);
367 resource->set_series_instance_uid(seriesInstanceUid);
368 resource->set_sop_instance_uid(sopInstanceUid);
369 }
370
371 virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE
372 {
373 if (lookupResources_ != NULL)
374 {
375 lookupResources_->add_resources_ids(resourceId);
376 }
377 else
378 {
379 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
380 }
381 }
382
383 virtual void AnswerMatchingResource(const std::string& resourceId,
384 const std::string& someInstanceId) ORTHANC_OVERRIDE
385 {
386 if (lookupResources_ != NULL)
387 {
388 lookupResources_->add_resources_ids(resourceId);
389 lookupResources_->add_instances_ids(someInstanceId);
390 }
391 else
392 {
393 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
394 }
395 }
396 };
397
398
399 static void ProcessDatabaseOperation(Orthanc::DatabasePluginMessages::DatabaseResponse& response,
400 const Orthanc::DatabasePluginMessages::DatabaseRequest& request,
401 IndexConnectionsPool& pool)
402 {
403 switch (request.operation())
404 {
405 case Orthanc::DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION:
406 {
407 IndexConnectionsPool::Accessor accessor(pool);
408 response.mutable_get_system_information()->set_database_version(accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()));
409 response.mutable_get_system_information()->set_supports_flush_to_disk(false);
410 response.mutable_get_system_information()->set_supports_revisions(accessor.GetBackend().HasRevisionsSupport());
411 break;
412 }
413
414 case Orthanc::DatabasePluginMessages::OPERATION_OPEN:
415 {
416 pool.OpenConnections();
417 break;
418 }
419
420 case Orthanc::DatabasePluginMessages::OPERATION_CLOSE:
421 {
422 pool.CloseConnections();
423 break;
424 }
425
426 case Orthanc::DatabasePluginMessages::OPERATION_FLUSH_TO_DISK:
427 {
428 // Raise an exception since "set_supports_flush_to_disk(false)"
429 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
430 }
431
432 case Orthanc::DatabasePluginMessages::OPERATION_START_TRANSACTION:
433 {
434 std::unique_ptr<IndexConnectionsPool::Accessor> transaction(new IndexConnectionsPool::Accessor(pool));
435
436 switch (request.start_transaction().type())
437 {
438 case Orthanc::DatabasePluginMessages::TRANSACTION_READ_ONLY:
439 transaction->GetManager().StartTransaction(TransactionType_ReadOnly);
440 break;
441
442 case Orthanc::DatabasePluginMessages::TRANSACTION_READ_WRITE:
443 transaction->GetManager().StartTransaction(TransactionType_ReadWrite);
444 break;
445
446 default:
447 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
448 }
449
450 response.mutable_start_transaction()->set_transaction(reinterpret_cast<intptr_t>(transaction.release()));
451 break;
452 }
453
454 case Orthanc::DatabasePluginMessages::OPERATION_UPGRADE:
455 {
456 IndexConnectionsPool::Accessor accessor(pool);
457 OrthancPluginStorageArea* storageArea = reinterpret_cast<OrthancPluginStorageArea*>(request.upgrade().storage_area());
458 accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), request.upgrade().target_version(), storageArea);
459 break;
460 }
461
462 case Orthanc::DatabasePluginMessages::OPERATION_FINALIZE_TRANSACTION:
463 {
464 IndexConnectionsPool::Accessor* transaction = reinterpret_cast<IndexConnectionsPool::Accessor*>(request.finalize_transaction().transaction());
465
466 if (transaction == NULL)
467 {
468 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
469 }
470 else
471 {
472 delete transaction;
473 }
474
475 break;
476 }
477
478 default:
479 LOG(ERROR) << "Not implemented database operation from protobuf: " << request.operation();
480 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
481 }
482 }
483
484
485 static void ApplyLookupResources(Orthanc::DatabasePluginMessages::LookupResources_Response& response,
486 const Orthanc::DatabasePluginMessages::LookupResources_Request& request,
487 IndexBackend& backend,
488 DatabaseManager& manager)
489 {
490 std::vector<Orthanc::DatabaseConstraint> lookup;
491 lookup.reserve(request.lookup().size());
492
493 size_t countValues = 0;
494
495 for (int i = 0; i < request.lookup().size(); i++)
496 {
497 const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i);
498 countValues += constraint.values().size();
499 }
500
501 std::vector<const char*> values;
502 values.reserve(countValues);
503
504 for (int i = 0; i < request.lookup().size(); i++)
505 {
506 const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i);
507
508 if (constraint.tag_group() > 0xffffu ||
509 constraint.tag_element() > 0xffffu)
510 {
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
512 }
513
514 OrthancPluginDatabaseConstraint c;
515 c.level = Convert(constraint.level());
516 c.tagGroup = constraint.tag_group();
517 c.tagElement = constraint.tag_element();
518 c.isIdentifierTag = (constraint.is_identifier_tag() ? 1 : 0);
519 c.isCaseSensitive = (constraint.is_case_sensitive() ? 1 : 0);
520 c.isMandatory = (constraint.is_mandatory() ? 1 : 0);
521
522 switch (constraint.type())
523 {
524 case Orthanc::DatabasePluginMessages::CONSTRAINT_EQUAL:
525 c.type = OrthancPluginConstraintType_Equal;
526 break;
527
528 case Orthanc::DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL:
529 c.type = OrthancPluginConstraintType_SmallerOrEqual;
530 break;
531
532 case Orthanc::DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL:
533 c.type = OrthancPluginConstraintType_GreaterOrEqual;
534 break;
535
536 case Orthanc::DatabasePluginMessages::CONSTRAINT_WILDCARD:
537 c.type = OrthancPluginConstraintType_Wildcard;
538 break;
539
540 case Orthanc::DatabasePluginMessages::CONSTRAINT_LIST:
541 c.type = OrthancPluginConstraintType_List;
542 break;
543
544 default:
545 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
546 }
547
548 c.valuesCount = constraint.values().size();
549
550 if (c.valuesCount == 0)
551 {
552 c.values = NULL;
553 }
554 else
555 {
556 c.values = &values[values.size()];
557
558 for (int j = 0; j < constraint.values().size(); j++)
559 {
560 assert(values.size() < countValues);
561 values.push_back(constraint.values(j).c_str());
562 }
563 }
564
565 lookup.push_back(Orthanc::DatabaseConstraint(c));
566 }
567
568 assert(values.size() == countValues);
569
570 Output output(response);
571 backend.LookupResources(output, manager, lookup, Convert(request.query_level()),
572 request.limit(), request.retrieve_instances_ids());
573 }
574
575
576 static void ProcessTransactionOperation(Orthanc::DatabasePluginMessages::TransactionResponse& response,
577 const Orthanc::DatabasePluginMessages::TransactionRequest& request,
578 IndexBackend& backend,
579 DatabaseManager& manager)
580 {
581 switch (request.operation())
582 {
583 case Orthanc::DatabasePluginMessages::OPERATION_ROLLBACK:
584 {
585 manager.RollbackTransaction();
586 break;
587 }
588
589 case Orthanc::DatabasePluginMessages::OPERATION_COMMIT:
590 {
591 manager.CommitTransaction();
592 break;
593 }
594
595 case Orthanc::DatabasePluginMessages::OPERATION_ADD_ATTACHMENT:
596 {
597 OrthancPluginAttachment attachment;
598 attachment.uuid = request.add_attachment().attachment().uuid().c_str();
599 attachment.contentType = request.add_attachment().attachment().content_type();
600 attachment.uncompressedSize = request.add_attachment().attachment().uncompressed_size();
601 attachment.uncompressedHash = request.add_attachment().attachment().uncompressed_hash().c_str();
602 attachment.compressionType = request.add_attachment().attachment().compression_type();
603 attachment.compressedSize = request.add_attachment().attachment().compressed_size();
604 attachment.compressedHash = request.add_attachment().attachment().compressed_hash().c_str();
605
606 backend.AddAttachment(manager, request.add_attachment().id(), attachment, request.add_attachment().revision());
607 break;
608 }
609
610 case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_CHANGES:
611 {
612 backend.ClearChanges(manager);
613 break;
614 }
615
616 case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES:
617 {
618 backend.ClearExportedResources(manager);
619 break;
620 }
621
622 case Orthanc::DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT:
623 {
624 Output output(*response.mutable_delete_attachment());
625 backend.DeleteAttachment(output, manager, request.delete_attachment().id(), request.delete_attachment().type());
626 break;
627 }
628
629 case Orthanc::DatabasePluginMessages::OPERATION_DELETE_METADATA:
630 {
631 backend.DeleteMetadata(manager, request.delete_metadata().id(), request.delete_metadata().type());
632 break;
633 }
634
635 case Orthanc::DatabasePluginMessages::OPERATION_DELETE_RESOURCE:
636 {
637 response.mutable_delete_resource()->set_is_remaining_ancestor(false);
638
639 Output output(*response.mutable_delete_resource());
640 backend.DeleteResource(output, manager, request.delete_resource().id());
641 break;
642 }
643
644 case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_METADATA:
645 {
646 typedef std::map<int32_t, std::string> Values;
647
648 Values values;
649 backend.GetAllMetadata(values, manager, request.get_all_metadata().id());
650
651 response.mutable_get_all_metadata()->mutable_metadata()->Reserve(values.size());
652 for (Values::const_iterator it = values.begin(); it != values.end(); ++it)
653 {
654 Orthanc::DatabasePluginMessages::GetAllMetadata_Response_Metadata* metadata =
655 response.mutable_get_all_metadata()->add_metadata();
656 metadata->set_type(it->first);
657 metadata->set_value(it->second);
658 }
659
660 break;
661 }
662
663 case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS:
664 {
665 std::list<std::string> values;
666 backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids().resource_type()));
667
668 response.mutable_get_all_public_ids()->mutable_ids()->Reserve(values.size());
669 for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
670 {
671 response.mutable_get_all_public_ids()->add_ids(*it);
672 }
673
674 break;
675 }
676
677 case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS:
678 {
679 std::list<std::string> values;
680 backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids_with_limits().resource_type()),
681 request.get_all_public_ids_with_limits().since(),
682 request.get_all_public_ids_with_limits().limit());
683
684 response.mutable_get_all_public_ids_with_limits()->mutable_ids()->Reserve(values.size());
685 for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
686 {
687 response.mutable_get_all_public_ids_with_limits()->add_ids(*it);
688 }
689
690 break;
691 }
692
693 case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES:
694 {
695 Output output(*response.mutable_get_changes());
696
697 bool done;
698 backend.GetChanges(output, done, manager, request.get_changes().since(), request.get_changes().limit());
699
700 response.mutable_get_changes()->set_done(done);
701 break;
702 }
703
704 case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID:
705 {
706 std::list<int64_t> values;
707 backend.GetChildrenInternalId(values, manager, request.get_children_internal_id().id());
708
709 response.mutable_get_children_internal_id()->mutable_ids()->Reserve(values.size());
710 for (std::list<int64_t>::const_iterator it = values.begin(); it != values.end(); ++it)
711 {
712 response.mutable_get_children_internal_id()->add_ids(*it);
713 }
714
715 break;
716 }
717
718 case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_PUBLIC_ID:
719 {
720 std::list<std::string> values;
721 backend.GetChildrenPublicId(values, manager, request.get_children_public_id().id());
722
723 response.mutable_get_children_public_id()->mutable_ids()->Reserve(values.size());
724 for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
725 {
726 response.mutable_get_children_public_id()->add_ids(*it);
727 }
728
729 break;
730 }
731
732 case Orthanc::DatabasePluginMessages::OPERATION_GET_EXPORTED_RESOURCES:
733 {
734 Output output(*response.mutable_get_exported_resources());
735
736 bool done;
737 backend.GetExportedResources(output, done, manager, request.get_exported_resources().since(),
738 request.get_exported_resources().limit());
739
740 response.mutable_get_exported_resources()->set_done(done);
741 break;
742 }
743
744 case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE:
745 {
746 response.mutable_get_last_change()->set_found(false);
747
748 Output output(*response.mutable_get_last_change());
749 backend.GetLastChange(output, manager);
750 break;
751 }
752
753 case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_EXPORTED_RESOURCE:
754 {
755 response.mutable_get_last_exported_resource()->set_found(false);
756
757 Output output(*response.mutable_get_last_exported_resource());
758 backend.GetLastExportedResource(output, manager);
759 break;
760 }
761
762 case Orthanc::DatabasePluginMessages::OPERATION_GET_MAIN_DICOM_TAGS:
763 {
764 Output output(*response.mutable_get_main_dicom_tags());
765 backend.GetMainDicomTags(output, manager, request.get_main_dicom_tags().id());
766 break;
767 }
768
769 case Orthanc::DatabasePluginMessages::OPERATION_GET_PUBLIC_ID:
770 {
771 const std::string id = backend.GetPublicId(manager, request.get_public_id().id());
772 response.mutable_get_public_id()->set_id(id);
773 break;
774 }
775
776 case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCES_COUNT:
777 {
778 OrthancPluginResourceType type = Convert(request.get_resources_count().type());
779 uint64_t count = backend.GetResourcesCount(manager, type);
780 response.mutable_get_resources_count()->set_count(count);
781 break;
782 }
783
784 case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCE_TYPE:
785 {
786 OrthancPluginResourceType type = backend.GetResourceType(manager, request.get_resource_type().id());
787 response.mutable_get_resource_type()->set_type(Convert(type));
788 break;
789 }
790
791 case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_COMPRESSED_SIZE:
792 {
793 response.mutable_get_total_compressed_size()->set_size(backend.GetTotalCompressedSize(manager));
794 break;
795 }
796
797 case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE:
798 {
799 response.mutable_get_total_uncompressed_size()->set_size(backend.GetTotalUncompressedSize(manager));
800 break;
801 }
802
803 case Orthanc::DatabasePluginMessages::OPERATION_IS_PROTECTED_PATIENT:
804 {
805 bool isProtected = backend.IsProtectedPatient(manager, request.is_protected_patient().patient_id());
806 response.mutable_is_protected_patient()->set_protected_patient(isProtected);
807 break;
808 }
809
810 case Orthanc::DatabasePluginMessages::OPERATION_LIST_AVAILABLE_ATTACHMENTS:
811 {
812 std::list<int32_t> values;
813 backend.ListAvailableAttachments(values, manager, request.list_available_attachments().id());
814
815 response.mutable_list_available_attachments()->mutable_attachments()->Reserve(values.size());
816 for (std::list<int32_t>::const_iterator it = values.begin(); it != values.end(); ++it)
817 {
818 response.mutable_list_available_attachments()->add_attachments(*it);
819 }
820
821 break;
822 }
823
824 case Orthanc::DatabasePluginMessages::OPERATION_LOG_CHANGE:
825 {
826 backend.LogChange(manager, request.log_change().change_type(),
827 request.log_change().resource_id(),
828 Convert(request.log_change().resource_type()),
829 request.log_change().date().c_str());
830 break;
831 }
832
833 case Orthanc::DatabasePluginMessages::OPERATION_LOG_EXPORTED_RESOURCE:
834 {
835 backend.LogExportedResource(manager,
836 Convert(request.log_exported_resource().resource_type()),
837 request.log_exported_resource().public_id().c_str(),
838 request.log_exported_resource().modality().c_str(),
839 request.log_exported_resource().date().c_str(),
840 request.log_exported_resource().patient_id().c_str(),
841 request.log_exported_resource().study_instance_uid().c_str(),
842 request.log_exported_resource().series_instance_uid().c_str(),
843 request.log_exported_resource().sop_instance_uid().c_str());
844 break;
845 }
846
847 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_ATTACHMENT:
848 {
849 Output output(*response.mutable_lookup_attachment());
850
851 int64_t revision = -1;
852 backend.LookupAttachment(output, revision, manager, request.lookup_attachment().id(), request.lookup_attachment().content_type());
853
854 if (response.lookup_attachment().found())
855 {
856 response.mutable_lookup_attachment()->set_revision(revision);
857 }
858
859 break;
860 }
861
862 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_GLOBAL_PROPERTY:
863 {
864 std::string value;
865 if (backend.LookupGlobalProperty(value, manager, request.lookup_global_property().server_id().c_str(),
866 request.lookup_global_property().property()))
867 {
868 response.mutable_lookup_global_property()->set_found(true);
869 response.mutable_lookup_global_property()->set_value(value);
870 }
871 else
872 {
873 response.mutable_lookup_global_property()->set_found(false);
874 }
875
876 break;
877 }
878
879 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_METADATA:
880 {
881 std::string value;
882 int64_t revision = -1;
883 if (backend.LookupMetadata(value, revision, manager, request.lookup_metadata().id(), request.lookup_metadata().metadata_type()))
884 {
885 response.mutable_lookup_metadata()->set_found(true);
886 response.mutable_lookup_metadata()->set_value(value);
887 response.mutable_lookup_metadata()->set_revision(revision);
888 }
889 else
890 {
891 response.mutable_lookup_metadata()->set_found(false);
892 }
893
894 break;
895 }
896
897 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_PARENT:
898 {
899 int64_t parent = -1;
900 if (backend.LookupParent(parent, manager, request.lookup_parent().id()))
901 {
902 response.mutable_lookup_parent()->set_found(true);
903 response.mutable_lookup_parent()->set_parent(parent);
904 }
905 else
906 {
907 response.mutable_lookup_parent()->set_found(false);
908 }
909
910 break;
911 }
912
913 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE:
914 {
915 int64_t internalId = -1;
916 OrthancPluginResourceType type;
917 if (backend.LookupResource(internalId, type, manager, request.lookup_resource().public_id().c_str()))
918 {
919 response.mutable_lookup_resource()->set_found(true);
920 response.mutable_lookup_resource()->set_internal_id(internalId);
921 response.mutable_lookup_resource()->set_type(Convert(type));
922 }
923 else
924 {
925 response.mutable_lookup_resource()->set_found(false);
926 }
927
928 break;
929 }
930
931 case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE:
932 {
933 int64_t patientId = -1;
934 if (backend.SelectPatientToRecycle(patientId, manager))
935 {
936 response.mutable_select_patient_to_recycle()->set_found(true);
937 response.mutable_select_patient_to_recycle()->set_patient_id(patientId);
938 }
939 else
940 {
941 response.mutable_select_patient_to_recycle()->set_found(false);
942 }
943
944 break;
945 }
946
947 case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID:
948 {
949 int64_t patientId = -1;
950 if (backend.SelectPatientToRecycle(patientId, manager, request.select_patient_to_recycle_with_avoid().patient_id_to_avoid()))
951 {
952 response.mutable_select_patient_to_recycle_with_avoid()->set_found(true);
953 response.mutable_select_patient_to_recycle_with_avoid()->set_patient_id(patientId);
954 }
955 else
956 {
957 response.mutable_select_patient_to_recycle_with_avoid()->set_found(false);
958 }
959
960 break;
961 }
962
963 case Orthanc::DatabasePluginMessages::OPERATION_SET_GLOBAL_PROPERTY:
964 {
965 backend.SetGlobalProperty(manager, request.set_global_property().server_id().c_str(),
966 request.set_global_property().property(),
967 request.set_global_property().value().c_str());
968 break;
969 }
970
971 case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_MAIN_DICOM_TAGS:
972 {
973 backend.ClearMainDicomTags(manager, request.clear_main_dicom_tags().id());
974 break;
975 }
976
977 case Orthanc::DatabasePluginMessages::OPERATION_SET_METADATA:
978 {
979 backend.SetMetadata(manager, request.set_metadata().id(),
980 request.set_metadata().metadata_type(),
981 request.set_metadata().value().c_str(),
982 request.set_metadata().revision());
983 break;
984 }
985
986 case Orthanc::DatabasePluginMessages::OPERATION_SET_PROTECTED_PATIENT:
987 {
988 backend.SetProtectedPatient(manager, request.set_protected_patient().patient_id(),
989 request.set_protected_patient().protected_patient());
990 break;
991 }
992
993 case Orthanc::DatabasePluginMessages::OPERATION_IS_DISK_SIZE_ABOVE:
994 {
995 bool above = (backend.GetTotalCompressedSize(manager) >= request.is_disk_size_above().threshold());
996 response.mutable_is_disk_size_above()->set_result(above);
997 break;
998 }
999
1000 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES:
1001 {
1002 ApplyLookupResources(*response.mutable_lookup_resources(), request.lookup_resources(), backend, manager);
1003 break;
1004 }
1005
1006 case Orthanc::DatabasePluginMessages::OPERATION_CREATE_INSTANCE:
1007 {
1008 const char* hashPatient = request.create_instance().patient().c_str();
1009 const char* hashStudy = request.create_instance().study().c_str();
1010 const char* hashSeries = request.create_instance().series().c_str();
1011 const char* hashInstance = request.create_instance().instance().c_str();
1012
1013 OrthancPluginCreateInstanceResult result;
1014
1015 if (backend.HasCreateInstance())
1016 {
1017 backend.CreateInstance(result, manager, hashPatient, hashStudy, hashSeries, hashInstance);
1018 }
1019 else
1020 {
1021 backend.CreateInstanceGeneric(result, manager, hashPatient, hashStudy, hashSeries, hashInstance);
1022 }
1023
1024 response.mutable_create_instance()->set_is_new_instance(result.isNewInstance);
1025 response.mutable_create_instance()->set_instance_id(result.instanceId);
1026
1027 if (result.isNewInstance)
1028 {
1029 response.mutable_create_instance()->set_is_new_patient(result.isNewPatient);
1030 response.mutable_create_instance()->set_is_new_study(result.isNewStudy);
1031 response.mutable_create_instance()->set_is_new_series(result.isNewSeries);
1032 response.mutable_create_instance()->set_patient_id(result.patientId);
1033 response.mutable_create_instance()->set_study_id(result.studyId);
1034 response.mutable_create_instance()->set_series_id(result.seriesId);
1035 }
1036
1037 break;
1038 }
1039
1040 case Orthanc::DatabasePluginMessages::OPERATION_SET_RESOURCES_CONTENT:
1041 {
1042 std::vector<OrthancPluginResourcesContentTags> identifierTags;
1043 std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
1044
1045 identifierTags.reserve(request.set_resources_content().tags().size());
1046 mainDicomTags.reserve(request.set_resources_content().tags().size());
1047
1048 for (int i = 0; i < request.set_resources_content().tags().size(); i++)
1049 {
1050 if (request.set_resources_content().tags(i).group() > 0xffffu ||
1051 request.set_resources_content().tags(i).element() > 0xffffu)
1052 {
1053 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1054 }
1055
1056 OrthancPluginResourcesContentTags tag;
1057 tag.resource = request.set_resources_content().tags(i).resource_id();
1058 tag.group = request.set_resources_content().tags(i).group();
1059 tag.element = request.set_resources_content().tags(i).element();
1060 tag.value = request.set_resources_content().tags(i).value().c_str();
1061
1062 if (request.set_resources_content().tags(i).is_identifier())
1063 {
1064 identifierTags.push_back(tag);
1065 }
1066 else
1067 {
1068 mainDicomTags.push_back(tag);
1069 }
1070 }
1071
1072 std::vector<OrthancPluginResourcesContentMetadata> metadata;
1073 metadata.reserve(request.set_resources_content().metadata().size());
1074
1075 for (int i = 0; i < request.set_resources_content().metadata().size(); i++)
1076 {
1077 OrthancPluginResourcesContentMetadata item;
1078 item.resource = request.set_resources_content().metadata(i).resource_id();
1079 item.metadata = request.set_resources_content().metadata(i).metadata();
1080 item.value = request.set_resources_content().metadata(i).value().c_str();
1081 metadata.push_back(item);
1082 }
1083
1084 backend.SetResourcesContent(manager,
1085 identifierTags.size(), (identifierTags.empty() ? NULL : &identifierTags[0]),
1086 mainDicomTags.size(), (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
1087 metadata.size(), (metadata.empty() ? NULL : &metadata[0]));
1088 break;
1089 }
1090
1091 case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_METADATA:
1092 {
1093 std::list<std::string> values;
1094 backend.GetChildrenMetadata(values, manager, request.get_children_metadata().id(), request.get_children_metadata().metadata());
1095
1096 response.mutable_get_children_metadata()->mutable_values()->Reserve(values.size());
1097 for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
1098 {
1099 response.mutable_get_children_metadata()->add_values(*it);
1100 }
1101
1102 break;
1103 }
1104
1105 case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE_INDEX:
1106 {
1107 response.mutable_get_last_change_index()->set_result(backend.GetLastChangeIndex(manager));
1108 break;
1109 }
1110
1111 case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE_AND_PARENT:
1112 {
1113 int64_t id;
1114 OrthancPluginResourceType type;
1115 std::string parent;
1116
1117 if (backend.LookupResourceAndParent(id, type, parent, manager, request.lookup_resource_and_parent().public_id().c_str()))
1118 {
1119 response.mutable_lookup_resource_and_parent()->set_found(true);
1120 response.mutable_lookup_resource_and_parent()->set_id(id);
1121 response.mutable_lookup_resource_and_parent()->set_type(Convert(type));
1122
1123 switch (type)
1124 {
1125 case OrthancPluginResourceType_Study:
1126 case OrthancPluginResourceType_Series:
1127 case OrthancPluginResourceType_Instance:
1128 if (parent.empty())
1129 {
1130 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1131 }
1132 else
1133 {
1134 response.mutable_lookup_resource_and_parent()->set_parent_public_id(parent);
1135 }
1136 break;
1137
1138 case OrthancPluginResourceType_Patient:
1139 if (!parent.empty())
1140 {
1141 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1142 }
1143 break;
1144
1145 default:
1146 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1147 }
1148 }
1149 else
1150 {
1151 response.mutable_lookup_resource_and_parent()->set_found(false);
1152 }
1153
1154 break;
1155 }
1156
1157 default:
1158 LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation();
1159 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1160 }
1161 }
1162
1163
1164 static OrthancPluginErrorCode CallBackend(OrthancPluginMemoryBuffer64* serializedResponse,
1165 void* rawPool,
1166 const void* requestData,
1167 uint64_t requestSize)
1168 {
1169 Orthanc::DatabasePluginMessages::Request request;
1170 if (!request.ParseFromArray(requestData, requestSize))
1171 {
1172 LOG(ERROR) << "Cannot parse message from the Orthanc core using protobuf";
1173 return OrthancPluginErrorCode_InternalError;
1174 }
1175
1176 if (rawPool == NULL)
1177 {
1178 LOG(ERROR) << "Received a NULL pointer from the database";
1179 return OrthancPluginErrorCode_InternalError;
1180 }
1181
1182 IndexConnectionsPool& pool = *reinterpret_cast<IndexConnectionsPool*>(rawPool);
1183
1184 try
1185 {
1186 Orthanc::DatabasePluginMessages::Response response;
1187
1188 switch (request.type())
1189 {
1190 case Orthanc::DatabasePluginMessages::REQUEST_DATABASE:
1191 ProcessDatabaseOperation(*response.mutable_database_response(), request.database_request(), pool);
1192 break;
1193
1194 case Orthanc::DatabasePluginMessages::REQUEST_TRANSACTION:
1195 {
1196 IndexConnectionsPool::Accessor& transaction = *reinterpret_cast<IndexConnectionsPool::Accessor*>(request.transaction_request().transaction());
1197 ProcessTransactionOperation(*response.mutable_transaction_response(), request.transaction_request(),
1198 transaction.GetBackend(), transaction.GetManager());
1199 break;
1200 }
1201
1202 default:
1203 LOG(ERROR) << "Not implemented request type from protobuf: " << request.type();
1204 break;
1205 }
1206
1207 std::string s;
1208 if (!response.SerializeToString(&s))
1209 {
1210 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot serialize to protobuf");
1211 }
1212
1213 if (OrthancPluginCreateMemoryBuffer64(pool.GetContext(), serializedResponse, s.size()) != OrthancPluginErrorCode_Success)
1214 {
1215 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "Cannot allocate a memory buffer");
1216 }
1217
1218 if (!s.empty())
1219 {
1220 assert(serializedResponse->size == s.size());
1221 memcpy(serializedResponse->data, s.c_str(), s.size());
1222 }
1223
1224 return OrthancPluginErrorCode_Success;
1225 }
1226 catch (::Orthanc::OrthancException& e)
1227 {
1228 LOG(ERROR) << "Exception in database back-end: " << e.What();
1229 return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
1230 }
1231 catch (::std::runtime_error& e)
1232 {
1233 LOG(ERROR) << "Exception in database back-end: " << e.what();
1234 return OrthancPluginErrorCode_DatabasePlugin;
1235 }
1236 catch (...)
1237 {
1238 LOG(ERROR) << "Native exception";
1239 return OrthancPluginErrorCode_DatabasePlugin;
1240 }
1241 }
1242
1243 static void FinalizeBackend(void* rawPool)
1244 {
1245 if (rawPool != NULL)
1246 {
1247 IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(rawPool);
1248
1249 if (isBackendInUse_)
1250 {
1251 isBackendInUse_ = false;
1252 }
1253 else
1254 {
1255 LOG(ERROR) << "More than one index backend was registered, internal error";
1256 }
1257
1258 delete pool;
1259 }
1260 else
1261 {
1262 LOG(ERROR) << "Received a null pointer from the Orthanc core, internal error";
1263 }
1264 }
1265
1266
1267 void DatabaseBackendAdapterV4::Register(IndexBackend* backend,
1268 size_t countConnections,
1269 unsigned int maxDatabaseRetries)
1270 {
1271 std::unique_ptr<IndexConnectionsPool> pool(new IndexConnectionsPool(backend, countConnections));
1272
1273 if (isBackendInUse_)
1274 {
1275 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1276 }
1277
1278 OrthancPluginContext* context = backend->GetContext();
1279
1280 if (OrthancPluginRegisterDatabaseBackendV4(context, pool.release(), maxDatabaseRetries,
1281 CallBackend, FinalizeBackend) != OrthancPluginErrorCode_Success)
1282 {
1283 delete backend;
1284 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend");
1285 }
1286
1287 isBackendInUse_ = true;
1288 }
1289
1290
1291 void DatabaseBackendAdapterV4::Finalize()
1292 {
1293 if (isBackendInUse_)
1294 {
1295 LOG(ERROR) << "The Orthanc core has not destructed the index backend, internal error";
1296 }
1297 }
1298 }
1299
1300 # endif
1301 #endif